Skip to content

Commit 922d1dd

Browse files
committed
Add customer and pricing details to invoices
This commit expands the invoice model to include customer details (name, email, phone, address) and pricing fields (subtotal, tax rate/amount, discount, payment terms). It makes the `OrderId` field optional, supports mapping invoice items, and improves validation for invoice item creation. Updates include database schema changes, DTO adjustments, and service logic enhancements.
1 parent db081af commit 922d1dd

File tree

9 files changed

+152
-30
lines changed

9 files changed

+152
-30
lines changed

Controllers/InvoiceController.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,23 @@ public async Task<ActionResult> CreateInvoice(CreateInvoiceDto createInvoiceDto)
6969
IssueDate = createInvoiceDto.IssueDate,
7070
DueDate = createInvoiceDto.DueDate,
7171
Notes = createInvoiceDto.Notes,
72-
OrderId = createInvoiceDto.OrderId,
72+
// Only set OrderId if it's not empty/null
73+
OrderId = createInvoiceDto.OrderId == Guid.Empty ? null : createInvoiceDto.OrderId,
7374
UserId = createInvoiceDto.UserId,
7475
Status = createInvoiceDto.Status,
7576
PaidAmount = createInvoiceDto.PaidAmount,
77+
// Add the new fields from your payload
78+
CustomerName = createInvoiceDto.CustomerName,
79+
CustomerEmail = createInvoiceDto.CustomerEmail,
80+
CustomerPhone = createInvoiceDto.CustomerPhone,
81+
CustomerAddress = createInvoiceDto.CustomerAddress,
82+
BillingAddress = createInvoiceDto.BillingAddress,
83+
TaxRate = createInvoiceDto.TaxRate,
84+
DiscountAmount = createInvoiceDto.DiscountAmount,
85+
PaymentTerms = createInvoiceDto.PaymentTerms,
86+
Subtotal = createInvoiceDto.Subtotal,
87+
TaxAmount = createInvoiceDto.TaxAmount,
88+
TotalAmount = createInvoiceDto.TotalAmount,
7689
Created = DateTime.UtcNow,
7790
Modified = DateTime.UtcNow,
7891
CreatedById = createInvoiceDto.CreatedById,

Data/ApplicationDbContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,12 @@ protected override void OnModelCreating(ModelBuilder builder)
3030
entity.HasIndex(e => e.TokenId).IsUnique();
3131
entity.HasIndex(e => e.ExpiryDate); // For efficient cleanup queries
3232
});
33+
34+
// Configure an Invoice-Order relationship as optional
35+
builder.Entity<Invoice>()
36+
.HasOne(i => i.Order)
37+
.WithMany()
38+
.HasForeignKey(i => i.OrderId)
39+
.IsRequired(false);
3340
}
3441
}

Dtos/Invoice/CreateInvoiceDto.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,24 @@ public class CreateInvoiceDto
99
public DateTime IssueDate { get; set; }
1010
public DateTime DueDate { get; set; }
1111
public string Notes { get; set; }
12-
public Guid OrderId { get; set; } = Guid.Empty;
12+
public Guid? OrderId { get; set; } // Make nullable
1313
public string UserId { get; set; }
1414
public decimal PaidAmount { get; set; }
1515
public InvoiceStatus Status { get; set; }
1616
public string CreatedById { get; set; }
17+
18+
// Add missing fields that are in your payload
19+
public string CustomerName { get; set; }
20+
public string CustomerEmail { get; set; }
21+
public string CustomerPhone { get; set; }
22+
public string CustomerAddress { get; set; }
23+
public string BillingAddress { get; set; }
24+
public decimal? TaxRate { get; set; }
25+
public decimal? DiscountAmount { get; set; }
26+
public string PaymentTerms { get; set; }
27+
public decimal? Subtotal { get; set; }
28+
public decimal? TaxAmount { get; set; }
29+
public decimal TotalAmount { get; set; }
30+
1731
public List<CreateInvoiceItemDto> Items { get; set; } = new List<CreateInvoiceItemDto>();
1832
}

Dtos/Invoice/InvoiceResponseDto.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@ public class InvoiceResponseDto
1414
public decimal PaidAmount { get; set; }
1515
public InvoiceStatus Status { get; set; }
1616
public string Notes { get; set; }
17-
public Guid OrderId { get; set; } = Guid.Empty;
17+
public Guid? OrderId { get; set; } = Guid.Empty;
1818
public string UserId { get; set; }
1919
public string UserName { get; set; }
20-
public List<InvoiceItemDto> Items { get; set; } = new List<InvoiceItemDto>();
20+
public List<InvoiceItemResponseDto> Items { get; set; } = new List<InvoiceItemResponseDto>();
21+
public string CustomerName { get; set; }
22+
public string CustomerEmail { get; set; }
23+
public string CustomerPhone { get; set; }
24+
public string CustomerAddress { get; set; }
25+
public string BillingAddress { get; set; }
26+
public decimal? Subtotal { get; set; }
27+
public decimal? TaxRate { get; set; }
28+
public decimal? TaxAmount { get; set; }
29+
public decimal? DiscountAmount { get; set; }
30+
public string PaymentTerms { get; set; }
2131
public DateTime Created { get; set; }
2232
public string CreatedById { get; set; }
2333
public UserDto CreatedBy { get; set; }
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
namespace AdminHubApi.Dtos.InvoiceItem;
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace AdminHubApi.Dtos.InvoiceItem;
24

35
public class CreateInvoiceItemDto
46
{
5-
public string Description { get; set; }
6-
public decimal Quantity { get; set; }
7-
public decimal UnitPrice { get; set; }
8-
public int? ProductId { get; set; }
7+
[Required] public string Description { get; set; }
8+
[Required] public decimal Quantity { get; set; }
9+
[Required] public decimal UnitPrice { get; set; }
10+
[Required] public decimal TotalPrice { get; set; }
11+
public Guid? ProductId { get; set; }
912
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
namespace AdminHubApi.Dtos.InvoiceItem;
22

3-
public class InvoiceItemDto
3+
public class InvoiceItemResponseDto
44
{
5-
public int Id { get; set; }
5+
public Guid Id { get; set; }
66
public string Description { get; set; }
77
public decimal Quantity { get; set; }
88
public decimal UnitPrice { get; set; }
99
public decimal TotalPrice { get; set; }
10-
public int? ProductId { get; set; }
11-
public string ProductName { get; set; }
10+
public Guid? ProductId { get; set; }
11+
public DateTime Created { get; set; }
12+
public DateTime Modified { get; set; }
1213
}

Entities/Invoice/Invoice.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,37 @@ public class Invoice
2828

2929
public decimal PaidAmount { get; set; }
3030

31-
[Required] public InvoiceStatus Status { get; set; } // Draft, Sent, Paid, Overdue, Cancelled
31+
[Required] public InvoiceStatus Status { get; set; }
3232

3333
public string Notes { get; set; }
3434

35-
// Foreign key for related order
36-
public Guid OrderId { get; set; }
35+
// Make OrderId optional - invoices can be standalone or linked to orders
36+
public Guid? OrderId { get; set; } // Changed to nullable
3737

3838
[ForeignKey("OrderId")] public Order Order { get; set; }
3939

40-
// Foreign key for customer/user
40+
// Customer information (since invoices can be standalone)
41+
public string CustomerName { get; set; }
42+
public string CustomerEmail { get; set; }
43+
public string CustomerPhone { get; set; }
44+
public string CustomerAddress { get; set; }
45+
public string BillingAddress { get; set; }
46+
47+
// Payment and pricing details
48+
public decimal? Subtotal { get; set; }
49+
public decimal? TaxRate { get; set; }
50+
public decimal? TaxAmount { get; set; }
51+
public decimal? DiscountAmount { get; set; }
52+
public string PaymentTerms { get; set; }
53+
54+
// Foreign key for customer/user (also make optional)
4155
public string UserId { get; set; }
4256

4357
[ForeignKey("UserId")] public ApplicationUser User { get; set; }
4458

4559
// Collection of invoice items
4660
public ICollection<InvoiceItem> Items { get; set; } = new List<InvoiceItem>();
61+
4762
public DateTime Created { get; set; }
4863
public string CreatedById { get; set; }
4964
public ApplicationUser CreatedBy { get; set; }

Migrations/ApplicationDbContextModelSnapshot.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,30 @@ protected override void BuildModel(ModelBuilder modelBuilder)
119119
.ValueGeneratedOnAdd()
120120
.HasColumnType("uuid");
121121

122+
b.Property<string>("BillingAddress")
123+
.HasColumnType("text");
124+
122125
b.Property<DateTime>("Created")
123126
.HasColumnType("timestamp with time zone");
124127

125128
b.Property<string>("CreatedById")
126129
.HasColumnType("text");
127130

131+
b.Property<string>("CustomerAddress")
132+
.HasColumnType("text");
133+
134+
b.Property<string>("CustomerEmail")
135+
.HasColumnType("text");
136+
137+
b.Property<string>("CustomerName")
138+
.HasColumnType("text");
139+
140+
b.Property<string>("CustomerPhone")
141+
.HasColumnType("text");
142+
143+
b.Property<decimal?>("DiscountAmount")
144+
.HasColumnType("numeric");
145+
128146
b.Property<DateTime>("DueDate")
129147
.HasColumnType("timestamp with time zone");
130148

@@ -144,15 +162,27 @@ protected override void BuildModel(ModelBuilder modelBuilder)
144162
b.Property<string>("Notes")
145163
.HasColumnType("text");
146164

147-
b.Property<Guid>("OrderId")
165+
b.Property<Guid?>("OrderId")
148166
.HasColumnType("uuid");
149167

150168
b.Property<decimal>("PaidAmount")
151169
.HasColumnType("numeric");
152170

171+
b.Property<string>("PaymentTerms")
172+
.HasColumnType("text");
173+
153174
b.Property<int>("Status")
154175
.HasColumnType("integer");
155176

177+
b.Property<decimal?>("Subtotal")
178+
.HasColumnType("numeric");
179+
180+
b.Property<decimal?>("TaxAmount")
181+
.HasColumnType("numeric");
182+
183+
b.Property<decimal?>("TaxRate")
184+
.HasColumnType("numeric");
185+
156186
b.Property<decimal>("TotalAmount")
157187
.HasColumnType("numeric");
158188

@@ -602,9 +632,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
602632

603633
b.HasOne("AdminHubApi.Entities.Order", "Order")
604634
.WithMany()
605-
.HasForeignKey("OrderId")
606-
.OnDelete(DeleteBehavior.Cascade)
607-
.IsRequired();
635+
.HasForeignKey("OrderId");
608636

609637
b.HasOne("AdminHubApi.Entities.ApplicationUser", "User")
610638
.WithMany()

Services/InvoiceService.cs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using AdminHubApi.Dtos.ApiResponse;
22
using AdminHubApi.Dtos.Invoice;
3+
using AdminHubApi.Dtos.InvoiceItem;
34
using AdminHubApi.Dtos.UserManagement;
45
using AdminHubApi.Entities.Invoice;
56
using AdminHubApi.Interfaces;
@@ -21,7 +22,6 @@ public async Task<ApiResponse<IEnumerable<InvoiceResponseDto>>> GetAllAsync()
2122
{
2223
try
2324
{
24-
2525
var invoices = await _invoiceRepository.GetAllAsync();
2626

2727
return new ApiResponse<IEnumerable<InvoiceResponseDto>>
@@ -103,9 +103,9 @@ public async Task<ApiResponse<InvoiceResponseDto>> UpdateAsync(InvoiceResponseDt
103103
{
104104
var existingInvoice = await _invoiceRepository.GetByIdAsync(invoiceResponse.Id);
105105

106-
if (existingInvoice == null)
106+
if (existingInvoice == null)
107107
throw new KeyNotFoundException($"Invoice with id: {invoiceResponse.Id} was not found");
108-
108+
109109
existingInvoice.InvoiceNumber = invoiceResponse.InvoiceNumber;
110110
existingInvoice.IssueDate = invoiceResponse.IssueDate;
111111
existingInvoice.OrderId = invoiceResponse.OrderId;
@@ -116,7 +116,7 @@ public async Task<ApiResponse<InvoiceResponseDto>> UpdateAsync(InvoiceResponseDt
116116
existingInvoice.ModifiedById = invoiceResponse.ModifiedById;
117117

118118
await _invoiceRepository.UpdateAsync(existingInvoice);
119-
119+
120120
return new ApiResponse<InvoiceResponseDto>
121121
{
122122
Succeeded = true,
@@ -130,11 +130,11 @@ public async Task<ApiResponse<bool>> DeleteAsync(Guid id)
130130
{
131131
var product = await _invoiceRepository.GetByIdAsync(id);
132132

133-
if (product == null)
133+
if (product == null)
134134
throw new KeyNotFoundException($"Invoice with id: {id} was not found");
135135

136136
await _invoiceRepository.DeleteAsync(id);
137-
137+
138138
return new ApiResponse<bool>
139139
{
140140
Succeeded = true,
@@ -152,12 +152,28 @@ private static InvoiceResponseDto MapToResponseDto(Invoice invoice)
152152
InvoiceNumber = invoice.InvoiceNumber,
153153
IssueDate = invoice.IssueDate,
154154
DueDate = invoice.DueDate,
155-
OrderId = invoice.OrderId,
155+
OrderId = invoice.OrderId, // Now both are nullable
156156
PaidAmount = invoice.PaidAmount,
157+
TotalAmount = invoice.TotalAmount,
157158
Notes = invoice.Notes,
158159
Status = invoice.Status,
160+
161+
// Customer fields
162+
CustomerName = invoice.CustomerName,
163+
CustomerEmail = invoice.CustomerEmail,
164+
CustomerPhone = invoice.CustomerPhone,
165+
CustomerAddress = invoice.CustomerAddress,
166+
BillingAddress = invoice.BillingAddress,
167+
168+
// Pricing fields
169+
Subtotal = invoice.Subtotal,
170+
TaxRate = invoice.TaxRate,
171+
TaxAmount = invoice.TaxAmount,
172+
DiscountAmount = invoice.DiscountAmount,
173+
PaymentTerms = invoice.PaymentTerms,
174+
159175
Created = invoice.Created,
160-
Modified = invoice.Modified,
176+
CreatedById = invoice.CreatedById,
161177
CreatedBy = invoice.CreatedBy != null
162178
? new UserDto
163179
{
@@ -172,6 +188,8 @@ private static InvoiceResponseDto MapToResponseDto(Invoice invoice)
172188
LockoutEnd = invoice.CreatedBy.LockoutEnd
173189
}
174190
: null,
191+
Modified = invoice.Modified,
192+
ModifiedById = invoice.ModifiedById,
175193
ModifiedBy = invoice.ModifiedBy != null
176194
? new UserDto
177195
{
@@ -185,7 +203,20 @@ private static InvoiceResponseDto MapToResponseDto(Invoice invoice)
185203
LockoutEnabled = invoice.ModifiedBy.LockoutEnabled,
186204
LockoutEnd = invoice.ModifiedBy.LockoutEnd
187205
}
188-
: null
206+
: null,
207+
208+
// Map invoice items
209+
Items = invoice.Items?.Select(item => new InvoiceItemResponseDto
210+
{
211+
Id = item.Id,
212+
Description = item.Description,
213+
Quantity = item.Quantity,
214+
UnitPrice = item.UnitPrice,
215+
TotalPrice = item.TotalPrice,
216+
ProductId = item.ProductId,
217+
Created = item.Created,
218+
Modified = item.Modified
219+
}).ToList() ?? new List<InvoiceItemResponseDto>()
189220
};
190221
}
191222
}

0 commit comments

Comments
 (0)