7c63f6c9ba
Add int? PayeeId to CreateExpenseRequest (UpdateExpenseRequest inherits) and to ExpenseListItemDto (so it round-trips to the form). Set e.PayeeId unconditionally in CreateAsync and UpdateAsync so 1099 attribution is independent of VendorPayment vs StaffReimbursement type. Map PayeeId in both DTO projections: the paged-list lambda and GetByIdAsync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
3.6 KiB
C#
83 lines
3.6 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
namespace ROLAC.API.DTOs.Expense;
|
|
|
|
public class ExpenseLineItemDto
|
|
{
|
|
public int Id { get; set; }
|
|
public int CategoryGroupId { get; set; }
|
|
public string CategoryGroupName { get; set; } = "";
|
|
public int SubCategoryId { get; set; }
|
|
public string SubCategoryName { get; set; } = "";
|
|
public string? FunctionalClass { get; set; }
|
|
public decimal Amount { get; set; }
|
|
public string? Description { get; set; }
|
|
}
|
|
|
|
public class ExpenseListItemDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string Type { get; set; } = "";
|
|
public string Status { get; set; } = "";
|
|
public decimal Amount { get; set; } // header total = sum of line amounts
|
|
public string Description { get; set; } = "";
|
|
public int MinistryId { get; set; }
|
|
public string MinistryName { get; set; } = "";
|
|
public int LineCount { get; set; }
|
|
public string PrimaryCategoryName { get; set; } = ""; // first line's category (list hint; full breakdown via detail)
|
|
public string? VendorName { get; set; }
|
|
public int? MemberId { get; set; }
|
|
public string? MemberName { get; set; } // legal name "FirstName_en LastName_en" (used on the printed check)
|
|
public string? MemberNickName { get; set; } // "NickName LastName_en"; null when the member has no distinct nickname
|
|
public string ExpenseDate { get; set; } = ""; // yyyy-MM-dd
|
|
public bool HasReceipt { get; set; }
|
|
public string? CheckNumber { get; set; }
|
|
// Review outcome — surfaced on the list so the Status column can show "Approved/Rejected by X · date".
|
|
public string? ReviewedByName { get; set; } // resolved Member full name, email fallback
|
|
public DateTimeOffset? ReviewedAt { get; set; }
|
|
public string? ReviewNotes { get; set; } // reject reason (or approval note)
|
|
public int? PayeeId { get; set; }
|
|
}
|
|
|
|
public class ExpenseDto : ExpenseListItemDto
|
|
{
|
|
public string? Notes { get; set; }
|
|
public string? SubmittedBy { get; set; }
|
|
public DateTimeOffset? SubmittedAt { get; set; }
|
|
public DateTimeOffset? PaidAt { get; set; }
|
|
public List<ExpenseLineItemDto> Lines { get; set; } = new();
|
|
}
|
|
|
|
public class ExpenseLineInput
|
|
{
|
|
[Required] public int CategoryGroupId { get; set; }
|
|
[Required] public int SubCategoryId { get; set; }
|
|
[Range(0.01, 9_999_999)] public decimal Amount { get; set; }
|
|
[MaxLength(20)] public string? FunctionalClass { get; set; }
|
|
[MaxLength(500)] public string? Description { get; set; }
|
|
}
|
|
|
|
public class CreateExpenseRequest
|
|
{
|
|
[Required] public string Type { get; set; } = "StaffReimbursement"; // VendorPayment|StaffReimbursement
|
|
[Required] public int MinistryId { get; set; }
|
|
[Required, MinLength(1)] public List<ExpenseLineInput> Lines { get; set; } = new();
|
|
[Required, MaxLength(500)] public string Description { get; set; } = "";
|
|
[MaxLength(200)] public string? VendorName { get; set; }
|
|
public int? MemberId { get; set; } // ignored for self-service (server uses caller)
|
|
[MaxLength(50)] public string? CheckNumber { get; set; }
|
|
[Required] public DateOnly ExpenseDate { get; set; }
|
|
public string? Notes { get; set; }
|
|
public int? PayeeId { get; set; }
|
|
}
|
|
public class UpdateExpenseRequest : CreateExpenseRequest { }
|
|
|
|
public class RejectExpenseRequest
|
|
{
|
|
[MaxLength(500)] public string? ReviewNotes { get; set; }
|
|
}
|
|
public class PayExpenseRequest
|
|
{
|
|
[MaxLength(50)] public string? CheckNumber { get; set; }
|
|
public DateOnly? PaidAt { get; set; }
|
|
}
|