initial commit

This commit is contained in:
2026-07-21 14:28:43 +07:00
commit 01107db6fd
995 changed files with 75124 additions and 0 deletions
@@ -0,0 +1,55 @@
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class CreateBillRequest
{
public DateTime? BillDate { get; set; }
public Data.Enums.BillStatus BillStatus { get; set; }
public string? Description { get; set; }
public string? PurchaseOrderId { get; set; }
}
public class CreateBillResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
}
public record CreateBillCommand(CreateBillRequest Data) : IRequest<CreateBillResponse>;
public class CreateBillHandler : IRequestHandler<CreateBillCommand, CreateBillResponse>
{
private readonly AppDbContext _context;
public CreateBillHandler(AppDbContext context) => _context = context;
public async Task<CreateBillResponse> Handle(CreateBillCommand request, CancellationToken cancellationToken)
{
var entityName = nameof(Data.Entities.Bill);
var autoNo = await _context.GenerateAutoNumberAsync(
entityName: entityName,
prefixTemplate: $"BILL/{{Year}}/{{Month}}/",
ct: cancellationToken
);
var entity = new Data.Entities.Bill
{
AutoNumber = autoNo,
BillDate = request.Data.BillDate,
BillStatus = request.Data.BillStatus,
Description = request.Data.Description,
PurchaseOrderId = request.Data.PurchaseOrderId
};
_context.Bill.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateBillResponse
{
Id = entity.Id,
AutoNumber = entity.AutoNumber
};
}
}
@@ -0,0 +1,12 @@
using FluentValidation;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class CreateBillValidator : AbstractValidator<CreateBillRequest>
{
public CreateBillValidator()
{
RuleFor(x => x.BillDate).NotEmpty().WithMessage("Bill Date is required");
RuleFor(x => x.PurchaseOrderId).NotEmpty().WithMessage("Purchase Order is required");
}
}
@@ -0,0 +1,25 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public record DeleteBillByIdCommand(string Id) : IRequest<bool>;
public class DeleteBillByIdHandler : IRequestHandler<DeleteBillByIdCommand, bool>
{
private readonly AppDbContext _context;
public DeleteBillByIdHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteBillByIdCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Bill
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
if (entity == null) return false;
_context.Bill.Remove(entity);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,102 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class BillPurchaseOrderItemResponse
{
public string? Id { get; set; }
public string? ProductId { get; set; }
public string? ProductName { get; set; }
public string? Summary { get; set; }
public decimal? UnitPrice { get; set; }
public double? Quantity { get; set; }
public decimal? Total { get; set; }
}
public class GetBillByIdResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public DateTime? BillDate { get; set; }
public Data.Enums.BillStatus BillStatus { get; set; }
public string? Description { get; set; }
public string? PurchaseOrderId { get; set; }
public string? PurchaseOrderAutoNumber { get; set; }
public string? VendorName { get; set; }
public string? VendorStreet { get; set; }
public string? VendorCity { get; set; }
public string? CompanyName { get; set; }
public string? CompanyStreet { get; set; }
public string? CompanyCity { get; set; }
public string? CurrencySymbol { get; set; }
public decimal? BeforeTaxAmount { get; set; }
public decimal? TaxAmount { get; set; }
public decimal? AfterTaxAmount { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
public List<BillPurchaseOrderItemResponse> Items { get; set; } = new();
}
public record GetBillByIdQuery(string Id) : IRequest<GetBillByIdResponse?>;
public class GetBillByIdHandler : IRequestHandler<GetBillByIdQuery, GetBillByIdResponse?>
{
private readonly AppDbContext _context;
public GetBillByIdHandler(AppDbContext context) => _context = context;
public async Task<GetBillByIdResponse?> Handle(GetBillByIdQuery request, CancellationToken cancellationToken)
{
var company = await _context.Company
.AsNoTracking()
.Include(x => x.Currency)
.OrderByDescending(x => x.IsDefault)
.FirstOrDefaultAsync(cancellationToken);
return await _context.Bill
.AsNoTracking()
.Include(x => x.PurchaseOrder)
.ThenInclude(po => po!.Vendor)
.Include(x => x.PurchaseOrder)
.ThenInclude(po => po!.PurchaseOrderItemList)
.ThenInclude(i => i.Product)
.Where(x => x.Id == request.Id)
.Select(x => new GetBillByIdResponse
{
Id = x.Id,
AutoNumber = x.AutoNumber,
BillDate = x.BillDate,
BillStatus = x.BillStatus,
Description = x.Description,
PurchaseOrderId = x.PurchaseOrderId,
PurchaseOrderAutoNumber = x.PurchaseOrder!.AutoNumber,
VendorName = x.PurchaseOrder!.Vendor!.Name,
VendorStreet = x.PurchaseOrder!.Vendor!.Street,
VendorCity = x.PurchaseOrder!.Vendor!.City,
CompanyName = company != null ? company.Name : string.Empty,
CompanyStreet = company != null ? company.StreetAddress : string.Empty,
CompanyCity = company != null ? company.City : string.Empty,
CurrencySymbol = company != null && company.Currency != null ? company.Currency.Symbol : "IDR",
BeforeTaxAmount = x.PurchaseOrder!.BeforeTaxAmount,
TaxAmount = x.PurchaseOrder!.TaxAmount,
AfterTaxAmount = x.PurchaseOrder!.AfterTaxAmount,
CreatedAt = x.CreatedAt,
CreatedBy = x.CreatedBy,
UpdatedAt = x.UpdatedAt,
UpdatedBy = x.UpdatedBy,
Items = x.PurchaseOrder!.PurchaseOrderItemList.Select(i => new BillPurchaseOrderItemResponse
{
Id = i.Id,
ProductId = i.ProductId,
ProductName = i.Product!.Name,
Summary = i.Summary,
UnitPrice = i.UnitPrice,
Quantity = i.Quantity,
Total = i.Total
}).ToList()
}).FirstOrDefaultAsync(cancellationToken);
}
}
@@ -0,0 +1,43 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class GetBillListResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public DateTime? BillDate { get; set; }
public string? PurchaseOrderAutoNumber { get; set; }
public string? VendorName { get; set; }
public decimal? AfterTaxAmount { get; set; }
public Data.Enums.BillStatus BillStatus { get; set; }
}
public record GetBillListQuery() : IRequest<List<GetBillListResponse>>;
public class GetBillListHandler : IRequestHandler<GetBillListQuery, List<GetBillListResponse>>
{
private readonly AppDbContext _context;
public GetBillListHandler(AppDbContext context) => _context = context;
public async Task<List<GetBillListResponse>> Handle(GetBillListQuery request, CancellationToken cancellationToken)
{
return await _context.Bill
.AsNoTracking()
.Include(x => x.PurchaseOrder)
.ThenInclude(po => po!.Vendor)
.OrderByDescending(x => x.CreatedAt)
.Select(x => new GetBillListResponse
{
Id = x.Id,
AutoNumber = x.AutoNumber,
BillDate = x.BillDate,
PurchaseOrderAutoNumber = x.PurchaseOrder!.AutoNumber,
VendorName = x.PurchaseOrder!.Vendor!.Name,
AfterTaxAmount = x.PurchaseOrder!.AfterTaxAmount,
BillStatus = x.BillStatus
}).ToListAsync(cancellationToken);
}
}
@@ -0,0 +1,54 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Indotalent.Data.Enums;
using Indotalent.ConfigBackEnd.Extensions;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class BillLookupResponse
{
public List<LookupItem> PurchaseOrders { get; set; } = new();
public List<LookupStatusItem> Statuses { get; set; } = new();
}
public class LookupItem
{
public string? Id { get; set; }
public string? Name { get; set; }
}
public class LookupStatusItem
{
public int Value { get; set; }
public string? Name { get; set; }
}
public record GetBillLookupQuery() : IRequest<BillLookupResponse>;
public class GetBillLookupHandler : IRequestHandler<GetBillLookupQuery, BillLookupResponse>
{
private readonly AppDbContext _context;
public GetBillLookupHandler(AppDbContext context) => _context = context;
public async Task<BillLookupResponse> Handle(GetBillLookupQuery request, CancellationToken cancellationToken)
{
var response = new BillLookupResponse();
response.PurchaseOrders = await _context.PurchaseOrder.AsNoTracking()
.Where(x => x.OrderStatus == PurchaseOrderStatus.Confirmed)
.OrderByDescending(x => x.CreatedAt)
.Select(x => new LookupItem { Id = x.Id, Name = x.AutoNumber })
.ToListAsync(cancellationToken);
response.Statuses = Enum.GetValues(typeof(BillStatus))
.Cast<BillStatus>()
.Select(x => new LookupStatusItem
{
Value = (int)x,
Name = x.GetDescription()
}).ToList();
return response;
}
}
@@ -0,0 +1,39 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public record GetPurchaseOrderDetailForBillQuery(string PurchaseOrderId) : IRequest<GetBillByIdResponse?>;
public class GetPurchaseOrderDetailForBillHandler : IRequestHandler<GetPurchaseOrderDetailForBillQuery, GetBillByIdResponse?>
{
private readonly AppDbContext _context;
public GetPurchaseOrderDetailForBillHandler(AppDbContext context) => _context = context;
public async Task<GetBillByIdResponse?> Handle(GetPurchaseOrderDetailForBillQuery request, CancellationToken cancellationToken)
{
return await _context.PurchaseOrder
.AsNoTracking()
.Include(x => x.PurchaseOrderItemList)
.ThenInclude(i => i.Product)
.Where(x => x.Id == request.PurchaseOrderId)
.Select(x => new GetBillByIdResponse
{
PurchaseOrderId = x.Id,
BeforeTaxAmount = x.BeforeTaxAmount,
TaxAmount = x.TaxAmount,
AfterTaxAmount = x.AfterTaxAmount,
Items = x.PurchaseOrderItemList.Select(i => new BillPurchaseOrderItemResponse
{
Id = i.Id,
ProductId = i.ProductId,
ProductName = i.Product!.Name,
Summary = i.Summary,
UnitPrice = i.UnitPrice,
Quantity = i.Quantity,
Total = i.Total
}).ToList()
}).FirstOrDefaultAsync(cancellationToken);
}
}
@@ -0,0 +1,44 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class UpdateBillRequest
{
public string? Id { get; set; }
public DateTime? BillDate { get; set; }
public Data.Enums.BillStatus BillStatus { get; set; }
public string? AutoNumber { get; set; }
public string? Description { get; set; }
public string? PurchaseOrderId { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
public record UpdateBillCommand(UpdateBillRequest Data) : IRequest<bool>;
public class UpdateBillHandler : IRequestHandler<UpdateBillCommand, bool>
{
private readonly AppDbContext _context;
public UpdateBillHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(UpdateBillCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Bill
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null) return false;
entity.BillDate = request.Data.BillDate;
entity.BillStatus = request.Data.BillStatus;
entity.Description = request.Data.Description;
entity.PurchaseOrderId = request.Data.PurchaseOrderId;
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,12 @@
using FluentValidation;
namespace Indotalent.Features.Purchase.Bill.Cqrs;
public class UpdateBillValidator : AbstractValidator<UpdateBillRequest>
{
public UpdateBillValidator()
{
RuleFor(x => x.Id).NotEmpty();
RuleFor(x => x.PurchaseOrderId).NotEmpty().WithMessage("Purchase Order is required");
}
}