Repository navigation
feat(api): add receipts and receipt items tables with prices - #54
Merged
Merged
Conversation
lauriechammah
requested review from
lauriechammah
and removed request for
jspngl
November 6, 2026 16:19
anis022
requested changes
Nov 7, 2026
anis022
left a comment
Member
There was a problem hiding this comment.
I left a few inline comments to address before merging, mostly about recalculating receipts reliably and data integrity.
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.
Closes #16
Overview
The goal of this ticket is to design the tables for receipts, including the price information needed for invoice calculations, linked to donors and food items.
receipts:
id(uuid, generated),donor_id,total_amount(numeric(12,2), cannot be negative),receipt_date(date, defaults to today in America/Montreal, used for the tax year),created_at(timestamptz, defaultnow(), stored in UTC).receipt_items: one row per food item on a receipt.
receipt_id,food_item_id,donor_id,quantity(numeric(12,2), greater than 0),unit(varchar(20), not empty),unit_price(numeric(12,4), greater than 0),quality_percent(numeric(5,2), between 0 and 100). The primary key is (receipt_id,food_item_id).Links:
ON DELETE RESTRICT).receipt_itemslinks toreceiptsthrough (receipt_id,donor_id) (CASCADE) and tofood_itemsthrough (food_item_id,donor_id) (RESTRICT). A receipt can therefore only list food items from its own donor. Both parent tables get a unique constraint on (id,donor_id).food_item_idmeans a food item can only be on one receipt, so one donation cannot get two tax receipts.Invoice calculations:
unit_pricexquality_percent/ 100, rounded to the cent, half away from zero. The receipt total is the sum of the rounded lines.numeric, so there is no floating-point rounding.Changes:
ReceiptandReceiptItementities insrc/MoissonMontreal.Core/Entities/src/MoissonMontreal.Api/Data/Configurations/(precision, constraints, composite relationships, UTC conversion ofcreated_at)MoissonDbContext:ReceiptsandReceiptItemsDbSetsCreateReceiptsinsrc/MoissonMontreal.Api/Data/Migrations/(generated bydotnet ef)docs/database/sql/0003_create_receipts.sql, plus0002_create_donors_and_food_items.sql, which was missing for [Issue]: Database schema: donors and food items #15docs/database/receipts.dbml,docs/database/images/receipts.png,docs/database/receipts.mdtests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.csDonorandFoodItemare not modified. The migration adds the unique constraintak_food_items_id_donor_idtofood_items.Review feedback addressed
receipt_items, so a line can be recalculated after the food item is edited.receipts.md, with a test for the $0.225 case.0002and0003. The script command inreceipts.mdnow includes the step to remove it.unit_pricenow has 4 decimal places.food_item_id. Issue [Issue]: Receipt status state machine #31 will need to relax it for cancel-and-replace receipts. This is noted inreceipts.md.donor_id, as suggested.receipt_datecolumn holds the Montreal date for the tax year.created_atis converted to UTC on write, soDateTimeOffset.Nowno longer throws.Testing
Unit and integration tests:
dotnet test(Docker must be running). All 59 tests pass, including 30 inReceiptsMigrationTests. They run against PostgreSQL 17 and check that:receipt_dateis the Montreal date, and acreated_atwith a -05:00 offset is saved and read back as the same moment in UTCfk_receipts_donors_donor_idfk_receipt_items_receipts_receipt_id_donor_idfk_receipt_items_food_items_food_item_id_donor_idpk_receipt_itemsix_receipt_items_food_item_idck_receipts_total_amount_not_negativeck_receipt_items_quantity_positiveck_receipt_items_unit_not_emptyck_receipt_items_unit_price_positiveck_receipt_items_quality_percent_rangeManual testing: the three SQL scripts were run in order with
psql -v ON_ERROR_STOP=1on an empty PostgreSQL 17 database. Then the following were checked by hand:To try it locally:
docker compose up -ddotnet ef database update --project src/MoissonMontreal.Apidocker exec -it moisson-db psql -U moisson -d moisson_dev, then\d receiptsand\d receipt_itemsScreenshots / Screencasts
No frontend changes. Diagram of the schema from dbdiagram.io:
Checklist
Notes
Data/Migrations/are generated bydotnet ef, so the entities and configurations are the files worth reviewing.total_amountequals the sum of the rounded lines. That is left to the donation value calculation ([Issue]: Donation value calculation #30).donor_idis part of both composite keys, EF does not allow changing the donor of a saved receipt or food item.