[Add] MasterData Services.

This commit is contained in:
Thanakarn Klangkasame
2025-11-26 10:29:56 +07:00
parent d4ab1cb592
commit 1e636aa3d5
205 changed files with 7814 additions and 69 deletions

View File

@@ -0,0 +1,60 @@
using System.Data;
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen;
using AMREZ.EOP.Abstractions.Infrastructures.Common;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Allergen;
public sealed class CreateAllergenUseCase : ICreateAllergenUseCase
{
private readonly IAllergenRepository _repo;
private readonly IUnitOfWork _uow;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public CreateAllergenUseCase(IAllergenRepository repo, IUnitOfWork uow, ITenantResolver tr, IHttpContextAccessor http)
{
_repo = repo; _uow = uow; _tenantResolver = tr; _http = http;
}
public async Task<AllergenResponse> ExecuteAsync(AllergenCreateRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
await _uow.BeginAsync(tc, IsolationLevel.ReadCommitted, ct);
try
{
var tid = Guid.Parse(tc.Id);
if (req.Scope == "tenant" && await _repo.CodeExistsAsync(tid, req.Code, ct))
throw new InvalidOperationException($"Brand code '{req.Code}' already exists.");
var entity = new Domain.Entities.MasterData.Allergen()
{
TenantId = tid,
Scope = string.IsNullOrWhiteSpace(req.Scope) ? "tenant" : req.Scope,
Code = req.Code.Trim(),
Name = req.Name.Trim(),
NameI18n = req.NameI18n,
Meta = req.Meta,
IsActive = req.IsActive,
IsSystem = false,
OverridesGlobalId = req.OverridesGlobalId
};
await _repo.AddAsync(entity, ct);
await _uow.CommitAsync(ct);
return new AllergenResponse(entity.Id, entity.Scope, entity.Code, entity.Name, entity.NameI18n, entity.OverridesGlobalId, entity.IsActive, entity.IsSystem, entity.Meta);
}
catch
{
await _uow.RollbackAsync(ct);
throw;
}
}
}

View File

@@ -0,0 +1,30 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Allergen;
public class DeleteAllergenUseCase : IDeleteAllergenUseCase
{
private readonly IAllergenRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public DeleteAllergenUseCase(IAllergenRepository repo, ITenantResolver tr, IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tr;
_http = http;
}
public async Task<bool> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var n = await _repo.SoftDeleteAsync(id, tid, ct);
return n > 0;
}
}

View File

@@ -0,0 +1,17 @@
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen;
namespace AMREZ.EOP.Application.UseCases.MasterData.Allergen;
public class GetAllergenUseCase : IGetAllergenUseCase
{
private readonly IAllergenRepository _repo;
public GetAllergenUseCase(IAllergenRepository repo) => _repo = repo;
public async Task<AllergenResponse?> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var e = await _repo.GetAsync(id, ct);
return e is null ? null : new AllergenResponse(e.Id, e.Scope, e.Code, e.Name, e.NameI18n, e.OverridesGlobalId, e.IsActive, e.IsSystem, e.Meta);
}
}

View File

@@ -0,0 +1,36 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Allergen;
public class ListAllergenUseCase : IListAllergenUseCase
{
private readonly IAllergenRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ListAllergenUseCase(IAllergenRepository repo, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tenantResolver;
_http = http;
}
public async Task<PagedResponse<AllergenResponse>> ExecuteAsync(AllergenListRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var page = await _repo.SearchEffectiveAsync(tid, req, ct);
var items = page.Items.Select(Map).ToList();
return new PagedResponse<AllergenResponse>(page.Page, page.PageSize, page.Total, items);
}
private static AllergenResponse Map(Domain.Entities.MasterData.Allergen e) =>
new(e.Id, e.Scope, e.Code, e.Name, e.NameI18n, e.OverridesGlobalId, e.IsActive, e.IsSystem, e.Meta);
}

View File

@@ -0,0 +1,40 @@
using System.Data;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen;
using AMREZ.EOP.Abstractions.Infrastructures.Common;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen;
namespace AMREZ.EOP.Application.UseCases.MasterData.Allergen;
public class UpdateAllergenUseCase : IUpdateAllergenUseCase
{
private readonly IAllergenRepository _repo;
private readonly IUnitOfWork _uow;
public UpdateAllergenUseCase(IAllergenRepository repo, IUnitOfWork uow) { _repo = repo; _uow = uow; }
public async Task<AllergenResponse?> ExecuteAsync(Guid id, AllergenUpdateRequest req, CancellationToken ct = default)
{
await _uow.BeginAsync(null, IsolationLevel.ReadCommitted, ct);
try
{
var e = await _repo.GetAsync(id, ct);
if (e is null) { await _uow.RollbackAsync(ct); return null; }
e.Name = req.Name.Trim();
e.NameI18n = req.NameI18n;
e.Meta = req.Meta;
e.IsActive = req.IsActive;
await _repo.UpdateAsync(e, ct);
await _uow.CommitAsync(ct);
return new AllergenResponse(e.Id, e.Scope, e.Code, e.Name, e.NameI18n, e.OverridesGlobalId, e.IsActive, e.IsSystem, e.Meta);
}
catch
{
await _uow.RollbackAsync(ct);
throw;
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Data;
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand;
using AMREZ.EOP.Abstractions.Infrastructures.Common;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Brand;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Brand;
public sealed class CreateBrandUseCase : ICreateBrandUseCase
{
private readonly IBrandRepository _repo;
private readonly IUnitOfWork _uow;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public CreateBrandUseCase(IBrandRepository repo, IUnitOfWork uow, ITenantResolver tr, IHttpContextAccessor http)
{
_repo = repo; _uow = uow; _tenantResolver = tr; _http = http;
}
public async Task<BrandResponse> ExecuteAsync(BrandCreateRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
await _uow.BeginAsync(tc, IsolationLevel.ReadCommitted, ct);
try
{
var tid = Guid.Parse(tc.Id);
if (req.Scope == "tenant" && await _repo.CodeExistsAsync(tid, req.Code, ct))
throw new InvalidOperationException($"Brand code '{req.Code}' already exists.");
var entity = new Domain.Entities.MasterData.Brand
{
TenantId = tid,
Scope = string.IsNullOrWhiteSpace(req.Scope) ? "tenant" : req.Scope,
Code = req.Code.Trim(),
Name = req.Name.Trim(),
NameI18n = req.NameI18n,
Meta = req.Meta,
IsActive = req.IsActive,
IsSystem = false,
OverridesGlobalId = req.OverridesGlobalId
};
await _repo.AddAsync(entity, ct);
await _uow.CommitAsync(ct);
return new BrandResponse(entity.Id, entity.Scope, entity.Code, entity.Name, entity.NameI18n, entity.OverridesGlobalId, entity.IsActive, entity.IsSystem, entity.Meta);
}
catch
{
await _uow.RollbackAsync(ct);
throw;
}
}
}

View File

@@ -0,0 +1,30 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Brand;
public sealed class DeleteBrandUseCase : IDeleteBrandUseCase
{
private readonly IBrandRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public DeleteBrandUseCase(IBrandRepository repo, ITenantResolver tr, IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tr;
_http = http;
}
public async Task<bool> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var n = await _repo.SoftDeleteAsync(id, tid, ct);
return n > 0;
}
}

View File

@@ -0,0 +1,17 @@
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Brand;
namespace AMREZ.EOP.Application.UseCases.MasterData.Brand;
public sealed class GetBrandUseCase : IGetBrandUseCase
{
private readonly IBrandRepository _repo;
public GetBrandUseCase(IBrandRepository repo) => _repo = repo;
public async Task<BrandResponse?> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var e = await _repo.GetAsync(id, ct);
return e is null ? null : new BrandResponse(e.Id, e.Scope, e.Code, e.Name, e.NameI18n, e.OverridesGlobalId, e.IsActive, e.IsSystem, e.Meta);
}
}

View File

@@ -0,0 +1,36 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Brand;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Brand;
public sealed class ListBrandsUseCase : IListBrandsUseCase
{
private readonly IBrandRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ListBrandsUseCase(IBrandRepository repo, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tenantResolver;
_http = http;
}
public async Task<PagedResponse<BrandResponse>> ExecuteAsync(BrandListRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var page = await _repo.SearchEffectiveAsync(tid, req, ct);
var items = page.Items.Select(Map).ToList();
return new PagedResponse<BrandResponse>(page.Page, page.PageSize, page.Total, items);
}
private static BrandResponse Map(Domain.Entities.MasterData.Brand e) =>
new(e.Id, e.Scope, e.Code, e.Name, e.NameI18n, e.OverridesGlobalId, e.IsActive, e.IsSystem, e.Meta);
}

View File

@@ -0,0 +1,40 @@
using System.Data;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand;
using AMREZ.EOP.Abstractions.Infrastructures.Common;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Brand;
namespace AMREZ.EOP.Application.UseCases.MasterData.Brand;
public sealed class UpdateBrandUseCase : IUpdateBrandUseCase
{
private readonly IBrandRepository _repo;
private readonly IUnitOfWork _uow;
public UpdateBrandUseCase(IBrandRepository repo, IUnitOfWork uow) { _repo = repo; _uow = uow; }
public async Task<BrandResponse?> ExecuteAsync(Guid id, BrandUpdateRequest req, CancellationToken ct = default)
{
await _uow.BeginAsync(null, IsolationLevel.ReadCommitted, ct);
try
{
var e = await _repo.GetAsync(id, ct);
if (e is null) { await _uow.RollbackAsync(ct); return null; }
e.Name = req.Name.Trim();
e.NameI18n = req.NameI18n;
e.Meta = req.Meta;
e.IsActive = req.IsActive;
await _repo.UpdateAsync(e, ct);
await _uow.CommitAsync(ct);
return new BrandResponse(e.Id, e.Scope, e.Code, e.Name, e.NameI18n, e.OverridesGlobalId, e.IsActive, e.IsSystem, e.Meta);
}
catch
{
await _uow.RollbackAsync(ct);
throw;
}
}
}

View File

@@ -0,0 +1,37 @@
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.District;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.District;
namespace AMREZ.EOP.Application.UseCases.MasterData.District;
public sealed class GetDistrictUseCase : IGetDistrictUseCase
{
private readonly IDistrictRepository _repo;
public GetDistrictUseCase(IDistrictRepository repo)
{
_repo = repo;
}
public async Task<DistrictResponse?> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var e = await _repo.GetAsync(id, ct);
if (e is null) return null;
return new DistrictResponse(
e.Id,
e.Scope,
e.Code,
e.Name,
e.NameI18n,
e.OverridesGlobalId,
e.IsActive,
e.IsSystem,
e.Meta,
e.Code,
e.ProvinceId,
e.Province.Name,
e.Province.Code
);
}
}

View File

@@ -0,0 +1,54 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.District;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.District;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.District;
public sealed class ListDistrictsUseCase : IListDistrictsUseCase
{
private readonly IDistrictRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ListDistrictsUseCase(
IDistrictRepository repo,
ITenantResolver tenantResolver,
IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tenantResolver;
_http = http;
}
public async Task<PagedResponse<DistrictResponse>> ExecuteAsync(DistrictListRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var page = await _repo.SearchEffectiveAsync(tid, req, ct);
var items = page.Items.Select(Map).ToList();
return new PagedResponse<DistrictResponse>(page.Page, page.PageSize, page.Total, items);
}
private static DistrictResponse Map(Domain.Entities.MasterData.District e) =>
new(
e.Id,
e.Scope,
e.Code,
e.Name,
e.NameI18n,
e.OverridesGlobalId,
e.IsActive,
e.IsSystem,
e.Meta,
e.Code,
e.ProvinceId,
e.Province.Name,
e.Province.Code
);
}

View File

@@ -0,0 +1,34 @@
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Province;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Province;
namespace AMREZ.EOP.Application.UseCases.MasterData.Province;
public sealed class GetProvinceUseCase : IGetProvinceUseCase
{
private readonly IProvinceRepository _repo;
public GetProvinceUseCase(IProvinceRepository repo)
{
_repo = repo;
}
public async Task<ProvinceResponse?> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var e = await _repo.GetAsync(id, ct);
if (e is null) return null;
return new ProvinceResponse(
e.Id,
e.Scope,
e.Code,
e.Name,
e.NameI18n,
e.OverridesGlobalId,
e.IsActive,
e.IsSystem,
e.Meta,
e.Code
);
}
}

View File

@@ -0,0 +1,51 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Province;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Province;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Province;
public sealed class ListProvincesUseCase : IListProvincesUseCase
{
private readonly IProvinceRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ListProvincesUseCase(
IProvinceRepository repo,
ITenantResolver tenantResolver,
IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tenantResolver;
_http = http;
}
public async Task<PagedResponse<ProvinceResponse>> ExecuteAsync(ProvinceListRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var page = await _repo.SearchEffectiveAsync(tid, req, ct);
var items = page.Items.Select(Map).ToList();
return new PagedResponse<ProvinceResponse>(page.Page, page.PageSize, page.Total, items);
}
private static ProvinceResponse Map(Domain.Entities.MasterData.Province e) =>
new(
e.Id,
e.Scope,
e.Code,
e.Name,
e.NameI18n,
e.OverridesGlobalId,
e.IsActive,
e.IsSystem,
e.Meta,
e.Code
);
}

View File

@@ -0,0 +1,41 @@
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Subdistrict;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict;
namespace AMREZ.EOP.Application.UseCases.MasterData.Subdistricts;
public sealed class GetSubdistrictUseCase : IGetSubdistrictUseCase
{
private readonly ISubdistrictRepository _repo;
public GetSubdistrictUseCase(ISubdistrictRepository repo)
{
_repo = repo;
}
public async Task<SubdistrictResponse?> ExecuteAsync(Guid id, CancellationToken ct = default)
{
var e = await _repo.GetAsync(id, ct);
if (e is null) return null;
return new SubdistrictResponse(
e.Id,
e.Scope,
e.Code,
e.Name,
e.NameI18n,
e.OverridesGlobalId,
e.IsActive,
e.IsSystem,
e.Meta,
e.Code,
e.Postcode,
e.DistrictId,
e.District.Name,
e.District.Code,
e.District.ProvinceId,
e.District.Province.Name,
e.District.Province.Code
);
}
}

View File

@@ -0,0 +1,58 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Subdistrict;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict;
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Application.UseCases.MasterData.Subdistricts;
public sealed class ListSubdistrictsUseCase : IListSubdistrictsUseCase
{
private readonly ISubdistrictRepository _repo;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ListSubdistrictsUseCase(
ISubdistrictRepository repo,
ITenantResolver tenantResolver,
IHttpContextAccessor http)
{
_repo = repo;
_tenantResolver = tenantResolver;
_http = http;
}
public async Task<PagedResponse<SubdistrictResponse>> ExecuteAsync(SubdistrictListRequest req, CancellationToken ct = default)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
var tid = Guid.Parse(tc.Id);
var page = await _repo.SearchEffectiveAsync(tid, req, ct);
var items = page.Items.Select(Map).ToList();
return new PagedResponse<SubdistrictResponse>(page.Page, page.PageSize, page.Total, items);
}
private static SubdistrictResponse Map(Domain.Entities.MasterData.Subdistrict e) =>
new(
e.Id,
e.Scope,
e.Code,
e.Name,
e.NameI18n,
e.OverridesGlobalId,
e.IsActive,
e.IsSystem,
e.Meta,
e.Code,
e.Postcode,
e.DistrictId,
e.District.Name,
e.District.Code,
e.District.ProvinceId,
e.District.Province.Name,
e.District.Province.Code
);
}