From afcda175be20a39122e46643e163be1d3034a5cd Mon Sep 17 00:00:00 2001 From: MarcAntoineGmd Date: Thu, 5 Nov 2026 23:39:12 -0500 Subject: [PATCH 1/2] feat(api): add receipts and receipt items tables with prices --- docs/database/images/receipts.png | Bin 0 -> 58588 bytes docs/database/receipts.dbml | 94 ++++ docs/database/receipts.md | 81 ++++ .../sql/0002_create_donors_and_food_items.sql | 36 ++ docs/database/sql/0003_create_receipts.sql | 32 ++ .../Configurations/ReceiptConfiguration.cs | 31 ++ .../ReceiptItemConfiguration.cs | 40 ++ .../20261106021712_CreateReceipts.Designer.cs | 400 ++++++++++++++++++ .../20261106021712_CreateReceipts.cs | 84 ++++ .../MoissonDbContextModelSnapshot.cs | 107 +++++ .../Data/MoissonDbContext.cs | 10 + src/MoissonMontreal.Core/Entities/Receipt.cs | 37 ++ .../Entities/ReceiptItem.cs | 38 ++ .../Api/ReceiptsMigrationTests.cs | 328 ++++++++++++++ 14 files changed, 1318 insertions(+) create mode 100644 docs/database/images/receipts.png create mode 100644 docs/database/receipts.dbml create mode 100644 docs/database/receipts.md create mode 100644 docs/database/sql/0002_create_donors_and_food_items.sql create mode 100644 docs/database/sql/0003_create_receipts.sql create mode 100644 src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs create mode 100644 src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs create mode 100644 src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs create mode 100644 src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs create mode 100644 src/MoissonMontreal.Core/Entities/Receipt.cs create mode 100644 src/MoissonMontreal.Core/Entities/ReceiptItem.cs create mode 100644 tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs diff --git a/docs/database/images/receipts.png b/docs/database/images/receipts.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a20ca4ad06b2adb8e086bf195f2dc39bab38f2 GIT binary patch literal 58588 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 Puc@Y@shqU`^p*bxP*XV~ literal 0 HcmV?d00001 diff --git a/docs/database/receipts.dbml b/docs/database/receipts.dbml new file mode 100644 index 0000000..558777c --- /dev/null +++ b/docs/database/receipts.dbml @@ -0,0 +1,94 @@ +// Receipts and their prices +// Paste this file into https://dbdiagram.io to render the diagram. +// Matches the migration 20261106021712_CreateReceipts. +// donors and food_items come from the migration 20261105163138_CreateDonorsAndFoodItems +// and are shown here because receipts link to them. + +Table donors { + id uuid [not null, default: `gen_random_uuid()`] + name varchar(255) [not null] + address text [null] + postal_code varchar(20) [null] + contact_person varchar(255) [null] + created_at timestamptz [not null, default: `now()`] + + indexes { + id [pk, name: 'pk_donors'] + } + + checks { + `length(name) > 0` [name: 'ck_donors_name_not_empty'] + } + + Note: 'Organizations or people who give food' +} + +Table food_items { + id uuid [not null, default: `gen_random_uuid()`] + donor_id uuid [not null] + name varchar(255) [not null] + quantity numeric(12,2) [not null] + unit varchar(20) [not null] + donation_date date [not null] + quality varchar(1) [not null, note: 'A, B or C'] + created_at timestamptz [not null, default: `now()`] + + indexes { + id [pk, name: 'pk_food_items'] + donor_id [name: 'ix_food_items_donor_id'] + } + + checks { + `length(name) > 0` [name: 'ck_food_items_name_not_empty'] + `quality IN ('A', 'B', 'C')` [name: 'ck_food_items_quality'] + `quantity > 0` [name: 'ck_food_items_quantity_positive'] + `length(unit) > 0` [name: 'ck_food_items_unit_not_empty'] + } + + Note: 'Food donated by a donor, with its date and quality' +} + +Table receipts { + id uuid [not null, default: `gen_random_uuid()`] + donor_id uuid [not null] + total_amount numeric(12,2) [not null, note: 'Sum of the line values, in dollars'] + created_at timestamptz [not null, default: `now()`] + + indexes { + id [pk, name: 'pk_receipts'] + donor_id [name: 'ix_receipts_donor_id'] + } + + checks { + `total_amount >= 0` [name: 'ck_receipts_total_amount_not_negative'] + } + + Note: 'Tax receipts given to donors' +} + +Table receipt_items { + receipt_id uuid [not null] + food_item_id uuid [not null] + unit_price numeric(12,2) [not null, note: 'Price of one unit when the receipt was made'] + quality_percent numeric(5,2) [not null, note: 'A = 100, B = 50 to 75, C = 0'] + + indexes { + (receipt_id, food_item_id) [pk, name: 'pk_receipt_items'] + food_item_id [name: 'ix_receipt_items_food_item_id'] + } + + checks { + `unit_price > 0` [name: 'ck_receipt_items_unit_price_positive'] + `quality_percent BETWEEN 0 AND 100` [name: 'ck_receipt_items_quality_percent_range'] + } + + Note: 'Food items on a receipt. Line value = quantity x unit_price x quality_percent / 100' +} + +Ref fk_food_items_donors_donor_id: food_items.donor_id > donors.id [delete: restrict] + +Ref fk_receipts_donors_donor_id: receipts.donor_id > donors.id [delete: restrict] + +Ref fk_receipt_items_receipts_receipt_id: receipt_items.receipt_id > receipts.id [delete: cascade] + +Ref fk_receipt_items_food_items_food_item_id: receipt_items.food_item_id > food_items.id [delete: restrict] diff --git a/docs/database/receipts.md b/docs/database/receipts.md new file mode 100644 index 0000000..e63eabf --- /dev/null +++ b/docs/database/receipts.md @@ -0,0 +1,81 @@ +# Receipts and their prices + +These two tables hold the tax receipts given to donors and the value of each food item on them. A receipt belongs to one donor and lists the food items that donor gave. For each item, the receipt keeps the price and the quality percentage used to value it, so the amount can always be checked and recalculated. + +## Diagram + +![Receipts diagram](images/receipts.png) + +The diagram is generated from [receipts.dbml](receipts.dbml). To update it, paste the content of that file into [dbdiagram.io](https://dbdiagram.io). + +The donors and food_items tables come from an earlier step. They are shown in the diagram because receipts link to them. + +## Tables + +### receipts + +One row per tax receipt. + +| Column | Meaning | +| --- | --- | +| id | Code that identifies the receipt, assigned automatically | +| donor_id | The donor who receives the receipt (see the donors table) | +| total_amount | Total value of the receipt, in dollars and cents. It is the sum of the values of its food items. | +| created_at | Date and time the receipt was created | + +### receipt_items + +One row per food item on a receipt. + +| Column | Meaning | +| --- | --- | +| receipt_id | The receipt this line belongs to | +| food_item_id | The food item valued on this line (see the food_items table) | +| unit_price | Price of one unit of the item when the receipt was made, in dollars and cents. For example, the price of 1 kg of bread. | +| quality_percent | Share of the price kept for the item's quality grade: 100 for grade A, between 50 and 75 for grade B, 0 for grade C | + +## How the value is calculated + +The value of each food item on a receipt is: + +quantity of the item × unit price × quality percentage + +The total of the receipt is the sum of these values. For example, a bakery gives 12.50 kg of bread of grade B and 3 cases of apples of grade A: + +| Item | Quantity | Unit price | Quality | Value | +| --- | --- | --- | --- | --- | +| Bread | 12.50 kg | $4.00 | 75 % | $37.50 | +| Apples | 3 cases | $2.49 | 100 % | $7.47 | +| Total | | | | $44.97 | + +The quantity comes from the food item. The unit price and the quality percentage are saved on the receipt itself. If the price grid changes later, receipts that already exist keep the values they were made with. + +All amounts are stored as exact decimal numbers, so no cent is lost or rounded along the way. + +## How the tables are linked + +1. Each receipt belongs to exactly one donor. A donor can have many receipts. +2. Each receipt lists its food items through receipt_items. A food item can appear on more than one receipt, for example when a receipt is replaced later. +3. The same food item cannot appear twice on the same receipt. + +## Rules enforced by the database + +1. A receipt must belong to a donor that exists. +2. A donor cannot be deleted while they still have a receipt. +3. A food item cannot be deleted while it is on a receipt. +4. When a receipt is deleted, its lines are deleted with it. The food items themselves are kept. +5. The total of a receipt cannot be negative. It can be $0, for example when every item is grade C. +6. A unit price must be greater than $0. +7. A quality percentage must be between 0 and 100. + +The app, not the database, makes sure that the food items on a receipt were given by the receipt's donor, and that the total matches the sum of the lines. + +## Migration + +| Item | Location | +| --- | --- | +| Entity Framework migration | [20261106021712_CreateReceipts](../../src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs) | +| Equivalent SQL script | [0003_create_receipts.sql](sql/0003_create_receipts.sql) | +| Table settings | [ReceiptConfiguration.cs](../../src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs) and [ReceiptItemConfiguration.cs](../../src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs) | + +The SQL script is generated from the migration with `dotnet ef migrations script CreateDonorsAndFoodItems CreateReceipts --project src/MoissonMontreal.Api --output docs/database/sql/0003_create_receipts.sql`. It must be run after [0001_create_users_and_roles.sql](sql/0001_create_users_and_roles.sql) and [0002_create_donors_and_food_items.sql](sql/0002_create_donors_and_food_items.sql). It records itself in the migration history, so Entity Framework will not apply it a second time. diff --git a/docs/database/sql/0002_create_donors_and_food_items.sql b/docs/database/sql/0002_create_donors_and_food_items.sql new file mode 100644 index 0000000..e5ea88a --- /dev/null +++ b/docs/database/sql/0002_create_donors_and_food_items.sql @@ -0,0 +1,36 @@ +START TRANSACTION; +CREATE TABLE donors ( + id uuid NOT NULL DEFAULT (gen_random_uuid()), + name character varying(255) NOT NULL, + address text, + postal_code character varying(20), + contact_person character varying(255), + created_at timestamp with time zone NOT NULL DEFAULT (now()), + CONSTRAINT pk_donors PRIMARY KEY (id), + CONSTRAINT ck_donors_name_not_empty CHECK (length(name) > 0) +); + +CREATE TABLE food_items ( + id uuid NOT NULL DEFAULT (gen_random_uuid()), + donor_id uuid NOT NULL, + name character varying(255) NOT NULL, + quantity numeric(12,2) NOT NULL, + unit character varying(20) NOT NULL, + donation_date date NOT NULL, + quality character varying(1) NOT NULL, + created_at timestamp with time zone NOT NULL DEFAULT (now()), + CONSTRAINT pk_food_items PRIMARY KEY (id), + CONSTRAINT ck_food_items_name_not_empty CHECK (length(name) > 0), + CONSTRAINT ck_food_items_quality CHECK (quality IN ('A', 'B', 'C')), + CONSTRAINT ck_food_items_quantity_positive CHECK (quantity > 0), + CONSTRAINT ck_food_items_unit_not_empty CHECK (length(unit) > 0), + CONSTRAINT fk_food_items_donors_donor_id FOREIGN KEY (donor_id) REFERENCES donors (id) ON DELETE RESTRICT +); + +CREATE INDEX ix_food_items_donor_id ON food_items (donor_id); + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20261105163138_CreateDonorsAndFoodItems', '10.0.13'); + +COMMIT; + diff --git a/docs/database/sql/0003_create_receipts.sql b/docs/database/sql/0003_create_receipts.sql new file mode 100644 index 0000000..1862d1c --- /dev/null +++ b/docs/database/sql/0003_create_receipts.sql @@ -0,0 +1,32 @@ +START TRANSACTION; +CREATE TABLE receipts ( + id uuid NOT NULL DEFAULT (gen_random_uuid()), + donor_id uuid NOT NULL, + total_amount numeric(12,2) NOT NULL, + created_at timestamp with time zone NOT NULL DEFAULT (now()), + CONSTRAINT pk_receipts PRIMARY KEY (id), + CONSTRAINT ck_receipts_total_amount_not_negative CHECK (total_amount >= 0), + CONSTRAINT fk_receipts_donors_donor_id FOREIGN KEY (donor_id) REFERENCES donors (id) ON DELETE RESTRICT +); + +CREATE TABLE receipt_items ( + receipt_id uuid NOT NULL, + food_item_id uuid NOT NULL, + unit_price numeric(12,2) NOT NULL, + quality_percent numeric(5,2) NOT NULL, + CONSTRAINT pk_receipt_items PRIMARY KEY (receipt_id, food_item_id), + CONSTRAINT ck_receipt_items_quality_percent_range CHECK (quality_percent BETWEEN 0 AND 100), + CONSTRAINT ck_receipt_items_unit_price_positive CHECK (unit_price > 0), + CONSTRAINT fk_receipt_items_food_items_food_item_id FOREIGN KEY (food_item_id) REFERENCES food_items (id) ON DELETE RESTRICT, + CONSTRAINT fk_receipt_items_receipts_receipt_id FOREIGN KEY (receipt_id) REFERENCES receipts (id) ON DELETE CASCADE +); + +CREATE INDEX ix_receipt_items_food_item_id ON receipt_items (food_item_id); + +CREATE INDEX ix_receipts_donor_id ON receipts (donor_id); + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20261106021712_CreateReceipts', '10.0.13'); + +COMMIT; + diff --git a/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs b/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs new file mode 100644 index 0000000..7a4b11b --- /dev/null +++ b/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using MoissonMontreal.Core.Entities; + +namespace MoissonMontreal.Api.Data.Configurations; + +/// +/// Database rules of the receipts table and its link to the donors table. +/// +public class ReceiptConfiguration : IEntityTypeConfiguration +{ + /// + /// Configures the columns, constraint and donor relationship of the receipts table. + /// + /// Builder used to configure the receipt entity. + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(t => t.HasCheckConstraint("ck_receipts_total_amount_not_negative", "total_amount >= 0")); + + builder.Property(r => r.Id).HasDefaultValueSql("gen_random_uuid()"); + + builder.Property(r => r.TotalAmount).HasPrecision(12, 2); + + builder.Property(r => r.CreatedAt).HasDefaultValueSql("now()"); + + builder.HasOne(r => r.Donor) + .WithMany() + .HasForeignKey(r => r.DonorId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs b/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs new file mode 100644 index 0000000..b8850b4 --- /dev/null +++ b/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using MoissonMontreal.Core.Entities; + +namespace MoissonMontreal.Api.Data.Configurations; + +/// +/// Database rules of the receipt_items table and its links to the receipts and food_items tables. +/// +public class ReceiptItemConfiguration : IEntityTypeConfiguration +{ + /// + /// Configures the key, price columns, constraints and relationships of the receipt_items table. + /// + /// Builder used to configure the receipt item entity. + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(t => + { + t.HasCheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); + t.HasCheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + }); + + builder.HasKey(i => new { i.ReceiptId, i.FoodItemId }); + + builder.Property(i => i.UnitPrice).HasPrecision(12, 2); + + builder.Property(i => i.QualityPercent).HasPrecision(5, 2); + + builder.HasOne(i => i.Receipt) + .WithMany(r => r.Items) + .HasForeignKey(i => i.ReceiptId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(i => i.FoodItem) + .WithMany() + .HasForeignKey(i => i.FoodItemId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs b/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs new file mode 100644 index 0000000..30bcfcc --- /dev/null +++ b/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs @@ -0,0 +1,400 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MoissonMontreal.Api.Data; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MoissonMontreal.Api.Data.Migrations +{ + [DbContext(typeof(MoissonDbContext))] + [Migration("20261106021712_CreateReceipts")] + partial class CreateReceipts + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Donor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Address") + .HasColumnType("text") + .HasColumnName("address"); + + b.Property("ContactPerson") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("contact_person"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("name"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("postal_code"); + + b.HasKey("Id") + .HasName("pk_donors"); + + b.ToTable("donors", null, t => + { + t.HasCheckConstraint("ck_donors_name_not_empty", "length(name) > 0"); + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.FoodItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("DonationDate") + .HasColumnType("date") + .HasColumnName("donation_date"); + + b.Property("DonorId") + .HasColumnType("uuid") + .HasColumnName("donor_id"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("name"); + + b.Property("Quality") + .IsRequired() + .HasMaxLength(1) + .HasColumnType("character varying(1)") + .HasColumnName("quality"); + + b.Property("Quantity") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("quantity"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("unit"); + + b.HasKey("Id") + .HasName("pk_food_items"); + + b.HasIndex("DonorId") + .HasDatabaseName("ix_food_items_donor_id"); + + b.ToTable("food_items", null, t => + { + t.HasCheckConstraint("ck_food_items_name_not_empty", "length(name) > 0"); + + t.HasCheckConstraint("ck_food_items_quality", "quality IN ('A', 'B', 'C')"); + + t.HasCheckConstraint("ck_food_items_quantity_positive", "quantity > 0"); + + t.HasCheckConstraint("ck_food_items_unit_not_empty", "length(unit) > 0"); + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Receipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("DonorId") + .HasColumnType("uuid") + .HasColumnName("donor_id"); + + b.Property("TotalAmount") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("total_amount"); + + b.HasKey("Id") + .HasName("pk_receipts"); + + b.HasIndex("DonorId") + .HasDatabaseName("ix_receipts_donor_id"); + + b.ToTable("receipts", null, t => + { + t.HasCheckConstraint("ck_receipts_total_amount_not_negative", "total_amount >= 0"); + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.ReceiptItem", b => + { + b.Property("ReceiptId") + .HasColumnType("uuid") + .HasColumnName("receipt_id"); + + b.Property("FoodItemId") + .HasColumnType("uuid") + .HasColumnName("food_item_id"); + + b.Property("QualityPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)") + .HasColumnName("quality_percent"); + + b.Property("UnitPrice") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("unit_price"); + + b.HasKey("ReceiptId", "FoodItemId") + .HasName("pk_receipt_items"); + + b.HasIndex("FoodItemId") + .HasDatabaseName("ix_receipt_items_food_item_id"); + + b.ToTable("receipt_items", null, t => + { + t.HasCheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + + t.HasCheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Role", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("code"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("pk_roles"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("ix_roles_code"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_roles_name"); + + b.ToTable("roles", (string)null); + + b.HasData( + new + { + Id = 1, + Code = "Supply", + Name = "Supply" + }, + new + { + Id = 2, + Code = "SupplyDirector", + Name = "Supply Director" + }, + new + { + Id = 3, + Code = "Finance", + Name = "Finance" + }, + new + { + Id = 4, + Code = "Admin", + Name = "Admin" + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("display_name"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("email"); + + b.Property("EntraObjectId") + .HasColumnType("uuid") + .HasColumnName("entra_object_id"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("is_active"); + + b.Property("RoleId") + .HasColumnType("integer") + .HasColumnName("role_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("ix_users_email"); + + b.HasIndex("EntraObjectId") + .IsUnique() + .HasDatabaseName("ix_users_entra_object_id"); + + b.HasIndex("RoleId") + .HasDatabaseName("ix_users_role_id"); + + b.ToTable("users", null, t => + { + t.HasCheckConstraint("ck_users_email_lowercase", "email = lower(email)"); + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.FoodItem", b => + { + b.HasOne("MoissonMontreal.Core.Entities.Donor", "Donor") + .WithMany("FoodItems") + .HasForeignKey("DonorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_food_items_donors_donor_id"); + + b.Navigation("Donor"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Receipt", b => + { + b.HasOne("MoissonMontreal.Core.Entities.Donor", "Donor") + .WithMany() + .HasForeignKey("DonorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_receipts_donors_donor_id"); + + b.Navigation("Donor"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.ReceiptItem", b => + { + b.HasOne("MoissonMontreal.Core.Entities.FoodItem", "FoodItem") + .WithMany() + .HasForeignKey("FoodItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_receipt_items_food_items_food_item_id"); + + b.HasOne("MoissonMontreal.Core.Entities.Receipt", "Receipt") + .WithMany("Items") + .HasForeignKey("ReceiptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_receipt_items_receipts_receipt_id"); + + b.Navigation("FoodItem"); + + b.Navigation("Receipt"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.User", b => + { + b.HasOne("MoissonMontreal.Core.Entities.Role", "Role") + .WithMany("Users") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_users_roles_role_id"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Donor", b => + { + b.Navigation("FoodItems"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Receipt", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.Role", b => + { + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs b/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs new file mode 100644 index 0000000..0f692a9 --- /dev/null +++ b/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MoissonMontreal.Api.Data.Migrations +{ + /// + public partial class CreateReceipts : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "receipts", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + donor_id = table.Column(type: "uuid", nullable: false), + total_amount = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_receipts", x => x.id); + table.CheckConstraint("ck_receipts_total_amount_not_negative", "total_amount >= 0"); + table.ForeignKey( + name: "fk_receipts_donors_donor_id", + column: x => x.donor_id, + principalTable: "donors", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "receipt_items", + columns: table => new + { + receipt_id = table.Column(type: "uuid", nullable: false), + food_item_id = table.Column(type: "uuid", nullable: false), + unit_price = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false), + quality_percent = table.Column(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_receipt_items", x => new { x.receipt_id, x.food_item_id }); + table.CheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + table.CheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); + table.ForeignKey( + name: "fk_receipt_items_food_items_food_item_id", + column: x => x.food_item_id, + principalTable: "food_items", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_receipt_items_receipts_receipt_id", + column: x => x.receipt_id, + principalTable: "receipts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_receipt_items_food_item_id", + table: "receipt_items", + column: "food_item_id"); + + migrationBuilder.CreateIndex( + name: "ix_receipts_donor_id", + table: "receipts", + column: "donor_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "receipt_items"); + + migrationBuilder.DropTable( + name: "receipts"); + } + } +} diff --git a/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs b/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs index feed2d4..97bc2a2 100644 --- a/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs +++ b/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs @@ -128,6 +128,75 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("MoissonMontreal.Core.Entities.Receipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("DonorId") + .HasColumnType("uuid") + .HasColumnName("donor_id"); + + b.Property("TotalAmount") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("total_amount"); + + b.HasKey("Id") + .HasName("pk_receipts"); + + b.HasIndex("DonorId") + .HasDatabaseName("ix_receipts_donor_id"); + + b.ToTable("receipts", null, t => + { + t.HasCheckConstraint("ck_receipts_total_amount_not_negative", "total_amount >= 0"); + }); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.ReceiptItem", b => + { + b.Property("ReceiptId") + .HasColumnType("uuid") + .HasColumnName("receipt_id"); + + b.Property("FoodItemId") + .HasColumnType("uuid") + .HasColumnName("food_item_id"); + + b.Property("QualityPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)") + .HasColumnName("quality_percent"); + + b.Property("UnitPrice") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("unit_price"); + + b.HasKey("ReceiptId", "FoodItemId") + .HasName("pk_receipt_items"); + + b.HasIndex("FoodItemId") + .HasDatabaseName("ix_receipt_items_food_item_id"); + + b.ToTable("receipt_items", null, t => + { + t.HasCheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + + t.HasCheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); + }); + }); + modelBuilder.Entity("MoissonMontreal.Core.Entities.Role", b => { b.Property("Id") @@ -263,6 +332,39 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Donor"); }); + modelBuilder.Entity("MoissonMontreal.Core.Entities.Receipt", b => + { + b.HasOne("MoissonMontreal.Core.Entities.Donor", "Donor") + .WithMany() + .HasForeignKey("DonorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_receipts_donors_donor_id"); + + b.Navigation("Donor"); + }); + + modelBuilder.Entity("MoissonMontreal.Core.Entities.ReceiptItem", b => + { + b.HasOne("MoissonMontreal.Core.Entities.FoodItem", "FoodItem") + .WithMany() + .HasForeignKey("FoodItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_receipt_items_food_items_food_item_id"); + + b.HasOne("MoissonMontreal.Core.Entities.Receipt", "Receipt") + .WithMany("Items") + .HasForeignKey("ReceiptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_receipt_items_receipts_receipt_id"); + + b.Navigation("FoodItem"); + + b.Navigation("Receipt"); + }); + modelBuilder.Entity("MoissonMontreal.Core.Entities.User", b => { b.HasOne("MoissonMontreal.Core.Entities.Role", "Role") @@ -280,6 +382,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("FoodItems"); }); + modelBuilder.Entity("MoissonMontreal.Core.Entities.Receipt", b => + { + b.Navigation("Items"); + }); + modelBuilder.Entity("MoissonMontreal.Core.Entities.Role", b => { b.Navigation("Users"); diff --git a/src/MoissonMontreal.Api/Data/MoissonDbContext.cs b/src/MoissonMontreal.Api/Data/MoissonDbContext.cs index 594cd02..0c53bd8 100644 --- a/src/MoissonMontreal.Api/Data/MoissonDbContext.cs +++ b/src/MoissonMontreal.Api/Data/MoissonDbContext.cs @@ -29,6 +29,16 @@ public class MoissonDbContext(DbContextOptions options) : DbCo /// public DbSet FoodItems => Set(); + /// + /// Tax receipts given to donors. + /// + public DbSet Receipts => Set(); + + /// + /// Food items on each receipt, with the price used to value them. + /// + public DbSet ReceiptItems => Set(); + /// /// Names every table, column, key and index in snake_case. /// diff --git a/src/MoissonMontreal.Core/Entities/Receipt.cs b/src/MoissonMontreal.Core/Entities/Receipt.cs new file mode 100644 index 0000000..05cfa49 --- /dev/null +++ b/src/MoissonMontreal.Core/Entities/Receipt.cs @@ -0,0 +1,37 @@ +namespace MoissonMontreal.Core.Entities; + +/// +/// Tax receipt given to a donor for the food items they donated. +/// +public class Receipt +{ + /// + /// Identifier of the receipt, assigned by the database. + /// + public Guid Id { get; set; } + + /// + /// Identifier of the donor who receives this receipt. + /// + public Guid DonorId { get; set; } + + /// + /// Donor who receives this receipt. + /// + public Donor Donor { get; set; } = null!; + + /// + /// Total value of the receipt in dollars, with two decimal places. Cannot be negative. + /// + public decimal TotalAmount { get; set; } + + /// + /// Date and time the receipt was created, set by the database. + /// + public DateTimeOffset CreatedAt { get; set; } + + /// + /// Food items on this receipt, with the price used for each one. + /// + public ICollection Items { get; set; } = []; +} \ No newline at end of file diff --git a/src/MoissonMontreal.Core/Entities/ReceiptItem.cs b/src/MoissonMontreal.Core/Entities/ReceiptItem.cs new file mode 100644 index 0000000..e1ba30f --- /dev/null +++ b/src/MoissonMontreal.Core/Entities/ReceiptItem.cs @@ -0,0 +1,38 @@ +namespace MoissonMontreal.Core.Entities; + +/// +/// One food item on a receipt, with the price and quality percentage used to value it. +/// The value of the line is the item's quantity times the unit price times the quality percentage. +/// +public class ReceiptItem +{ + /// + /// Identifier of the receipt this line belongs to. + /// + public Guid ReceiptId { get; set; } + + /// + /// Receipt this line belongs to. + /// + public Receipt Receipt { get; set; } = null!; + + /// + /// Identifier of the food item valued on this line. + /// + public Guid FoodItemId { get; set; } + + /// + /// Food item valued on this line. + /// + public FoodItem FoodItem { get; set; } = null!; + + /// + /// Price of one unit of the food item when the receipt was made, in dollars. Must be greater than zero. + /// + public decimal UnitPrice { get; set; } + + /// + /// Percentage of the price applied for the item's quality grade, from 0 to 100. + /// + public decimal QualityPercent { get; set; } +} \ No newline at end of file diff --git a/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs b/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs new file mode 100644 index 0000000..abec5df --- /dev/null +++ b/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs @@ -0,0 +1,328 @@ +using System.Data.Common; +using Microsoft.EntityFrameworkCore; +using MoissonMontreal.Api.Data; +using MoissonMontreal.Core.Entities; +using MoissonMontreal.Tests.Fixtures; + +namespace MoissonMontreal.Tests.Api; + +[Trait("Category", "Integration")] +public class ReceiptsMigrationTests(PostgresFixture postgres) : IClassFixture +{ + [Fact] + public async Task Migrate_CreatesReceiptsAndReceiptItems_OnEmptyDatabase() + { + await using var context = CreateContext("receipts_empty"); + + await context.Database.MigrateAsync(); + + Assert.Equal(0, await context.Receipts.CountAsync()); + Assert.Equal(0, await context.ReceiptItems.CountAsync()); + } + + [Fact] + public async Task Receipt_IsLinkedToItsDonor() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var saved = await context.Receipts.Include(r => r.Donor).SingleAsync(r => r.Id == seed.ReceiptId); + + Assert.Equal(seed.DonorId, saved.DonorId); + Assert.Equal(seed.DonorId, saved.Donor.Id); + } + + [Fact] + public async Task Receipt_IsLinkedToItsFoodItems() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var foodItemIds = await context.ReceiptItems + .Where(i => i.ReceiptId == seed.ReceiptId) + .Select(i => i.FoodItem.Id) + .ToListAsync(); + + Assert.Equal(new[] { seed.BreadId, seed.ApplesId }.Order(), foodItemIds.Order()); + } + + [Fact] + public async Task Receipt_StoresTotalAmount() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var saved = await context.Receipts.SingleAsync(r => r.Id == seed.ReceiptId); + + Assert.Equal(44.97m, saved.TotalAmount); + } + + [Fact] + public async Task Receipt_SetsCreatedAt() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var saved = await context.Receipts.SingleAsync(r => r.Id == seed.ReceiptId); + + Assert.NotEqual(default, saved.CreatedAt); + } + + [Fact] + public async Task ReceiptItem_StoresUnitPriceWithCents() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var saved = await context.ReceiptItems.SingleAsync(i => i.ReceiptId == seed.ReceiptId && i.FoodItemId == seed.ApplesId); + + Assert.Equal(2.49m, saved.UnitPrice); + } + + [Fact] + public async Task ReceiptItem_StoresQualityPercent() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var saved = await context.ReceiptItems.SingleAsync(i => i.ReceiptId == seed.ReceiptId && i.FoodItemId == seed.BreadId); + + Assert.Equal(75m, saved.QualityPercent); + } + + [Fact] + public async Task ReceiptItems_LineValuesAddUpToTotalAmount() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var lineValues = await context.ReceiptItems + .Where(i => i.ReceiptId == seed.ReceiptId) + .OrderBy(i => i.FoodItem.Name) + .Select(i => i.FoodItem.Quantity * i.UnitPrice * i.QualityPercent / 100m) + .ToListAsync(); + var total = await context.Receipts.Where(r => r.Id == seed.ReceiptId).Select(r => r.TotalAmount).SingleAsync(); + + Assert.Equal([37.50m, 7.47m], lineValues); + Assert.Equal(total, lineValues.Sum()); + } + + [Fact] + public async Task ReceiptItem_AcceptsZeroPercent_ForQualityC() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + var spoiled = new FoodItem + { + DonorId = seed.DonorId, + Name = "Yogourt", + Quantity = 2m, + Unit = "kg", + DonationDate = new DateOnly(2026, 11, 3), + Quality = "C" + }; + context.Receipts.Add(new Receipt + { + DonorId = seed.DonorId, + TotalAmount = 0m, + Items = [new ReceiptItem { FoodItem = spoiled, UnitPrice = 3.00m, QualityPercent = 0m }] + }); + + await context.SaveChangesAsync(); + } + + [Fact] + public async Task DeleteReceipt_DeletesItsItems_ButKeepsTheFoodItems() + { + var seed = await SeedAsync(); + + await using (var deleteContext = CreateContext()) + { + deleteContext.Receipts.Remove(await deleteContext.Receipts.SingleAsync(r => r.Id == seed.ReceiptId)); + await deleteContext.SaveChangesAsync(); + } + + await using var context = CreateContext(); + Assert.Equal(0, await context.ReceiptItems.CountAsync(i => i.ReceiptId == seed.ReceiptId)); + Assert.Equal(2, await context.FoodItems.CountAsync(f => f.Id == seed.BreadId || f.Id == seed.ApplesId)); + } + + [Fact] + public async Task SaveReceipt_Throws_WhenDonorDoesNotExist() + { + await using var context = CreateContext(); + await context.Database.MigrateAsync(); + + context.Receipts.Add(new Receipt { DonorId = Guid.NewGuid(), TotalAmount = 10m }); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipts_donors_donor_id"); + } + + [Fact] + public async Task SaveReceiptItem_Throws_WhenFoodItemDoesNotExist() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.ReceiptItems.Add(new ReceiptItem + { + ReceiptId = seed.ReceiptId, + FoodItemId = Guid.NewGuid(), + UnitPrice = 1m, + QualityPercent = 100m + }); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id"); + } + + [Fact] + public async Task SaveReceiptItem_Throws_WhenFoodItemIsAlreadyOnTheReceipt() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.ReceiptItems.Add(new ReceiptItem + { + ReceiptId = seed.ReceiptId, + FoodItemId = seed.BreadId, + UnitPrice = 4.00m, + QualityPercent = 75m + }); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "pk_receipt_items"); + } + + [Fact] + public async Task DeleteDonor_Throws_WhenReceiptsRemain() + { + Guid donorId; + await using (var seedContext = CreateContext()) + { + await seedContext.Database.MigrateAsync(); + var donor = new Donor { Name = "Épicerie du Plateau" }; + seedContext.Receipts.Add(new Receipt { Donor = donor, TotalAmount = 0m }); + await seedContext.SaveChangesAsync(); + donorId = donor.Id; + } + + await using var context = CreateContext(); + context.Donors.Remove(await context.Donors.SingleAsync(d => d.Id == donorId)); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipts_donors_donor_id"); + } + + [Fact] + public async Task DeleteFoodItem_Throws_WhenItIsOnAReceipt() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.FoodItems.Remove(await context.FoodItems.SingleAsync(f => f.Id == seed.BreadId)); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id"); + } + + [Fact] + public async Task SaveChanges_Throws_WhenTotalAmountIsNegative() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.Receipts.Add(new Receipt { DonorId = seed.DonorId, TotalAmount = -1m }); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipts_total_amount_not_negative"); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task SaveChanges_Throws_WhenUnitPriceIsNotPositive(int unitPrice) + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.Receipts.Add(new Receipt + { + DonorId = seed.DonorId, + TotalAmount = 0m, + Items = [new ReceiptItem { FoodItemId = seed.BreadId, UnitPrice = unitPrice, QualityPercent = 100m }] + }); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipt_items_unit_price_positive"); + } + + [Theory] + [InlineData(-1)] + [InlineData(101)] + public async Task SaveChanges_Throws_WhenQualityPercentIsOutOfRange(int qualityPercent) + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.Receipts.Add(new Receipt + { + DonorId = seed.DonorId, + TotalAmount = 0m, + Items = [new ReceiptItem { FoodItemId = seed.BreadId, UnitPrice = 4.00m, QualityPercent = qualityPercent }] + }); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipt_items_quality_percent_range"); + } + + private async Task SeedAsync() + { + await using var context = CreateContext(); + await context.Database.MigrateAsync(); + + var donor = new Donor { Name = "Boulangerie Saint-Laurent" }; + var bread = new FoodItem + { + Donor = donor, + Name = "Pain", + Quantity = 12.50m, + Unit = "kg", + DonationDate = new DateOnly(2026, 11, 3), + Quality = "B" + }; + var apples = new FoodItem + { + Donor = donor, + Name = "Pommes", + Quantity = 3m, + Unit = "caisse", + DonationDate = new DateOnly(2026, 11, 3), + Quality = "A" + }; + var receipt = new Receipt + { + Donor = donor, + TotalAmount = 44.97m, + Items = + [ + new ReceiptItem { FoodItem = bread, UnitPrice = 4.00m, QualityPercent = 75m }, + new ReceiptItem { FoodItem = apples, UnitPrice = 2.49m, QualityPercent = 100m } + ] + }; + + context.Receipts.Add(receipt); + await context.SaveChangesAsync(); + + return new Seed(donor.Id, receipt.Id, bread.Id, apples.Id); + } + + private MoissonDbContext CreateContext(string? database = null) + { + var connectionString = postgres.ConnectionString; + if (database is not null) + { + var builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; + builder["Database"] = database; + connectionString = builder.ConnectionString; + } + + return new MoissonDbContext(new DbContextOptionsBuilder().UseNpgsql(connectionString).Options); + } + + private sealed record Seed(Guid DonorId, Guid ReceiptId, Guid BreadId, Guid ApplesId); +} \ No newline at end of file From bafae6b36132a078d2869615276ad758c600ecc5 Mon Sep 17 00:00:00 2001 From: MarcAntoineGmd Date: Fri, 6 Nov 2026 23:49:59 -0500 Subject: [PATCH 2/2] fix(api): address review feedback on receipts --- docs/database/images/receipts.png | Bin 58588 -> 72326 bytes docs/database/receipts.dbml | 26 +- docs/database/receipts.md | 58 +++- .../sql/0002_create_donors_and_food_items.sql | 2 +- docs/database/sql/0003_create_receipts.sql | 25 +- .../Configurations/ReceiptConfiguration.cs | 6 +- .../ReceiptItemConfiguration.cs | 19 +- ...20261107041912_CreateReceipts.Designer.cs} | 52 +++- ...ts.cs => 20261107041912_CreateReceipts.cs} | 43 ++- .../MoissonDbContextModelSnapshot.cs | 50 +++- src/MoissonMontreal.Core/Entities/Receipt.cs | 7 +- .../Entities/ReceiptItem.cs | 21 +- .../Api/ReceiptsMigrationTests.cs | 257 +++++++++++++++--- 13 files changed, 465 insertions(+), 101 deletions(-) rename src/MoissonMontreal.Api/Data/Migrations/{20261106021712_CreateReceipts.Designer.cs => 20261107041912_CreateReceipts.Designer.cs} (88%) rename src/MoissonMontreal.Api/Data/Migrations/{20261106021712_CreateReceipts.cs => 20261107041912_CreateReceipts.cs} (63%) diff --git a/docs/database/images/receipts.png b/docs/database/images/receipts.png index c9a20ca4ad06b2adb8e086bf195f2dc39bab38f2..52c4e73520770ad8f7655ce9e09590bd45a058ba 100644 GIT binary patch literal 72326 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 Y#oWG!555S)Yfuyps~$=|c=pEs0NHHFegFUf literal 58588 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 Puc@Y@shqU`^p*bxP*XV~ diff --git a/docs/database/receipts.dbml b/docs/database/receipts.dbml index 558777c..490a2ea 100644 --- a/docs/database/receipts.dbml +++ b/docs/database/receipts.dbml @@ -1,6 +1,6 @@ // Receipts and their prices // Paste this file into https://dbdiagram.io to render the diagram. -// Matches the migration 20261106021712_CreateReceipts. +// Matches the migration 20261107041912_CreateReceipts. // donors and food_items come from the migration 20261105163138_CreateDonorsAndFoodItems // and are shown here because receipts link to them. @@ -35,6 +35,7 @@ Table food_items { indexes { id [pk, name: 'pk_food_items'] + (id, donor_id) [unique, name: 'ak_food_items_id_donor_id', note: 'Added by CreateReceipts for the receipt_items link'] donor_id [name: 'ix_food_items_donor_id'] } @@ -51,11 +52,13 @@ Table food_items { Table receipts { id uuid [not null, default: `gen_random_uuid()`] donor_id uuid [not null] - total_amount numeric(12,2) [not null, note: 'Sum of the line values, in dollars'] - created_at timestamptz [not null, default: `now()`] + total_amount numeric(12,2) [not null, note: 'Sum of the rounded line values, in dollars'] + receipt_date date [not null, default: `(now() AT TIME ZONE 'America/Montreal')::date`, note: 'Date in Montreal time, used for the tax year'] + created_at timestamptz [not null, default: `now()`, note: 'Stored in UTC'] indexes { id [pk, name: 'pk_receipts'] + (id, donor_id) [unique, name: 'ak_receipts_id_donor_id'] donor_id [name: 'ix_receipts_donor_id'] } @@ -69,26 +72,33 @@ Table receipts { Table receipt_items { receipt_id uuid [not null] food_item_id uuid [not null] - unit_price numeric(12,2) [not null, note: 'Price of one unit when the receipt was made'] + donor_id uuid [not null, note: 'Donor of both the receipt and the food item'] + quantity numeric(12,2) [not null, note: 'Copied from the food item when the receipt was made'] + unit varchar(20) [not null, note: 'Copied from the food item when the receipt was made'] + unit_price numeric(12,4) [not null, note: 'Price of one unit when the receipt was made'] quality_percent numeric(5,2) [not null, note: 'A = 100, B = 50 to 75, C = 0'] indexes { (receipt_id, food_item_id) [pk, name: 'pk_receipt_items'] - food_item_id [name: 'ix_receipt_items_food_item_id'] + food_item_id [unique, name: 'ix_receipt_items_food_item_id'] + (food_item_id, donor_id) [name: 'ix_receipt_items_food_item_id_donor_id'] + (receipt_id, donor_id) [name: 'ix_receipt_items_receipt_id_donor_id'] } checks { + `quantity > 0` [name: 'ck_receipt_items_quantity_positive'] + `length(unit) > 0` [name: 'ck_receipt_items_unit_not_empty'] `unit_price > 0` [name: 'ck_receipt_items_unit_price_positive'] `quality_percent BETWEEN 0 AND 100` [name: 'ck_receipt_items_quality_percent_range'] } - Note: 'Food items on a receipt. Line value = quantity x unit_price x quality_percent / 100' + Note: 'Food items on a receipt. Line value = round(quantity x unit_price x quality_percent / 100, 2), half away from zero' } Ref fk_food_items_donors_donor_id: food_items.donor_id > donors.id [delete: restrict] Ref fk_receipts_donors_donor_id: receipts.donor_id > donors.id [delete: restrict] -Ref fk_receipt_items_receipts_receipt_id: receipt_items.receipt_id > receipts.id [delete: cascade] +Ref fk_receipt_items_receipts_receipt_id_donor_id: receipt_items.(receipt_id, donor_id) > receipts.(id, donor_id) [delete: cascade] -Ref fk_receipt_items_food_items_food_item_id: receipt_items.food_item_id > food_items.id [delete: restrict] +Ref fk_receipt_items_food_items_food_item_id_donor_id: receipt_items.(food_item_id, donor_id) > food_items.(id, donor_id) [delete: restrict] diff --git a/docs/database/receipts.md b/docs/database/receipts.md index e63eabf..eeee932 100644 --- a/docs/database/receipts.md +++ b/docs/database/receipts.md @@ -1,6 +1,6 @@ # Receipts and their prices -These two tables hold the tax receipts given to donors and the value of each food item on them. A receipt belongs to one donor and lists the food items that donor gave. For each item, the receipt keeps the price and the quality percentage used to value it, so the amount can always be checked and recalculated. +These two tables hold the tax receipts given to donors and the value of each food item on them. A receipt belongs to one donor and lists the food items that donor gave. For each item, the receipt keeps the quantity, unit, price and quality percentage used to value it, so the amount can always be checked and recalculated, even if the food item is edited later. ## Diagram @@ -20,8 +20,9 @@ One row per tax receipt. | --- | --- | | id | Code that identifies the receipt, assigned automatically | | donor_id | The donor who receives the receipt (see the donors table) | -| total_amount | Total value of the receipt, in dollars and cents. It is the sum of the values of its food items. | -| created_at | Date and time the receipt was created | +| total_amount | Total value of the receipt, in dollars and cents. It is the sum of the rounded values of its food items. | +| receipt_date | Date of the receipt in Montreal time, set automatically. This is the date used for the tax year. | +| created_at | Date and time the receipt was created, stored in UTC | ### receipt_items @@ -31,14 +32,17 @@ One row per food item on a receipt. | --- | --- | | receipt_id | The receipt this line belongs to | | food_item_id | The food item valued on this line (see the food_items table) | -| unit_price | Price of one unit of the item when the receipt was made, in dollars and cents. For example, the price of 1 kg of bread. | +| donor_id | The donor of both the receipt and the food item. It makes sure they are the same donor. | +| quantity | Quantity of the food item when the receipt was made, copied from the food item | +| unit | Unit of that quantity, such as kg or case, copied from the food item | +| unit_price | Price of one unit of the item when the receipt was made, in dollars, with up to 4 decimal places. For example, $0.125 per kg. | | quality_percent | Share of the price kept for the item's quality grade: 100 for grade A, between 50 and 75 for grade B, 0 for grade C | ## How the value is calculated The value of each food item on a receipt is: -quantity of the item × unit price × quality percentage +quantity × unit price × quality percentage The total of the receipt is the sum of these values. For example, a bakery gives 12.50 kg of bread of grade B and 3 cases of apples of grade A: @@ -48,15 +52,32 @@ The total of the receipt is the sum of these values. For example, a bakery gives | Apples | 3 cases | $2.49 | 100 % | $7.47 | | Total | | | | $44.97 | -The quantity comes from the food item. The unit price and the quality percentage are saved on the receipt itself. If the price grid changes later, receipts that already exist keep the values they were made with. +The quantity, unit, unit price and quality percentage are all saved on the receipt itself. If the food item or the price grid changes later, receipts that already exist keep the values they were made with. -All amounts are stored as exact decimal numbers, so no cent is lost or rounded along the way. +All amounts are stored as exact decimal numbers, so no cent is lost along the way. + +### Rounding + +1. Each line value is rounded to the cent. +2. A value exactly halfway between two cents is rounded away from zero. +3. The total is the sum of the rounded line values, so it always matches the line-by-line detail. + +For example, 1 kg × $0.45 × 50 % = $0.225, which gives $0.23. + +This is what PostgreSQL's round() does. In .NET, use Math.Round(value, 2, MidpointRounding.AwayFromZero). The default Math.Round(value, 2) rounds half to even and would give $0.22. + +## Dates and time zones + +receipt_date is the date to use for the tax year and on the printed receipt. The database sets it to the current date in Montreal time. A receipt made on December 31 at 8 p.m. in Montreal is dated December 31, even though it is already January 1 in UTC. + +created_at is a precise moment, stored and read back in UTC. Convert it to America/Montreal before showing it. The app can save a time with any offset, such as DateTimeOffset.Now, because it is converted to UTC before it is written. ## How the tables are linked 1. Each receipt belongs to exactly one donor. A donor can have many receipts. -2. Each receipt lists its food items through receipt_items. A food item can appear on more than one receipt, for example when a receipt is replaced later. -3. The same food item cannot appear twice on the same receipt. +2. Each receipt lists its food items through receipt_items. +3. A food item can appear on only one receipt, so one donation cannot get two tax receipts. +4. A receipt can only list food items given by its own donor. ## Rules enforced by the database @@ -64,18 +85,27 @@ All amounts are stored as exact decimal numbers, so no cent is lost or rounded a 2. A donor cannot be deleted while they still have a receipt. 3. A food item cannot be deleted while it is on a receipt. 4. When a receipt is deleted, its lines are deleted with it. The food items themselves are kept. -5. The total of a receipt cannot be negative. It can be $0, for example when every item is grade C. -6. A unit price must be greater than $0. -7. A quality percentage must be between 0 and 100. +5. A food item cannot be on two receipts. +6. The food item and the receipt on a line must have the same donor. This is enforced by including donor_id in both links. +7. The total of a receipt cannot be negative. It can be $0, for example when every item is grade C. +8. A quantity must be greater than 0, and a unit cannot be empty. +9. A unit price must be greater than $0. +10. A quality percentage must be between 0 and 100. + +Because donor_id is part of both links, the app cannot change the donor of a receipt or of a food item once it is saved. -The app, not the database, makes sure that the food items on a receipt were given by the receipt's donor, and that the total matches the sum of the lines. +The app, not the database, makes sure that the total matches the sum of the rounded lines. + +When cancelled and replacement receipts are added (issue #31), rule 5 will need to allow a food item on a cancelled receipt and on the receipt that replaces it. ## Migration | Item | Location | | --- | --- | -| Entity Framework migration | [20261106021712_CreateReceipts](../../src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs) | +| Entity Framework migration | [20261107041912_CreateReceipts](../../src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.cs) | | Equivalent SQL script | [0003_create_receipts.sql](sql/0003_create_receipts.sql) | | Table settings | [ReceiptConfiguration.cs](../../src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs) and [ReceiptItemConfiguration.cs](../../src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs) | The SQL script is generated from the migration with `dotnet ef migrations script CreateDonorsAndFoodItems CreateReceipts --project src/MoissonMontreal.Api --output docs/database/sql/0003_create_receipts.sql`. It must be run after [0001_create_users_and_roles.sql](sql/0001_create_users_and_roles.sql) and [0002_create_donors_and_food_items.sql](sql/0002_create_donors_and_food_items.sql). It records itself in the migration history, so Entity Framework will not apply it a second time. + +The generated file starts with a UTF-8 byte order mark (BOM). Remove it before committing, for example with `sed -i '1s/^\xEF\xBB\xBF//' docs/database/sql/0003_create_receipts.sql`. diff --git a/docs/database/sql/0002_create_donors_and_food_items.sql b/docs/database/sql/0002_create_donors_and_food_items.sql index e5ea88a..ae07368 100644 --- a/docs/database/sql/0002_create_donors_and_food_items.sql +++ b/docs/database/sql/0002_create_donors_and_food_items.sql @@ -1,4 +1,4 @@ -START TRANSACTION; +START TRANSACTION; CREATE TABLE donors ( id uuid NOT NULL DEFAULT (gen_random_uuid()), name character varying(255) NOT NULL, diff --git a/docs/database/sql/0003_create_receipts.sql b/docs/database/sql/0003_create_receipts.sql index 1862d1c..30e6726 100644 --- a/docs/database/sql/0003_create_receipts.sql +++ b/docs/database/sql/0003_create_receipts.sql @@ -1,10 +1,14 @@ -START TRANSACTION; +START TRANSACTION; +ALTER TABLE food_items ADD CONSTRAINT ak_food_items_id_donor_id UNIQUE (id, donor_id); + CREATE TABLE receipts ( id uuid NOT NULL DEFAULT (gen_random_uuid()), donor_id uuid NOT NULL, total_amount numeric(12,2) NOT NULL, + receipt_date date NOT NULL DEFAULT ((now() AT TIME ZONE 'America/Montreal')::date), created_at timestamp with time zone NOT NULL DEFAULT (now()), CONSTRAINT pk_receipts PRIMARY KEY (id), + CONSTRAINT ak_receipts_id_donor_id UNIQUE (id, donor_id), CONSTRAINT ck_receipts_total_amount_not_negative CHECK (total_amount >= 0), CONSTRAINT fk_receipts_donors_donor_id FOREIGN KEY (donor_id) REFERENCES donors (id) ON DELETE RESTRICT ); @@ -12,21 +16,30 @@ CREATE TABLE receipts ( CREATE TABLE receipt_items ( receipt_id uuid NOT NULL, food_item_id uuid NOT NULL, - unit_price numeric(12,2) NOT NULL, + donor_id uuid NOT NULL, + quantity numeric(12,2) NOT NULL, + unit character varying(20) NOT NULL, + unit_price numeric(12,4) NOT NULL, quality_percent numeric(5,2) NOT NULL, CONSTRAINT pk_receipt_items PRIMARY KEY (receipt_id, food_item_id), CONSTRAINT ck_receipt_items_quality_percent_range CHECK (quality_percent BETWEEN 0 AND 100), + CONSTRAINT ck_receipt_items_quantity_positive CHECK (quantity > 0), + CONSTRAINT ck_receipt_items_unit_not_empty CHECK (length(unit) > 0), CONSTRAINT ck_receipt_items_unit_price_positive CHECK (unit_price > 0), - CONSTRAINT fk_receipt_items_food_items_food_item_id FOREIGN KEY (food_item_id) REFERENCES food_items (id) ON DELETE RESTRICT, - CONSTRAINT fk_receipt_items_receipts_receipt_id FOREIGN KEY (receipt_id) REFERENCES receipts (id) ON DELETE CASCADE + CONSTRAINT fk_receipt_items_food_items_food_item_id_donor_id FOREIGN KEY (food_item_id, donor_id) REFERENCES food_items (id, donor_id) ON DELETE RESTRICT, + CONSTRAINT fk_receipt_items_receipts_receipt_id_donor_id FOREIGN KEY (receipt_id, donor_id) REFERENCES receipts (id, donor_id) ON DELETE CASCADE ); -CREATE INDEX ix_receipt_items_food_item_id ON receipt_items (food_item_id); +CREATE UNIQUE INDEX ix_receipt_items_food_item_id ON receipt_items (food_item_id); + +CREATE INDEX ix_receipt_items_food_item_id_donor_id ON receipt_items (food_item_id, donor_id); + +CREATE INDEX ix_receipt_items_receipt_id_donor_id ON receipt_items (receipt_id, donor_id); CREATE INDEX ix_receipts_donor_id ON receipts (donor_id); INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20261106021712_CreateReceipts', '10.0.13'); +VALUES ('20261107041912_CreateReceipts', '10.0.13'); COMMIT; diff --git a/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs b/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs index 7a4b11b..1f7b4d2 100644 --- a/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs +++ b/src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs @@ -21,7 +21,11 @@ public void Configure(EntityTypeBuilder builder) builder.Property(r => r.TotalAmount).HasPrecision(12, 2); - builder.Property(r => r.CreatedAt).HasDefaultValueSql("now()"); + builder.Property(r => r.ReceiptDate).HasDefaultValueSql("(now() AT TIME ZONE 'America/Montreal')::date"); + + builder.Property(r => r.CreatedAt) + .HasDefaultValueSql("now()") + .HasConversion(v => v.ToUniversalTime(), v => v); builder.HasOne(r => r.Donor) .WithMany() diff --git a/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs b/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs index b8850b4..0527fbe 100644 --- a/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs +++ b/src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs @@ -10,31 +10,42 @@ namespace MoissonMontreal.Api.Data.Configurations; public class ReceiptItemConfiguration : IEntityTypeConfiguration { /// - /// Configures the key, price columns, constraints and relationships of the receipt_items table. + /// Configures the keys, copied values, constraints and relationships of the receipt_items table. + /// Both links include donor_id, so a receipt can only list food items given by its own donor. /// /// Builder used to configure the receipt item entity. public void Configure(EntityTypeBuilder builder) { builder.ToTable(t => { + t.HasCheckConstraint("ck_receipt_items_quantity_positive", "quantity > 0"); + t.HasCheckConstraint("ck_receipt_items_unit_not_empty", "length(unit) > 0"); t.HasCheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); t.HasCheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); }); builder.HasKey(i => new { i.ReceiptId, i.FoodItemId }); - builder.Property(i => i.UnitPrice).HasPrecision(12, 2); + builder.HasIndex(i => i.FoodItemId).IsUnique(); + + builder.Property(i => i.Quantity).HasPrecision(12, 2); + + builder.Property(i => i.Unit).HasMaxLength(20); + + builder.Property(i => i.UnitPrice).HasPrecision(12, 4); builder.Property(i => i.QualityPercent).HasPrecision(5, 2); builder.HasOne(i => i.Receipt) .WithMany(r => r.Items) - .HasForeignKey(i => i.ReceiptId) + .HasForeignKey(i => new { i.ReceiptId, i.DonorId }) + .HasPrincipalKey(r => new { r.Id, r.DonorId }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne(i => i.FoodItem) .WithMany() - .HasForeignKey(i => i.FoodItemId) + .HasForeignKey(i => new { i.FoodItemId, i.DonorId }) + .HasPrincipalKey(f => new { f.Id, f.DonorId }) .OnDelete(DeleteBehavior.Restrict); } } \ No newline at end of file diff --git a/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs b/src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.Designer.cs similarity index 88% rename from src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs rename to src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.Designer.cs index 30bcfcc..de5465e 100644 --- a/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.Designer.cs +++ b/src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.Designer.cs @@ -12,7 +12,7 @@ namespace MoissonMontreal.Api.Data.Migrations { [DbContext(typeof(MoissonDbContext))] - [Migration("20261106021712_CreateReceipts")] + [Migration("20261107041912_CreateReceipts")] partial class CreateReceipts { /// @@ -116,6 +116,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id") .HasName("pk_food_items"); + b.HasAlternateKey("Id", "DonorId") + .HasName("ak_food_items_id_donor_id"); + b.HasIndex("DonorId") .HasDatabaseName("ix_food_items_donor_id"); @@ -149,6 +152,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("donor_id"); + b.Property("ReceiptDate") + .ValueGeneratedOnAdd() + .HasColumnType("date") + .HasColumnName("receipt_date") + .HasDefaultValueSql("(now() AT TIME ZONE 'America/Montreal')::date"); + b.Property("TotalAmount") .HasPrecision(12, 2) .HasColumnType("numeric(12,2)") @@ -157,6 +166,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id") .HasName("pk_receipts"); + b.HasAlternateKey("Id", "DonorId") + .HasName("ak_receipts_id_donor_id"); + b.HasIndex("DonorId") .HasDatabaseName("ix_receipts_donor_id"); @@ -176,26 +188,52 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("food_item_id"); + b.Property("DonorId") + .HasColumnType("uuid") + .HasColumnName("donor_id"); + b.Property("QualityPercent") .HasPrecision(5, 2) .HasColumnType("numeric(5,2)") .HasColumnName("quality_percent"); - b.Property("UnitPrice") + b.Property("Quantity") .HasPrecision(12, 2) .HasColumnType("numeric(12,2)") + .HasColumnName("quantity"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("unit"); + + b.Property("UnitPrice") + .HasPrecision(12, 4) + .HasColumnType("numeric(12,4)") .HasColumnName("unit_price"); b.HasKey("ReceiptId", "FoodItemId") .HasName("pk_receipt_items"); b.HasIndex("FoodItemId") + .IsUnique() .HasDatabaseName("ix_receipt_items_food_item_id"); + b.HasIndex("FoodItemId", "DonorId") + .HasDatabaseName("ix_receipt_items_food_item_id_donor_id"); + + b.HasIndex("ReceiptId", "DonorId") + .HasDatabaseName("ix_receipt_items_receipt_id_donor_id"); + b.ToTable("receipt_items", null, t => { t.HasCheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + t.HasCheckConstraint("ck_receipt_items_quantity_positive", "quantity > 0"); + + t.HasCheckConstraint("ck_receipt_items_unit_not_empty", "length(unit) > 0"); + t.HasCheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); }); }); @@ -351,17 +389,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("MoissonMontreal.Core.Entities.FoodItem", "FoodItem") .WithMany() - .HasForeignKey("FoodItemId") + .HasForeignKey("FoodItemId", "DonorId") + .HasPrincipalKey("Id", "DonorId") .OnDelete(DeleteBehavior.Restrict) .IsRequired() - .HasConstraintName("fk_receipt_items_food_items_food_item_id"); + .HasConstraintName("fk_receipt_items_food_items_food_item_id_donor_id"); b.HasOne("MoissonMontreal.Core.Entities.Receipt", "Receipt") .WithMany("Items") - .HasForeignKey("ReceiptId") + .HasForeignKey("ReceiptId", "DonorId") + .HasPrincipalKey("Id", "DonorId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_receipt_items_receipts_receipt_id"); + .HasConstraintName("fk_receipt_items_receipts_receipt_id_donor_id"); b.Navigation("FoodItem"); diff --git a/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs b/src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.cs similarity index 63% rename from src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs rename to src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.cs index 0f692a9..35a20af 100644 --- a/src/MoissonMontreal.Api/Data/Migrations/20261106021712_CreateReceipts.cs +++ b/src/MoissonMontreal.Api/Data/Migrations/20261107041912_CreateReceipts.cs @@ -11,6 +11,11 @@ public partial class CreateReceipts : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.AddUniqueConstraint( + name: "ak_food_items_id_donor_id", + table: "food_items", + columns: new[] { "id", "donor_id" }); + migrationBuilder.CreateTable( name: "receipts", columns: table => new @@ -18,11 +23,13 @@ protected override void Up(MigrationBuilder migrationBuilder) id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), donor_id = table.Column(type: "uuid", nullable: false), total_amount = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false), + receipt_date = table.Column(type: "date", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'America/Montreal')::date"), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { table.PrimaryKey("pk_receipts", x => x.id); + table.UniqueConstraint("ak_receipts_id_donor_id", x => new { x.id, x.donor_id }); table.CheckConstraint("ck_receipts_total_amount_not_negative", "total_amount >= 0"); table.ForeignKey( name: "fk_receipts_donors_donor_id", @@ -38,32 +45,48 @@ protected override void Up(MigrationBuilder migrationBuilder) { receipt_id = table.Column(type: "uuid", nullable: false), food_item_id = table.Column(type: "uuid", nullable: false), - unit_price = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false), + donor_id = table.Column(type: "uuid", nullable: false), + quantity = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false), + unit = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + unit_price = table.Column(type: "numeric(12,4)", precision: 12, scale: 4, nullable: false), quality_percent = table.Column(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false) }, constraints: table => { table.PrimaryKey("pk_receipt_items", x => new { x.receipt_id, x.food_item_id }); table.CheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + table.CheckConstraint("ck_receipt_items_quantity_positive", "quantity > 0"); + table.CheckConstraint("ck_receipt_items_unit_not_empty", "length(unit) > 0"); table.CheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); table.ForeignKey( - name: "fk_receipt_items_food_items_food_item_id", - column: x => x.food_item_id, + name: "fk_receipt_items_food_items_food_item_id_donor_id", + columns: x => new { x.food_item_id, x.donor_id }, principalTable: "food_items", - principalColumn: "id", + principalColumns: new[] { "id", "donor_id" }, onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "fk_receipt_items_receipts_receipt_id", - column: x => x.receipt_id, + name: "fk_receipt_items_receipts_receipt_id_donor_id", + columns: x => new { x.receipt_id, x.donor_id }, principalTable: "receipts", - principalColumn: "id", + principalColumns: new[] { "id", "donor_id" }, onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "ix_receipt_items_food_item_id", table: "receipt_items", - column: "food_item_id"); + column: "food_item_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_receipt_items_food_item_id_donor_id", + table: "receipt_items", + columns: new[] { "food_item_id", "donor_id" }); + + migrationBuilder.CreateIndex( + name: "ix_receipt_items_receipt_id_donor_id", + table: "receipt_items", + columns: new[] { "receipt_id", "donor_id" }); migrationBuilder.CreateIndex( name: "ix_receipts_donor_id", @@ -79,6 +102,10 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "receipts"); + + migrationBuilder.DropUniqueConstraint( + name: "ak_food_items_id_donor_id", + table: "food_items"); } } } diff --git a/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs b/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs index 97bc2a2..eaffa10 100644 --- a/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs +++ b/src/MoissonMontreal.Api/Data/Migrations/MoissonDbContextModelSnapshot.cs @@ -113,6 +113,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id") .HasName("pk_food_items"); + b.HasAlternateKey("Id", "DonorId") + .HasName("ak_food_items_id_donor_id"); + b.HasIndex("DonorId") .HasDatabaseName("ix_food_items_donor_id"); @@ -146,6 +149,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("donor_id"); + b.Property("ReceiptDate") + .ValueGeneratedOnAdd() + .HasColumnType("date") + .HasColumnName("receipt_date") + .HasDefaultValueSql("(now() AT TIME ZONE 'America/Montreal')::date"); + b.Property("TotalAmount") .HasPrecision(12, 2) .HasColumnType("numeric(12,2)") @@ -154,6 +163,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id") .HasName("pk_receipts"); + b.HasAlternateKey("Id", "DonorId") + .HasName("ak_receipts_id_donor_id"); + b.HasIndex("DonorId") .HasDatabaseName("ix_receipts_donor_id"); @@ -173,26 +185,52 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("food_item_id"); + b.Property("DonorId") + .HasColumnType("uuid") + .HasColumnName("donor_id"); + b.Property("QualityPercent") .HasPrecision(5, 2) .HasColumnType("numeric(5,2)") .HasColumnName("quality_percent"); - b.Property("UnitPrice") + b.Property("Quantity") .HasPrecision(12, 2) .HasColumnType("numeric(12,2)") + .HasColumnName("quantity"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("unit"); + + b.Property("UnitPrice") + .HasPrecision(12, 4) + .HasColumnType("numeric(12,4)") .HasColumnName("unit_price"); b.HasKey("ReceiptId", "FoodItemId") .HasName("pk_receipt_items"); b.HasIndex("FoodItemId") + .IsUnique() .HasDatabaseName("ix_receipt_items_food_item_id"); + b.HasIndex("FoodItemId", "DonorId") + .HasDatabaseName("ix_receipt_items_food_item_id_donor_id"); + + b.HasIndex("ReceiptId", "DonorId") + .HasDatabaseName("ix_receipt_items_receipt_id_donor_id"); + b.ToTable("receipt_items", null, t => { t.HasCheckConstraint("ck_receipt_items_quality_percent_range", "quality_percent BETWEEN 0 AND 100"); + t.HasCheckConstraint("ck_receipt_items_quantity_positive", "quantity > 0"); + + t.HasCheckConstraint("ck_receipt_items_unit_not_empty", "length(unit) > 0"); + t.HasCheckConstraint("ck_receipt_items_unit_price_positive", "unit_price > 0"); }); }); @@ -348,17 +386,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("MoissonMontreal.Core.Entities.FoodItem", "FoodItem") .WithMany() - .HasForeignKey("FoodItemId") + .HasForeignKey("FoodItemId", "DonorId") + .HasPrincipalKey("Id", "DonorId") .OnDelete(DeleteBehavior.Restrict) .IsRequired() - .HasConstraintName("fk_receipt_items_food_items_food_item_id"); + .HasConstraintName("fk_receipt_items_food_items_food_item_id_donor_id"); b.HasOne("MoissonMontreal.Core.Entities.Receipt", "Receipt") .WithMany("Items") - .HasForeignKey("ReceiptId") + .HasForeignKey("ReceiptId", "DonorId") + .HasPrincipalKey("Id", "DonorId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_receipt_items_receipts_receipt_id"); + .HasConstraintName("fk_receipt_items_receipts_receipt_id_donor_id"); b.Navigation("FoodItem"); diff --git a/src/MoissonMontreal.Core/Entities/Receipt.cs b/src/MoissonMontreal.Core/Entities/Receipt.cs index 05cfa49..5a22a48 100644 --- a/src/MoissonMontreal.Core/Entities/Receipt.cs +++ b/src/MoissonMontreal.Core/Entities/Receipt.cs @@ -26,7 +26,12 @@ public class Receipt public decimal TotalAmount { get; set; } /// - /// Date and time the receipt was created, set by the database. + /// Date of the receipt in Montreal time, set by the database. Used for the tax year. + /// + public DateOnly ReceiptDate { get; set; } + + /// + /// Date and time the receipt was created, set by the database. Stored and read back in UTC. /// public DateTimeOffset CreatedAt { get; set; } diff --git a/src/MoissonMontreal.Core/Entities/ReceiptItem.cs b/src/MoissonMontreal.Core/Entities/ReceiptItem.cs index e1ba30f..56f2e70 100644 --- a/src/MoissonMontreal.Core/Entities/ReceiptItem.cs +++ b/src/MoissonMontreal.Core/Entities/ReceiptItem.cs @@ -1,8 +1,8 @@ namespace MoissonMontreal.Core.Entities; /// -/// One food item on a receipt, with the price and quality percentage used to value it. -/// The value of the line is the item's quantity times the unit price times the quality percentage. +/// One food item on a receipt, with the quantity, price and quality percentage used to value it. +/// The value of the line is the quantity times the unit price times the quality percentage, rounded to the cent. /// public class ReceiptItem { @@ -16,6 +16,11 @@ public class ReceiptItem /// public Receipt Receipt { get; set; } = null!; + /// + /// Identifier of the donor of both the receipt and the food item, so they always match. + /// + public Guid DonorId { get; set; } + /// /// Identifier of the food item valued on this line. /// @@ -27,7 +32,17 @@ public class ReceiptItem public FoodItem FoodItem { get; set; } = null!; /// - /// Price of one unit of the food item when the receipt was made, in dollars. Must be greater than zero. + /// Quantity of the food item when the receipt was made. Must be greater than zero. + /// + public decimal Quantity { get; set; } + + /// + /// Unit of the quantity when the receipt was made, such as kg or case. + /// + public string Unit { get; set; } = string.Empty; + + /// + /// Price of one unit of the food item when the receipt was made, in dollars, with up to four decimal places. Must be greater than zero. /// public decimal UnitPrice { get; set; } diff --git a/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs b/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs index abec5df..a4d035c 100644 --- a/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs +++ b/tests/MoissonMontreal.Tests/Api/ReceiptsMigrationTests.cs @@ -69,14 +69,55 @@ public async Task Receipt_SetsCreatedAt() } [Fact] - public async Task ReceiptItem_StoresUnitPriceWithCents() + public async Task Receipt_SetsReceiptDate_InMontrealTime() { var seed = await SeedAsync(); await using var context = CreateContext(); - var saved = await context.ReceiptItems.SingleAsync(i => i.ReceiptId == seed.ReceiptId && i.FoodItemId == seed.ApplesId); + var saved = await context.Receipts.SingleAsync(r => r.Id == seed.ReceiptId); + var montreal = TimeZoneInfo.FindSystemTimeZoneById("America/Montreal"); - Assert.Equal(2.49m, saved.UnitPrice); + Assert.Equal(DateOnly.FromDateTime(TimeZoneInfo.ConvertTime(saved.CreatedAt, montreal).DateTime), saved.ReceiptDate); + } + + [Fact] + public async Task Receipt_SavesCreatedAt_WhenGivenInMontrealTime() + { + var seed = await SeedAsync(); + var newYearsEve = new DateTimeOffset(2026, 12, 31, 20, 0, 0, TimeSpan.FromHours(-5)); + Guid receiptId; + + await using (var saveContext = CreateContext()) + { + var receipt = new Receipt { DonorId = seed.DonorId, TotalAmount = 0m, CreatedAt = newYearsEve }; + saveContext.Receipts.Add(receipt); + await saveContext.SaveChangesAsync(); + receiptId = receipt.Id; + } + + await using var context = CreateContext(); + var saved = await context.Receipts.SingleAsync(r => r.Id == receiptId); + + Assert.Equal(newYearsEve, saved.CreatedAt); + Assert.Equal(TimeSpan.Zero, saved.CreatedAt.Offset); + } + + [Fact] + public async Task ReceiptItem_StoresUnitPriceWithFourDecimals() + { + var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); + + await using (var saveContext = CreateContext()) + { + saveContext.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, foodItemId, unitPrice: 0.125m)); + await saveContext.SaveChangesAsync(); + } + + await using var context = CreateContext(); + var saved = await context.ReceiptItems.SingleAsync(i => i.FoodItemId == foodItemId); + + Assert.Equal(0.125m, saved.UnitPrice); } [Fact] @@ -90,6 +131,26 @@ public async Task ReceiptItem_StoresQualityPercent() Assert.Equal(75m, saved.QualityPercent); } + [Fact] + public async Task ReceiptItem_KeepsItsQuantityAndUnit_WhenTheFoodItemChanges() + { + var seed = await SeedAsync(); + + await using (var editContext = CreateContext()) + { + var bread = await editContext.FoodItems.SingleAsync(f => f.Id == seed.BreadId); + bread.Quantity = 20m; + bread.Unit = "caisse"; + await editContext.SaveChangesAsync(); + } + + await using var context = CreateContext(); + var saved = await context.ReceiptItems.SingleAsync(i => i.ReceiptId == seed.ReceiptId && i.FoodItemId == seed.BreadId); + + Assert.Equal(12.50m, saved.Quantity); + Assert.Equal("kg", saved.Unit); + } + [Fact] public async Task ReceiptItems_LineValuesAddUpToTotalAmount() { @@ -99,7 +160,7 @@ public async Task ReceiptItems_LineValuesAddUpToTotalAmount() var lineValues = await context.ReceiptItems .Where(i => i.ReceiptId == seed.ReceiptId) .OrderBy(i => i.FoodItem.Name) - .Select(i => i.FoodItem.Quantity * i.UnitPrice * i.QualityPercent / 100m) + .Select(i => Math.Round(i.Quantity * i.UnitPrice * i.QualityPercent / 100m, 2)) .ToListAsync(); var total = await context.Receipts.Where(r => r.Id == seed.ReceiptId).Select(r => r.TotalAmount).SingleAsync(); @@ -107,26 +168,40 @@ public async Task ReceiptItems_LineValuesAddUpToTotalAmount() Assert.Equal(total, lineValues.Sum()); } + [Fact] + public async Task ReceiptItem_LineValueRoundsHalfAwayFromZero() + { + var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); + + await using (var saveContext = CreateContext()) + { + saveContext.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, foodItemId, unitPrice: 0.45m, qualityPercent: 50m)); + await saveContext.SaveChangesAsync(); + } + + await using var context = CreateContext(); + var lineValue = await context.ReceiptItems + .Where(i => i.FoodItemId == foodItemId) + .Select(i => Math.Round(i.Quantity * i.UnitPrice * i.QualityPercent / 100m, 2)) + .SingleAsync(); + + Assert.Equal(0.23m, lineValue); + Assert.Equal(Math.Round(0.225m, 2, MidpointRounding.AwayFromZero), lineValue); + } + [Fact] public async Task ReceiptItem_AcceptsZeroPercent_ForQualityC() { var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); await using var context = CreateContext(); - var spoiled = new FoodItem - { - DonorId = seed.DonorId, - Name = "Yogourt", - Quantity = 2m, - Unit = "kg", - DonationDate = new DateOnly(2026, 11, 3), - Quality = "C" - }; context.Receipts.Add(new Receipt { DonorId = seed.DonorId, TotalAmount = 0m, - Items = [new ReceiptItem { FoodItem = spoiled, UnitPrice = 3.00m, QualityPercent = 0m }] + Items = [Item(Guid.Empty, seed.DonorId, foodItemId, unitPrice: 3.00m, qualityPercent: 0m)] }); await context.SaveChangesAsync(); @@ -165,15 +240,9 @@ public async Task SaveReceiptItem_Throws_WhenFoodItemDoesNotExist() var seed = await SeedAsync(); await using var context = CreateContext(); - context.ReceiptItems.Add(new ReceiptItem - { - ReceiptId = seed.ReceiptId, - FoodItemId = Guid.NewGuid(), - UnitPrice = 1m, - QualityPercent = 100m - }); + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, Guid.NewGuid())); - await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id"); + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id_donor_id"); } [Fact] @@ -182,15 +251,51 @@ public async Task SaveReceiptItem_Throws_WhenFoodItemIsAlreadyOnTheReceipt() var seed = await SeedAsync(); await using var context = CreateContext(); - context.ReceiptItems.Add(new ReceiptItem + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, seed.BreadId)); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "pk_receipt_items"); + } + + [Fact] + public async Task SaveReceiptItem_Throws_WhenFoodItemIsOnAnotherReceipt() + { + var seed = await SeedAsync(); + + await using var context = CreateContext(); + context.Receipts.Add(new Receipt { - ReceiptId = seed.ReceiptId, - FoodItemId = seed.BreadId, - UnitPrice = 4.00m, - QualityPercent = 75m + DonorId = seed.DonorId, + TotalAmount = 37.50m, + Items = [Item(Guid.Empty, seed.DonorId, seed.BreadId, quantity: 12.50m, qualityPercent: 75m)] }); - await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "pk_receipt_items"); + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ix_receipt_items_food_item_id"); + } + + [Fact] + public async Task SaveReceiptItem_Throws_WhenFoodItemBelongsToAnotherDonor() + { + var seed = await SeedAsync(); + var otherDonorId = await AddDonorAsync(); + var otherFoodItemId = await AddFoodItemAsync(otherDonorId); + + await using var context = CreateContext(); + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, otherFoodItemId)); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id_donor_id"); + } + + [Fact] + public async Task SaveReceiptItem_Throws_WhenReceiptBelongsToAnotherDonor() + { + var seed = await SeedAsync(); + var otherDonorId = await AddDonorAsync(); + var otherFoodItemId = await AddFoodItemAsync(otherDonorId); + + await using var context = CreateContext(); + context.ReceiptItems.Add(Item(seed.ReceiptId, otherDonorId, otherFoodItemId)); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_receipts_receipt_id_donor_id"); } [Fact] @@ -220,7 +325,7 @@ public async Task DeleteFoodItem_Throws_WhenItIsOnAReceipt() await using var context = CreateContext(); context.FoodItems.Remove(await context.FoodItems.SingleAsync(f => f.Id == seed.BreadId)); - await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id"); + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "fk_receipt_items_food_items_food_item_id_donor_id"); } [Fact] @@ -234,20 +339,42 @@ public async Task SaveChanges_Throws_WhenTotalAmountIsNegative() await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipts_total_amount_not_negative"); } + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task SaveChanges_Throws_WhenQuantityIsNotPositive(int quantity) + { + var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); + + await using var context = CreateContext(); + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, foodItemId, quantity: quantity)); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipt_items_quantity_positive"); + } + + [Fact] + public async Task SaveChanges_Throws_WhenUnitIsEmpty() + { + var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); + + await using var context = CreateContext(); + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, foodItemId, unit: "")); + + await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipt_items_unit_not_empty"); + } + [Theory] [InlineData(0)] [InlineData(-1)] public async Task SaveChanges_Throws_WhenUnitPriceIsNotPositive(int unitPrice) { var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); await using var context = CreateContext(); - context.Receipts.Add(new Receipt - { - DonorId = seed.DonorId, - TotalAmount = 0m, - Items = [new ReceiptItem { FoodItemId = seed.BreadId, UnitPrice = unitPrice, QualityPercent = 100m }] - }); + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, foodItemId, unitPrice: unitPrice)); await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipt_items_unit_price_positive"); } @@ -258,14 +385,10 @@ public async Task SaveChanges_Throws_WhenUnitPriceIsNotPositive(int unitPrice) public async Task SaveChanges_Throws_WhenQualityPercentIsOutOfRange(int qualityPercent) { var seed = await SeedAsync(); + var foodItemId = await AddFoodItemAsync(seed.DonorId); await using var context = CreateContext(); - context.Receipts.Add(new Receipt - { - DonorId = seed.DonorId, - TotalAmount = 0m, - Items = [new ReceiptItem { FoodItemId = seed.BreadId, UnitPrice = 4.00m, QualityPercent = qualityPercent }] - }); + context.ReceiptItems.Add(Item(seed.ReceiptId, seed.DonorId, foodItemId, qualityPercent: qualityPercent)); await PostgresConstraintAssertions.AssertViolatesConstraintAsync(context, "ck_receipt_items_quality_percent_range"); } @@ -300,8 +423,8 @@ private async Task SeedAsync() TotalAmount = 44.97m, Items = [ - new ReceiptItem { FoodItem = bread, UnitPrice = 4.00m, QualityPercent = 75m }, - new ReceiptItem { FoodItem = apples, UnitPrice = 2.49m, QualityPercent = 100m } + new ReceiptItem { FoodItem = bread, Quantity = bread.Quantity, Unit = bread.Unit, UnitPrice = 4.00m, QualityPercent = 75m }, + new ReceiptItem { FoodItem = apples, Quantity = apples.Quantity, Unit = apples.Unit, UnitPrice = 2.49m, QualityPercent = 100m } ] }; @@ -311,6 +434,52 @@ private async Task SeedAsync() return new Seed(donor.Id, receipt.Id, bread.Id, apples.Id); } + private async Task AddDonorAsync() + { + await using var context = CreateContext(); + var donor = new Donor { Name = "Marché Jean-Talon" }; + context.Donors.Add(donor); + await context.SaveChangesAsync(); + + return donor.Id; + } + + private async Task AddFoodItemAsync(Guid donorId) + { + await using var context = CreateContext(); + var foodItem = new FoodItem + { + DonorId = donorId, + Name = "Yogourt", + Quantity = 1m, + Unit = "kg", + DonationDate = new DateOnly(2026, 11, 3), + Quality = "B" + }; + context.FoodItems.Add(foodItem); + await context.SaveChangesAsync(); + + return foodItem.Id; + } + + private static ReceiptItem Item( + Guid receiptId, + Guid donorId, + Guid foodItemId, + decimal quantity = 1m, + string unit = "kg", + decimal unitPrice = 4.00m, + decimal qualityPercent = 100m) => new() + { + ReceiptId = receiptId, + DonorId = donorId, + FoodItemId = foodItemId, + Quantity = quantity, + Unit = unit, + UnitPrice = unitPrice, + QualityPercent = qualityPercent + }; + private MoissonDbContext CreateContext(string? database = null) { var connectionString = postgres.ConnectionString; @@ -325,4 +494,4 @@ private MoissonDbContext CreateContext(string? database = null) } private sealed record Seed(Guid DonorId, Guid ReceiptId, Guid BreadId, Guid ApplesId); -} \ No newline at end of file +}