Repository navigation
feat(api): add receipts and receipt items tables with prices #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| // Receipts and their prices | ||
| // Paste this file into https://dbdiagram.io to render the diagram. | ||
| // Matches the migration 20261107041912_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'] | ||
| (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'] | ||
| } | ||
|
|
||
| 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 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'] | ||
| } | ||
|
|
||
| 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] | ||
| 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 [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 = 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_donor_id: receipt_items.(receipt_id, donor_id) > receipts.(id, donor_id) [delete: cascade] | ||
|
|
||
| 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] |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # 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 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 | ||
|
|
||
|  | ||
|
|
||
| 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 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 | ||
|
|
||
| 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) | | ||
| | 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 × 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, 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 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. | ||
| 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 | ||
|
|
||
| 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. 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 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 | [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`. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| 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 | ||
| ); | ||
|
|
||
| CREATE TABLE receipt_items ( | ||
| receipt_id uuid NOT NULL, | ||
| food_item_id uuid 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_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 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 ('20261107041912_CreateReceipts', '10.0.13'); | ||
|
|
||
| COMMIT; | ||
|
|
35 changes: 35 additions & 0 deletions
35
src/MoissonMontreal.Api/Data/Configurations/ReceiptConfiguration.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
| using MoissonMontreal.Core.Entities; | ||
|
|
||
| namespace MoissonMontreal.Api.Data.Configurations; | ||
|
|
||
| /// <summary> | ||
| /// Database rules of the receipts table and its link to the donors table. | ||
| /// </summary> | ||
| public class ReceiptConfiguration : IEntityTypeConfiguration<Receipt> | ||
| { | ||
| /// <summary> | ||
| /// Configures the columns, constraint and donor relationship of the receipts table. | ||
| /// </summary> | ||
| /// <param name="builder">Builder used to configure the receipt entity.</param> | ||
| public void Configure(EntityTypeBuilder<Receipt> 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.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() | ||
| .HasForeignKey(r => r.DonorId) | ||
| .OnDelete(DeleteBehavior.Restrict); | ||
| } | ||
| } |
51 changes: 51 additions & 0 deletions
51
src/MoissonMontreal.Api/Data/Configurations/ReceiptItemConfiguration.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
| using MoissonMontreal.Core.Entities; | ||
|
|
||
| namespace MoissonMontreal.Api.Data.Configurations; | ||
|
|
||
| /// <summary> | ||
| /// Database rules of the receipt_items table and its links to the receipts and food_items tables. | ||
| /// </summary> | ||
| public class ReceiptItemConfiguration : IEntityTypeConfiguration<ReceiptItem> | ||
| { | ||
| /// <summary> | ||
| /// 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. | ||
| /// </summary> | ||
| /// <param name="builder">Builder used to configure the receipt item entity.</param> | ||
| public void Configure(EntityTypeBuilder<ReceiptItem> 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 }); | ||
|
lauriechammah marked this conversation as resolved.
Resolved
|
||
|
|
||
| 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 => new { i.ReceiptId, i.DonorId }) | ||
| .HasPrincipalKey(r => new { r.Id, r.DonorId }) | ||
| .OnDelete(DeleteBehavior.Cascade); | ||
|
|
||
| builder.HasOne(i => i.FoodItem) | ||
| .WithMany() | ||
| .HasForeignKey(i => new { i.FoodItemId, i.DonorId }) | ||
| .HasPrincipalKey(f => new { f.Id, f.DonorId }) | ||
| .OnDelete(DeleteBehavior.Restrict); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.