Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

semmelei: Dispatch the Procurement to the Bakery after a Review Window

Open
#87 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
sql, typescript

Research direction

Start with the schedule module, pickup-day dispatch logic, and the HTTP test harness from blocked issue #78; this issue also depends on #84 for Bakery Article Numbers. Read the mail gateway proto and its message-building tests for CC and attachments. Done means the settings, dispatch timing and retries, CSV mail, customer confirmations, Procurement states, and listed external-behavior tests all work together.

Written by the indexing model from the issue text.

Description

ready-for-agent

Blocked by: #78 (HTTP test harness), #84 (Bakery Article Number on Items)

Problem Statement

Every pickup day, someone at the Nachbarschaftshilfe has to read the Procurement off the "Beschaffung" page and pass it on to the Bakery by hand. The Bakery wants the order at a fixed time (14:00), in its own format (article number, quantity, unit price). On top of that, today the binding confirmations go out to customers the moment the Deadline passes, so Admins get no time to look over the day's orders and cancel the ones that should not be bought: a customer whose order is cancelled at 13:30 has already been told at 13:00 that it is binding.

Solution

A pickup day gets three points in its life: the Deadline ("Bestellschluss", as today), then the Review Window ("Prüfzeit"), then the Dispatch ("Bestellversand").

  • At the Deadline, customers can no longer order or cancel. Nothing is sent yet.
  • During the Review Window, Admins look over the day's orders and cancel whole orders where needed; the customer gets a cancellation mail.
  • At the Dispatch (Deadline + Review Window), semmelei sends the Procurement as a CSV attachment to the Bakery's mail address, with a copy (CC) to the Nachbarschaftshilfe, and sends the customers their binding confirmations. From then on the Procurement is final.

The Review Window is a single Admin setting (minutes, 0 allowed). The Bakery's address and the copy address are Admin settings too. There is no manual dispatch and no resend: it always happens automatically, on time.

User Stories

  1. As an Admin, I want to set the Review Window in minutes, so that the Bakery gets the order at the time it asked for (e.g. Deadline 13:00 + 60 minutes = 14:00).
  2. As an Admin, I want a Review Window of 0, so that the Dispatch can happen right at the Deadline if no review is needed.
  3. As an Admin, I want the Dispatch to move with the Deadline when I override the Deadline of a single pickup day, so that I never have to adjust two times.
  4. As an Admin, I want a change of the Review Window to apply to a day that is currently in its Review Window, so that shortening it sends that day's order right away.
  5. As an Admin, I want to enter the Bakery's mail address in the shop settings, so that the order reaches the right inbox without a deployment.
  6. As an Admin, I want to enter a copy address of the Nachbarschaftshilfe in the shop settings, so that we see exactly what went to the Bakery.
  7. As an Admin, I want the copy to be a CC on the same mail, so that the Bakery sees who reads along and "reply all" reaches both of us.
  8. As an Admin, I want no Dispatch to happen while no Bakery address is set, so that an order is never sent into the void.
  9. As Verkauf, I want the Procurement page to tell me when a day was not dispatched because no Bakery address is set, so that someone can order by hand.
  10. As an Admin, I want to cancel whole orders during the Review Window, so that orders that should not be bought (duplicates, jokes, unpaid) stay out of the Bakery order.
  11. As a customer whose order is cancelled during the Review Window, I want a cancellation mail and no confirmation, so that I am never told an order is binding and then cancelled.
  12. As a customer, I want my binding confirmation at the Dispatch, so that it reflects what the Nachbarschaftshilfe really buys for me.
  13. As a customer, I want to be unable to place or cancel orders after the Deadline, as today, so that the shop behaves as I'm used to.
  14. As the Bakery, I want the order as a CSV with the columns Artikel;Artikelnummer;Menge;Einzelpreis, so that I can import it.
  15. As the Bakery, I want one line per Bakery Article Number with the quantities summed, so that I never have to add up duplicate lines.
  16. As the Bakery, I want the names of all Items sharing an article number joined with " / " in the "Artikel" column, so that I can see what the line covers.
  17. As the Bakery, I want prices in German notation without a currency sign (2,50), so that my spreadsheet reads them as numbers.
  18. As the Bakery, I want the file in UTF-8 with BOM, so that umlauts show correctly in Excel.
  19. As the Bakery, I want the lines sorted by article number, so that I can check the order against my range.
  20. As the Bakery, I want the file named after the pickup date (bestellung-<YYYY-MM-DD>.csv), so that I can tell days apart.
  21. As the Bakery, I want a German mail with the subject Bestellung Nachbarschaftshilfe für <Wochentag, Datum> and a short body, so that I know at a glance what it is.
  22. As the Bakery, I want positions of Items without a Bakery Article Number left out entirely (not in the CSV, not in the mail), so that I only see what I deliver.
  23. As the Bakery, I want no mail on a day with no positions for me, so that I don't get empty orders.
  24. As an Admin, I want a day with no Bakery positions to still count as dispatched and its customers to still get their confirmations, so that coffee-only days work as usual.
  25. As an Admin, I want a called-off pickup day never to be dispatched, so that the Bakery gets nothing for a day that does not happen.
  26. As an Admin, I want the Dispatch to be retried on the next tick if handing the mail over fails, so that a NATS hiccup never loses the Bakery order.
  27. As an Admin, I want each pickup day dispatched at most once in normal operation, so that the Bakery does not bake twice.
  28. As an Admin, I want a Dispatch that was missed during downtime to happen as soon as the service is back, so that a restart at 13:58 does not lose the order.
  29. As an Admin, I want "Jetzt einfrieren" to keep working and only end ordering early, without moving the Dispatch, so that the Bakery still gets the order at its fixed time.
  30. As an Admin, I want to still be able to cancel orders after the Dispatch, so that a customer who calls off later is handled, and I want the UI to warn me that the sent CSV does not change.
  31. As Verkauf, I want the Procurement page to show the day's state ("Bestellung offen bis 13:00", "Prüfzeit bis 14:00", "An die Bäckerei versendet um 14:00", "Nicht versendet: keine Bäckerei-Adresse"), so that I know where the day stands.
  32. As an Admin, I want a warning in the logs when Items sharing one article number have different prices, so that I can fix the catalog (the CSV uses the first Item's price).
  33. As a developer of another service, I want the mail gateway to accept CC recipients and attachments generically, so that other services can send files too.

Implementation Decisions

  • Shop settings: three new fields: the Review Window (whole minutes, non-negative, default 0 so behaviour is unchanged until set), the Bakery mail address (optional) and the copy address (optional). The admin settings schema and form carry them; addresses are validated as mail addresses. rorm migration adds them.
  • Dispatch time is computed, never stored: Dispatch due = the day's Deadline (rule or override) + the current Review Window. The schedule module (single source of truth for times) gains this computation next to the Deadline, in Europe/Berlin.
  • Pickup day: gains a nullable "dispatched at" timestamp, the only Dispatch state stored. The existing "confirmations sent at" moves from the Deadline to the Dispatch.
  • Freezing vs. Dispatch: the existing deadline job keeps freezing days at the Deadline (and "Jetzt einfrieren" keeps freezing early), but freezing no longer sends confirmations. In the same per-minute tick, a second step picks days that are due for Dispatch: Dispatch due has passed, not called off, not yet dispatched.
  • Dispatch step, per day:
    1. If no Bakery address is set: skip (do not mark), so the day dispatches as soon as an address is set. The Procurement response reports the reason.
    2. Build the Bakery order from all non-cancelled orders' positions: group by the Item's live Bakery Article Number (as decided in #84: read through the position's Item link, not snapshotted); positions of deleted Items or Items without a number are dropped. Quantity summed; "Artikel" = distinct name snapshots joined with " / "; "Einzelpreis" = the customer price snapshot of the first position (the Nachbarschaftshilfe sells at the Bakery's price); differing prices under one number log a warning.
    3. If there are lines: publish the Bakery mail and wait for the JetStream ack. Only after the ack, mark the day dispatched. A failed publish leaves it unmarked and the next tick retries (at least once; a duplicate order is the lesser failure than a lost one). This is the opposite of the customer mails, which mark before sending; the reason belongs in a code comment.
    4. If there are no lines: mark dispatched without a Bakery mail.
    5. Send the customer confirmations with the existing at-most-once logic.
  • CSV rendering: header Artikel;Artikelnummer;Menge;Einzelpreis, ; separator, quoting of fields per RFC 4180 where needed, German decimal comma, no currency sign, UTF-8 with BOM, CRLF line endings, sorted by article number, filename bestellung-<YYYY-MM-DD>.csv, content type text/csv; charset=utf-8.
  • Bakery mail: German regardless of any order's language, subject Bestellung Nachbarschaftshilfe für <Wochentag, Datum>, a one-sentence body with the pickup date; the copy address as CC.
  • Mail sink: the dispatch and confirmation code sends through a narrow mail-sending interface with the NATS publisher as the production implementation, so tests can record mails and simulate failed acks.
  • Mail gateway proto (mail.v1.SendEmail): new repeated string cc and repeated Attachment attachments with Attachment { string filename; string content_type; bytes content; }. Both optional, so existing producers are unaffected. The gateway builds a multipart message when attachments are present; it still only transports and renders nothing.
  • Procurement API: the summary response gains the day's Dispatch due time, "dispatched at", and a not-dispatched reason (none / no Bakery address). The page renders the four states from user story 31.
  • Order cancellation after Dispatch: allowed as today; the frontend shows a warning on days already dispatched.
  • Glossary: Deadline, Review Window and Dispatch are in the semmelei GLOSSARY.md; Procurement is "final once Dispatched".
  • Frontend: new i18n keys per CODING_STANDARDS.md; API client regenerated.

Testing Decisions

  • Good tests assert external behaviour only: HTTP requests and responses, and the mails that leave the service (recipients, subject, attachment bytes). Not database rows or internal functions.
  • Main seam: the HTTP harness from #78, plus the tick driven in-process with an injected "now" and the recording mail sink. Cases:
    • set Review Window and addresses through the admin settings API; invalid addresses and negative minutes rejected
    • no mail at Deadline; Bakery mail and confirmations at Deadline + Review Window, not one minute before
    • Deadline override of a single day moves the Dispatch; changing the Review Window during the window takes effect on the next tick
    • Review Window 0 dispatches at the Deadline
    • order cancelled during the window: missing from the CSV, customer gets a cancellation and no confirmation
    • CSV content: header, one line per article number, joined names, summed quantity, 2,50, BOM, sort order; Items without number and deleted Items left out
    • no Bakery positions: no Bakery mail, day dispatched, confirmations sent
    • called-off day: nothing sent
    • no Bakery address: nothing sent to the Bakery, Procurement reports the reason; setting the address dispatches on the next tick
    • failed ack: not marked, retried next tick; after success no second Bakery mail
    • "Jetzt einfrieren" does not trigger a Dispatch
    • CC carries the copy address
    • the Procurement response reports the right state at each stage
  • Second seam: mail gateway message building, unit tested without SMTP: CC header, multipart with attachment (filename, content type, bytes), and a plain message when there are none.
  • Prior art: bnv-manager-v2's HTTP tests and harness; the unit tests in semmelei's schedule module for time handling.

Out of Scope

  • Manual "send now" or "resend" buttons.
  • Editing quantities or removing single positions of an order during the Review Window (only whole orders are cancelled).
  • A separate purchase price per Item; the customer price is the Einzelpreis.
  • Correction mails to the Bakery after cancellations past the Dispatch.
  • Changes to the Procurement page's grouping (it stays per Item name as the packing list; only the CSV groups per article number).
  • Any other Bakery channel than mail (portal, API, fax).

Further Notes

  • The mail gateway change is generic and could ship first on its own.
  • With the default Review Window of 0 the Dispatch happens at the Deadline, so moving the confirmations to the Dispatch changes nothing visible until an Admin sets a Review Window.
Dominant language
TypeScript
Stars
0
Forks
1
Avg merge
10h 56m
Merged PRs (30d)
34

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from myOmikron/code

All issues in myOmikron/code

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.