initial commit

This commit is contained in:
2026-07-21 13:59:38 +07:00
commit c40792266a
1321 changed files with 100465 additions and 0 deletions
@@ -0,0 +1,51 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class CreateBudgetRequest
{
public string? CampaignId { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public DateTime? BudgetDate { get; set; }
public decimal? Amount { get; set; }
public BudgetStatus Status { get; set; }
}
public class CreateBudgetResponse
{
public string? Id { get; set; }
public string? Title { get; set; }
}
public record CreateBudgetCommand(CreateBudgetRequest Data) : IRequest<CreateBudgetResponse>;
public class CreateBudgetHandler : IRequestHandler<CreateBudgetCommand, CreateBudgetResponse>
{
private readonly AppDbContext _context;
public CreateBudgetHandler(AppDbContext context) => _context = context;
public async Task<CreateBudgetResponse> Handle(CreateBudgetCommand request, CancellationToken cancellationToken)
{
var entity = new Data.Entities.Budget
{
CampaignId = request.Data.CampaignId,
Title = request.Data.Title,
Description = request.Data.Description,
BudgetDate = request.Data.BudgetDate,
Amount = request.Data.Amount,
Status = request.Data.Status
};
_context.Budget.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateBudgetResponse
{
Id = entity.Id,
Title = entity.Title
};
}
}
@@ -0,0 +1,53 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class CreateCampaignRequest
{
public string? Title { get; set; }
public string? Description { get; set; }
public decimal? TargetRevenueAmount { get; set; }
public DateTime? CampaignDateStart { get; set; }
public DateTime? CampaignDateFinish { get; set; }
public CampaignStatus Status { get; set; }
public string? SalesTeamId { get; set; }
}
public class CreateCampaignResponse
{
public string? Id { get; set; }
public string? Title { get; set; }
}
public record CreateCampaignCommand(CreateCampaignRequest Data) : IRequest<CreateCampaignResponse>;
public class CreateCampaignHandler : IRequestHandler<CreateCampaignCommand, CreateCampaignResponse>
{
private readonly AppDbContext _context;
public CreateCampaignHandler(AppDbContext context) => _context = context;
public async Task<CreateCampaignResponse> Handle(CreateCampaignCommand request, CancellationToken cancellationToken)
{
var entity = new Data.Entities.Campaign
{
Title = request.Data.Title,
Description = request.Data.Description,
TargetRevenueAmount = request.Data.TargetRevenueAmount,
CampaignDateStart = request.Data.CampaignDateStart,
CampaignDateFinish = request.Data.CampaignDateFinish,
Status = request.Data.Status,
SalesTeamId = request.Data.SalesTeamId
};
_context.Campaign.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateCampaignResponse
{
Id = entity.Id,
Title = entity.Title
};
}
}
@@ -0,0 +1,13 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class CreateCampaignValidator : AbstractValidator<CreateCampaignRequest>
{
public CreateCampaignValidator()
{
RuleFor(x => x.Title).NotEmpty().MaximumLength(GlobalConsts.StringLengthShort);
RuleFor(x => x.SalesTeamId).NotEmpty().WithMessage("Sales Team is required");
}
}
@@ -0,0 +1,51 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class CreateExpenseRequest
{
public string? CampaignId { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public DateTime? ExpenseDate { get; set; }
public decimal? Amount { get; set; }
public ExpenseStatus Status { get; set; }
}
public class CreateExpenseResponse
{
public string? Id { get; set; }
public string? Title { get; set; }
}
public record CreateExpenseCommand(CreateExpenseRequest Data) : IRequest<CreateExpenseResponse>;
public class CreateExpenseHandler : IRequestHandler<CreateExpenseCommand, CreateExpenseResponse>
{
private readonly AppDbContext _context;
public CreateExpenseHandler(AppDbContext context) => _context = context;
public async Task<CreateExpenseResponse> Handle(CreateExpenseCommand request, CancellationToken cancellationToken)
{
var entity = new Data.Entities.Expense
{
CampaignId = request.Data.CampaignId,
Title = request.Data.Title,
Description = request.Data.Description,
ExpenseDate = request.Data.ExpenseDate,
Amount = request.Data.Amount,
Status = request.Data.Status
};
_context.Expense.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return new CreateExpenseResponse
{
Id = entity.Id,
Title = entity.Title
};
}
}
@@ -0,0 +1,25 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public record DeleteBudgetCommand(string Id) : IRequest<bool>;
public class DeleteBudgetHandler : IRequestHandler<DeleteBudgetCommand, bool>
{
private readonly AppDbContext _context;
public DeleteBudgetHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteBudgetCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Budget
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
if (entity == null) return false;
_context.Budget.Remove(entity);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,25 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public record DeleteCampaignByIdCommand(string Id) : IRequest<bool>;
public class DeleteCampaignByIdHandler : IRequestHandler<DeleteCampaignByIdCommand, bool>
{
private readonly AppDbContext _context;
public DeleteCampaignByIdHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteCampaignByIdCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Campaign
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
if (entity == null) return false;
_context.Campaign.Remove(entity);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,25 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public record DeleteExpenseCommand(string Id) : IRequest<bool>;
public class DeleteExpenseHandler : IRequestHandler<DeleteExpenseCommand, bool>
{
private readonly AppDbContext _context;
public DeleteExpenseHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(DeleteExpenseCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Expense
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
if (entity == null) return false;
_context.Expense.Remove(entity);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,100 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class CampaignBudgetResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public string? Title { get; set; }
public decimal? Amount { get; set; }
public DateTime? BudgetDate { get; set; }
public BudgetStatus Status { get; set; }
public string? Description { get; set; }
}
public class CampaignExpenseResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public string? Title { get; set; }
public decimal? Amount { get; set; }
public DateTime? ExpenseDate { get; set; }
public ExpenseStatus Status { get; set; }
public string? Description { get; set; }
}
public class GetCampaignByIdResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public decimal? TargetRevenueAmount { get; set; }
public DateTime? CampaignDateStart { get; set; }
public DateTime? CampaignDateFinish { get; set; }
public CampaignStatus Status { get; set; }
public string? SalesTeamId { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
public List<CampaignBudgetResponse> Budgets { get; set; } = new();
public List<CampaignExpenseResponse> Expenses { get; set; } = new();
}
public record GetCampaignByIdQuery(string Id) : IRequest<GetCampaignByIdResponse?>;
public class GetCampaignByIdHandler : IRequestHandler<GetCampaignByIdQuery, GetCampaignByIdResponse?>
{
private readonly AppDbContext _context;
public GetCampaignByIdHandler(AppDbContext context) => _context = context;
public async Task<GetCampaignByIdResponse?> Handle(GetCampaignByIdQuery request, CancellationToken cancellationToken)
{
return await _context.Campaign
.AsNoTracking()
.Include(x => x.BudgetList)
.Include(x => x.ExpenseList)
.Where(x => x.Id == request.Id)
.Select(x => new GetCampaignByIdResponse
{
Id = x.Id,
AutoNumber = x.AutoNumber,
Title = x.Title,
Description = x.Description,
TargetRevenueAmount = x.TargetRevenueAmount,
CampaignDateStart = x.CampaignDateStart,
CampaignDateFinish = x.CampaignDateFinish,
Status = x.Status,
SalesTeamId = x.SalesTeamId,
CreatedAt = x.CreatedAt,
CreatedBy = x.CreatedBy,
UpdatedAt = x.UpdatedAt,
UpdatedBy = x.UpdatedBy,
Budgets = x.BudgetList.Select(b => new CampaignBudgetResponse
{
Id = b.Id,
AutoNumber = b.AutoNumber,
Title = b.Title,
Amount = b.Amount,
BudgetDate = b.BudgetDate,
Status = b.Status,
Description = b.Description
}).ToList(),
Expenses = x.ExpenseList.Select(e => new CampaignExpenseResponse
{
Id = e.Id,
AutoNumber = e.AutoNumber,
Title = e.Title,
Amount = e.Amount,
ExpenseDate = e.ExpenseDate,
Status = e.Status,
Description = e.Description
}).ToList()
}).FirstOrDefaultAsync(cancellationToken);
}
}
@@ -0,0 +1,51 @@
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class GetCampaignListResponse
{
public string? Id { get; set; }
public string? AutoNumber { get; set; }
public string? Title { get; set; }
public string? SalesTeamName { get; set; }
public DateTime? CampaignDateStart { get; set; }
public string? Status { get; set; }
public decimal? TargetRevenueAmount { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
public record GetCampaignListQuery() : IRequest<List<GetCampaignListResponse>>;
public class GetCampaignListHandler : IRequestHandler<GetCampaignListQuery, List<GetCampaignListResponse>>
{
private readonly AppDbContext _context;
public GetCampaignListHandler(AppDbContext context) => _context = context;
public async Task<List<GetCampaignListResponse>> Handle(GetCampaignListQuery request, CancellationToken cancellationToken)
{
return await _context.Campaign
.AsNoTracking()
.Include(x => x.SalesTeam)
.OrderByDescending(x => x.CreatedAt)
.Select(x => new GetCampaignListResponse
{
Id = x.Id,
AutoNumber = x.AutoNumber,
Title = x.Title,
SalesTeamName = x.SalesTeam != null ? x.SalesTeam.Name : string.Empty,
CampaignDateStart = x.CampaignDateStart,
Status = x.Status.GetDescription(),
TargetRevenueAmount = x.TargetRevenueAmount,
CreatedAt = x.CreatedAt,
CreatedBy = x.CreatedBy,
UpdatedAt = x.UpdatedAt,
UpdatedBy = x.UpdatedBy
}).ToListAsync(cancellationToken);
}
}
@@ -0,0 +1,58 @@
using Indotalent.ConfigBackEnd.Extensions;
using Indotalent.Data.Enums;
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class CampaignLookupResponse
{
public List<LookupItem> SalesTeams { get; set; } = new();
public List<LookupItem> Statuses { get; set; } = new();
public List<LookupItem> BudgetStatuses { get; set; } = new();
public List<LookupItem> ExpenseStatuses { get; set; } = new();
}
public class LookupItem
{
public string? Id { get; set; }
public string? Name { get; set; }
public int Value { get; set; }
}
public record GetCampaignLookupQuery() : IRequest<CampaignLookupResponse>;
public class GetCampaignLookupHandler : IRequestHandler<GetCampaignLookupQuery, CampaignLookupResponse>
{
private readonly AppDbContext _context;
public GetCampaignLookupHandler(AppDbContext context) => _context = context;
public async Task<CampaignLookupResponse> Handle(GetCampaignLookupQuery request, CancellationToken cancellationToken)
{
var response = new CampaignLookupResponse();
response.SalesTeams = await _context.SalesTeam
.AsNoTracking()
.OrderBy(x => x.Name)
.Select(x => new LookupItem { Id = x.Id, Name = x.Name })
.ToListAsync(cancellationToken);
response.Statuses = Enum.GetValues(typeof(CampaignStatus))
.Cast<CampaignStatus>()
.Select(e => new LookupItem { Value = (int)e, Name = e.GetDescription() })
.ToList();
response.BudgetStatuses = Enum.GetValues(typeof(BudgetStatus))
.Cast<BudgetStatus>()
.Select(e => new LookupItem { Value = (int)e, Name = e.GetDescription() })
.ToList();
response.ExpenseStatuses = Enum.GetValues(typeof(ExpenseStatus))
.Cast<ExpenseStatus>()
.Select(e => new LookupItem { Value = (int)e, Name = e.GetDescription() })
.ToList();
return response;
}
}
@@ -0,0 +1,55 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class UpdateBudgetRequest
{
public string? Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public DateTime? BudgetDate { get; set; }
public decimal? Amount { get; set; }
public BudgetStatus Status { get; set; }
}
public class UpdateBudgetResponse
{
public string? Id { get; set; }
public bool Success { get; set; }
}
public record UpdateBudgetCommand(UpdateBudgetRequest Data) : IRequest<UpdateBudgetResponse>;
public class UpdateBudgetHandler : IRequestHandler<UpdateBudgetCommand, UpdateBudgetResponse>
{
private readonly AppDbContext _context;
public UpdateBudgetHandler(AppDbContext context) => _context = context;
public async Task<UpdateBudgetResponse> Handle(UpdateBudgetCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Budget
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
return new UpdateBudgetResponse { Id = request.Data.Id, Success = false };
}
entity.Title = request.Data.Title;
entity.Description = request.Data.Description;
entity.BudgetDate = request.Data.BudgetDate;
entity.Amount = request.Data.Amount;
entity.Status = request.Data.Status;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateBudgetResponse
{
Id = entity.Id,
Success = true
};
}
}
@@ -0,0 +1,63 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class UpdateCampaignRequest
{
public string? Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public decimal? TargetRevenueAmount { get; set; }
public DateTime? CampaignDateStart { get; set; }
public DateTime? CampaignDateFinish { get; set; }
public CampaignStatus Status { get; set; }
public string? SalesTeamId { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
public class UpdateCampaignResponse
{
public string? Id { get; set; }
public bool Success { get; set; }
}
public record UpdateCampaignCommand(UpdateCampaignRequest Data) : IRequest<UpdateCampaignResponse>;
public class UpdateCampaignHandler : IRequestHandler<UpdateCampaignCommand, UpdateCampaignResponse>
{
private readonly AppDbContext _context;
public UpdateCampaignHandler(AppDbContext context) => _context = context;
public async Task<UpdateCampaignResponse> Handle(UpdateCampaignCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Campaign
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
return new UpdateCampaignResponse { Id = request.Data.Id, Success = false };
}
entity.Title = request.Data.Title;
entity.Description = request.Data.Description;
entity.TargetRevenueAmount = request.Data.TargetRevenueAmount;
entity.CampaignDateStart = request.Data.CampaignDateStart;
entity.CampaignDateFinish = request.Data.CampaignDateFinish;
entity.Status = request.Data.Status;
entity.SalesTeamId = request.Data.SalesTeamId;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateCampaignResponse
{
Id = entity.Id,
Success = true
};
}
}
@@ -0,0 +1,14 @@
using FluentValidation;
using Indotalent.Shared.Consts;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class UpdateCampaignValidator : AbstractValidator<UpdateCampaignRequest>
{
public UpdateCampaignValidator()
{
RuleFor(x => x.Id).NotEmpty();
RuleFor(x => x.Title).NotEmpty().MaximumLength(GlobalConsts.StringLengthShort);
RuleFor(x => x.SalesTeamId).NotEmpty().WithMessage("Sales Team is required");
}
}
@@ -0,0 +1,55 @@
using Indotalent.Infrastructure.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Indotalent.Data.Enums;
namespace Indotalent.Features.Pipeline.Campaign.Cqrs;
public class UpdateExpenseRequest
{
public string? Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public DateTime? ExpenseDate { get; set; }
public decimal? Amount { get; set; }
public ExpenseStatus Status { get; set; }
}
public class UpdateExpenseResponse
{
public string? Id { get; set; }
public bool Success { get; set; }
}
public record UpdateExpenseCommand(UpdateExpenseRequest Data) : IRequest<UpdateExpenseResponse>;
public class UpdateExpenseHandler : IRequestHandler<UpdateExpenseCommand, UpdateExpenseResponse>
{
private readonly AppDbContext _context;
public UpdateExpenseHandler(AppDbContext context) => _context = context;
public async Task<UpdateExpenseResponse> Handle(UpdateExpenseCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Expense
.FirstOrDefaultAsync(x => x.Id == request.Data.Id, cancellationToken);
if (entity == null)
{
return new UpdateExpenseResponse { Id = request.Data.Id, Success = false };
}
entity.Title = request.Data.Title;
entity.Description = request.Data.Description;
entity.ExpenseDate = request.Data.ExpenseDate;
entity.Amount = request.Data.Amount;
entity.Status = request.Data.Status;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateExpenseResponse
{
Id = entity.Id,
Success = true
};
}
}