10.
Implementation A partner, not just software HexDive Technologies' local team handles setup, data migration, training and ongoing support — in Sinhala, Tamil and English. [confirm] 1 Discovery Map your network, products and processes. 2 Configuration Set up depots, pricing, schemes, routes and users. 3 Migration & training Move your data and train every team. 4 Go-live & support Dedicated account manager and local support desk.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.ExpenseVendor;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/expense-vendors")]
|
||||
public class ExpenseVendorController(ExpenseVendorService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("expense.view")]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] string? search,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50) =>
|
||||
Ok(await service.ListAsync(search, page, pageSize));
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("expense.manage_categories")]
|
||||
public async Task<IActionResult> Create(CreateExpenseVendorRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("expense.manage_categories")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateExpenseVendorRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Distribution.Features.ExpenseVendor;
|
||||
|
||||
public record ExpenseVendorResponse(Guid Id, string Name, string? ContactInfo, bool IsActive);
|
||||
|
||||
public record CreateExpenseVendorRequest(string Name, string? ContactInfo);
|
||||
|
||||
public record UpdateExpenseVendorRequest(string Name, string? ContactInfo, bool IsActive);
|
||||
@@ -0,0 +1,31 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.ExpenseVendor;
|
||||
|
||||
public class ExpenseVendorRepository(AppDBContext context) : IExpenseVendorRepository
|
||||
{
|
||||
public async Task<Models.ExpenseVendor?> GetByIdAsync(Guid id) =>
|
||||
await context.ExpenseVendors.FirstOrDefaultAsync(v => v.Id == id);
|
||||
|
||||
public async Task<List<Models.ExpenseVendor>> ListAsync(string? search, int page, int pageSize) =>
|
||||
await context.ExpenseVendors
|
||||
.Where(v => v.IsActive && (search == null || v.Name.Contains(search)))
|
||||
.OrderBy(v => v.Name)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<int> CountAsync(string? search) =>
|
||||
await context.ExpenseVendors
|
||||
.CountAsync(v => v.IsActive && (search == null || v.Name.Contains(search)));
|
||||
|
||||
public async Task<bool> NameExistsAsync(string name, Guid? excludeId) =>
|
||||
await context.ExpenseVendors.AnyAsync(v => v.Name == name && (excludeId == null || v.Id != excludeId));
|
||||
|
||||
public async Task AddAsync(Models.ExpenseVendor entity) =>
|
||||
await context.ExpenseVendors.AddAsync(entity);
|
||||
|
||||
public void Update(Models.ExpenseVendor entity) =>
|
||||
context.ExpenseVendors.Update(entity);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.ExpenseVendor;
|
||||
|
||||
public record PagedExpenseVendorsResponse(List<ExpenseVendorResponse> Items, int Page, int PageSize, int Total);
|
||||
|
||||
public class ExpenseVendorService(IExpenseVendorRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private static ExpenseVendorResponse Map(Models.ExpenseVendor v) =>
|
||||
new(v.Id, v.Name, v.ContactInfo, v.IsActive);
|
||||
|
||||
public async Task<PagedExpenseVendorsResponse> ListAsync(string? search, int page, int pageSize)
|
||||
{
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize < 1 || pageSize > 200) pageSize = 50;
|
||||
|
||||
search = string.IsNullOrWhiteSpace(search) ? null : search.Trim();
|
||||
var items = await repo.ListAsync(search, page, pageSize);
|
||||
var total = await repo.CountAsync(search);
|
||||
return new PagedExpenseVendorsResponse(items.Select(Map).ToList(), page, pageSize, total);
|
||||
}
|
||||
|
||||
public async Task<ExpenseVendorResponse> CreateAsync(CreateExpenseVendorRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var name = (request.Name ?? "").Trim();
|
||||
if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Name is required");
|
||||
if (await repo.NameExistsAsync(name, null))
|
||||
throw new InvalidOperationException("A vendor with this name already exists");
|
||||
|
||||
var entity = new Models.ExpenseVendor { Name = name, ContactInfo = request.ContactInfo };
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { await repo.AddAsync(entity); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(entity);
|
||||
}
|
||||
|
||||
public async Task<ExpenseVendorResponse?> UpdateAsync(Guid id, UpdateExpenseVendorRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var entity = await repo.GetByIdAsync(id);
|
||||
if (entity is null) return null;
|
||||
|
||||
var name = (request.Name ?? "").Trim();
|
||||
if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Name is required");
|
||||
if (await repo.NameExistsAsync(name, id))
|
||||
throw new InvalidOperationException("A vendor with this name already exists");
|
||||
|
||||
entity.Name = name;
|
||||
entity.ContactInfo = request.ContactInfo;
|
||||
entity.IsActive = request.IsActive;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(entity); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Distribution.Features.ExpenseVendor;
|
||||
|
||||
public interface IExpenseVendorRepository
|
||||
{
|
||||
Task<Models.ExpenseVendor?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.ExpenseVendor>> ListAsync(string? search, int page, int pageSize);
|
||||
Task<int> CountAsync(string? search);
|
||||
Task<bool> NameExistsAsync(string name, Guid? excludeId);
|
||||
Task AddAsync(Models.ExpenseVendor entity);
|
||||
void Update(Models.ExpenseVendor entity);
|
||||
}
|
||||
Reference in New Issue
Block a user