diff --git a/AMREZ.EOP.API/AMREZ.EOP.API.csproj b/AMREZ.EOP.API/AMREZ.EOP.API.csproj index be74a5e..efff37b 100644 --- a/AMREZ.EOP.API/AMREZ.EOP.API.csproj +++ b/AMREZ.EOP.API/AMREZ.EOP.API.csproj @@ -7,6 +7,7 @@ + diff --git a/AMREZ.EOP.API/Controllers/DataController.cs b/AMREZ.EOP.API/Controllers/DataController.cs new file mode 100644 index 0000000..2ebad3f --- /dev/null +++ b/AMREZ.EOP.API/Controllers/DataController.cs @@ -0,0 +1,187 @@ +using AMREZ.EOP.Abstractions.Applications.UseCases.ImportData.Location; +using AMREZ.EOP.Contracts.DTOs.ImportData.Location; +using AMREZ.EOP.Domain.Shared.Data; +using ClosedXML.Excel; +using Microsoft.AspNetCore.Mvc; + +namespace AMREZ.EOP.API.Controllers; + +public class AddressTemplateRow +{ + public string CustomerName { get; set; } = ""; // ชื่อ + public string Phone { get; set; } = ""; // เบอร์โทร + public string Address { get; set; } = ""; // ที่อยู่ (เลขที่/หมู่/ซอย/ถนน ฯลฯ) + public string Subdistrict { get; set; } = ""; // ตำบล + public string District { get; set; } = ""; // อำเภอ + public string Province { get; set; } = ""; // จังหวัด + public string PostalCode { get; set; } = ""; // รหัสไปรษณีย์ + + public string? ProductCode { get; set; } // เผื่อใช้ต่อ + public string? ProductName { get; set; } + public int? Quantity { get; set; } +} + +[ApiController] +[Route("api/[controller]")] +public class DataController : ControllerBase +{ + private readonly ILocationImportUseCase _locationImportUseCase; + + public DataController(ILocationImportUseCase locationImportUseCase) + { + _locationImportUseCase = locationImportUseCase; + } + + // เซ็ต Fixed สินค้า 3 รายการตามตัวอย่าง + private static readonly (string Code, string Name, decimal Price)[] FixedProducts = + { + ("900-000-01-01", "LAVIESTE (DIETARY SUPPLEMENT PRODUCT) KATHY LABZ BRAND", 1590m), + ("900-000-01-02", "KARISTA (DIETARY SUPPLEMENT PRODUCT) KATHY LABZ BRAND", 1590m), + ("890-001-00-01", "KATHY LABZ AUTO STIRRING", 390m) + }; + + [HttpPost("address-parse")] + public async Task ThaiParseAddress([FromForm(Name = "Image")] IFormFile? Image) + { + if (Image == null || Image.Length == 0) + return BadRequest("กรุณาอัปโหลดไฟล์ Excel (.xlsx) ที่เป็น 'ข้อมูลจริง ครั้งที่1'"); + + using var stream = new MemoryStream(); + await Image.CopyToAsync(stream); + stream.Position = 0; + + using var wb = new XLWorkbook(stream); + var ws = wb.Worksheets.First(); + + // row1 = header => "username", "Address" + var lastRow = ws.LastRowUsed().RowNumber(); + + var rows = new List(); + + for (int row = 2; row <= lastRow; row++) + { + var username = ws.Cell(row, 1).GetString().Trim(); // "username" + var rawAddress = ws.Cell(row, 2).GetString().Trim(); // "Address" + + if (string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(rawAddress)) + continue; + + var parsed = ThaiAddressParser.Parse(rawAddress); + + rows.Add(new AddressTemplateRow + { + CustomerName = string.IsNullOrWhiteSpace(parsed.Name) + ? username + : parsed.Name, + Phone = parsed.Phone, + Address = parsed.AddressMain, + Subdistrict = parsed.Subdistrict, + District = parsed.District, + Province = parsed.Province, + PostalCode = parsed.PostalCode + }); + } + + // ================== สร้างไฟล์ Excel ตาม Template ================== + using var outWb = new XLWorkbook(); + + var outWs = outWb.Worksheets.Add("template"); + + // Header แถวที่ 1 ให้ตรงกับไฟล์ตัวอย่าง + outWs.Cell(1, 1).Value = "สถานะออเดอร์"; + outWs.Cell(1, 2).Value = "สถานะการชำระเงิน"; + outWs.Cell(1, 3).Value = "รหัสพนักงานขาย"; + outWs.Cell(1, 4).Value = "รหัสช่องทางการขาย"; + outWs.Cell(1, 5).Value = "ช่องทางการชำระเงิน"; + outWs.Cell(1, 6).Value = "กำหนดส่งสินค้า"; + outWs.Cell(1, 7).Value = "Order No"; + outWs.Cell(1, 8).Value = "ชื่อขนส่ง"; + outWs.Cell(1, 9).Value = "ชื่อ"; + outWs.Cell(1, 10).Value = "เบอร์โทร"; + outWs.Cell(1, 11).Value = "E-mail"; + outWs.Cell(1, 12).Value = "Line"; + outWs.Cell(1, 13).Value = "Facebook"; + outWs.Cell(1, 14).Value = "IG"; + outWs.Cell(1, 15).Value = "หมายเหตุ"; + outWs.Cell(1, 16).Value = "ที่อยู่"; + outWs.Cell(1, 17).Value = "ตำบล"; + outWs.Cell(1, 18).Value = "อำเภอ"; + outWs.Cell(1, 19).Value = "จังหวัด"; + outWs.Cell(1, 20).Value = "รหัสไปรษณีย์"; + outWs.Cell(1, 21).Value = "รหัสสินค้า/รหัสโปรโมชั่น (ตายตัว"; + outWs.Cell(1, 22).Value = "ชื่อสินค้า/ชื่อโปรโมชั่น (ตายตัว)"; + outWs.Cell(1, 23).Value = "ราคา/ชิ้น"; + outWs.Cell(1, 24).Value = "จำนวน"; + outWs.Cell(1, 25).Value = "ส่วนลด"; + outWs.Cell(1, 26).Value = "ราคารวมสินค้า"; + outWs.Cell(1, 27).Value = "ค่าขนส่ง"; + outWs.Cell(1, 28).Value = "ส่วนลดท้ายบิล"; + outWs.Cell(1, 29).Value = "ราคารวมออเดอร์"; + outWs.Cell(1, 30).Value = "Tracking No."; + outWs.Cell(1, 31).Value = "วันอนุมัติ"; + + var outRow = 2; + + foreach (var r in rows) + { + // 1 ลูกค้า → 3 แถวสินค้า fix + foreach (var p in FixedProducts) + { + // ข้อมูลลูกค้า + outWs.Cell(outRow, 9).Value = r.CustomerName ?? ""; + outWs.Cell(outRow, 10).Value = r.Phone ?? ""; + outWs.Cell(outRow, 16).Value = r.Address ?? ""; + outWs.Cell(outRow, 17).Value = r.Subdistrict ?? ""; + outWs.Cell(outRow, 18).Value = r.District ?? ""; + outWs.Cell(outRow, 19).Value = r.Province ?? ""; + outWs.Cell(outRow, 20).Value = r.PostalCode ?? ""; + + // สินค้า + outWs.Cell(outRow, 21).Value = p.Code; // รหัสสินค้า + outWs.Cell(outRow, 22).Value = p.Name; // ชื่อสินค้า hard-coded + outWs.Cell(outRow, 23).Value = p.Price; // ราคา/ชิ้น + outWs.Cell(outRow, 24).Value = 1; // จำนวน = 1 + + // ส่วนลด/รวม/ค่าขนส่ง/ส่วนลดท้ายบิล/ราคารวมออเดอร์ ปล่อยว่าง + // คอลัมน์ 25–31 ไม่เซ็ตอะไร + + outRow++; + } + } + + outWs.Columns(1, 31).AdjustToContents(); + + using var outStream = new MemoryStream(); + outWb.SaveAs(outStream); + outStream.Position = 0; + + var fileName = $"import_SO_template_{DateTime.Now:yyyyMMddHHmmss}.xlsx"; + + return File( + outStream.ToArray(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + fileName + ); + } + + /// + /// Import ตารางจังหวัด / อำเภอ / ตำบล จากไฟล์ Excel (กรมการปกครอง) + /// + /// + /// ส่งไฟล์ .xlsx ผ่าน multipart/form-data, field name = "file" + /// + [HttpPost("import-th-location")] + [Consumes("multipart/form-data")] + [ProducesResponseType(typeof(LocationImportResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ImportLocations( + [FromForm(Name = "file")] IFormFile? file, + CancellationToken ct) + { + if (file == null || file.Length == 0) + return BadRequest("กรุณาอัปโหลดไฟล์ Excel (.xlsx) รหัสจังหวัด/อำเภอ/ตำบล"); + + var result = await _locationImportUseCase.ExecuteAsync(file, ct); + return Ok(result); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.API/Controllers/MasterData/BrandsController.cs b/AMREZ.EOP.API/Controllers/MasterData/BrandsController.cs new file mode 100644 index 0000000..646f89f --- /dev/null +++ b/AMREZ.EOP.API/Controllers/MasterData/BrandsController.cs @@ -0,0 +1,59 @@ +using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; +using Microsoft.AspNetCore.Mvc; + +namespace AMREZ.EOP.API.Controllers.MasterData; + +[ApiController] +[Route("api/master/brands")] +public sealed class BrandsController : ControllerBase +{ + private readonly IListBrandsUseCase _list; + private readonly IGetBrandUseCase _get; + private readonly ICreateBrandUseCase _create; + private readonly IUpdateBrandUseCase _update; + private readonly IDeleteBrandUseCase _delete; + + public BrandsController( + IListBrandsUseCase list, + IGetBrandUseCase get, + ICreateBrandUseCase create, + IUpdateBrandUseCase update, + IDeleteBrandUseCase delete) + { + _list = list; _get = get; _create = create; _update = update; _delete = delete; + } + + [HttpGet] + public async Task>> List([FromQuery] BrandListRequest query, CancellationToken ct) + => Ok(await _list.ExecuteAsync(query, ct)); + + [HttpGet("{id:guid}")] + public async Task Get(Guid id, CancellationToken ct) + { + var res = await _get.ExecuteAsync(id, ct); + return res is null ? NotFound() : Ok(res); + } + + [HttpPost] + public async Task Create([FromBody] BrandCreateRequest body, CancellationToken ct) + { + var res = await _create.ExecuteAsync(body, ct); + return CreatedAtAction(nameof(Get), new { id = res.Id }, res); + } + + [HttpPut("{id:guid}")] + public async Task Update(Guid id, [FromBody] BrandUpdateRequest body, CancellationToken ct) + { + var res = await _update.ExecuteAsync(id, body, ct); + return res is null ? NotFound() : Ok(res); + } + + [HttpDelete("{id:guid}")] + public async Task Delete(Guid id, CancellationToken ct) + { + var ok = await _delete.ExecuteAsync(id, ct); + return ok ? NoContent() : NotFound(); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.API/Controllers/SCBController.cs b/AMREZ.EOP.API/Controllers/SCBController.cs index 0aae1e6..a020bf8 100644 --- a/AMREZ.EOP.API/Controllers/SCBController.cs +++ b/AMREZ.EOP.API/Controllers/SCBController.cs @@ -28,13 +28,13 @@ public class SCBController : ControllerBase { if (Image is null || Image.Length == 0) return BadRequest(new { message = "Missing slip image" }); - + var res = await _verifyFromImage.ExecuteAsync(new VerifyFromImageRequest { Image = Image, OverrideSendingBank = OverrideSendingBank }, ct); - + if (res is null) return NotFound(new { message = "Cannot extract transRef or verification failed" }); return Ok(res); } diff --git a/AMREZ.EOP.Abstractions/AMREZ.EOP.Abstractions.csproj b/AMREZ.EOP.Abstractions/AMREZ.EOP.Abstractions.csproj index 0d66818..f1b38eb 100644 --- a/AMREZ.EOP.Abstractions/AMREZ.EOP.Abstractions.csproj +++ b/AMREZ.EOP.Abstractions/AMREZ.EOP.Abstractions.csproj @@ -16,7 +16,4 @@ - - - diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/ImportData/Location/ILocationImportUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/ImportData/Location/ILocationImportUseCase.cs new file mode 100644 index 0000000..3596857 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/ImportData/Location/ILocationImportUseCase.cs @@ -0,0 +1,9 @@ +using AMREZ.EOP.Contracts.DTOs.ImportData.Location; +using Microsoft.AspNetCore.Http; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.ImportData.Location; + +public interface ILocationImportUseCase +{ + Task ExecuteAsync(IFormFile file, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/ICreateAllergenUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/ICreateAllergenUseCase.cs new file mode 100644 index 0000000..586744f --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/ICreateAllergenUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen; + +public interface ICreateAllergenUseCase +{ + Task ExecuteAsync(AllergenCreateRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IDeleteAllergenUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IDeleteAllergenUseCase.cs new file mode 100644 index 0000000..85cac03 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IDeleteAllergenUseCase.cs @@ -0,0 +1,6 @@ +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen; + +public interface IDeleteAllergenUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IGetAllergenUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IGetAllergenUseCase.cs new file mode 100644 index 0000000..73842be --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IGetAllergenUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen; + +public interface IGetAllergenUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IListAllergenUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IListAllergenUseCase.cs new file mode 100644 index 0000000..01885e4 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IListAllergenUseCase.cs @@ -0,0 +1,9 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen; + +public interface IListAllergenUseCase +{ + Task> ExecuteAsync(AllergenListRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IUpdateAllergenUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IUpdateAllergenUseCase.cs new file mode 100644 index 0000000..18ca9d4 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Allergen/IUpdateAllergenUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Allergen; + +public interface IUpdateAllergenUseCase +{ + Task ExecuteAsync(Guid id, AllergenUpdateRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/ICreateBrandUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/ICreateBrandUseCase.cs new file mode 100644 index 0000000..6f6ce86 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/ICreateBrandUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; + +public interface ICreateBrandUseCase +{ + Task ExecuteAsync(BrandCreateRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IDeleteBrandUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IDeleteBrandUseCase.cs new file mode 100644 index 0000000..20a8fe5 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IDeleteBrandUseCase.cs @@ -0,0 +1,6 @@ +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; + +public interface IDeleteBrandUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IGetBrandUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IGetBrandUseCase.cs new file mode 100644 index 0000000..3a35e7b --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IGetBrandUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; + +public interface IGetBrandUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IListBrandsUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IListBrandsUseCase.cs new file mode 100644 index 0000000..f7bd9fe --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IListBrandsUseCase.cs @@ -0,0 +1,9 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; + +public interface IListBrandsUseCase +{ + Task> ExecuteAsync(BrandListRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IUpdateBrandUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IUpdateBrandUseCase.cs new file mode 100644 index 0000000..db28159 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Brand/IUpdateBrandUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; + +public interface IUpdateBrandUseCase +{ + Task ExecuteAsync(Guid id, BrandUpdateRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/District/IGetDistrictUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/District/IGetDistrictUseCase.cs new file mode 100644 index 0000000..66e6647 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/District/IGetDistrictUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.District; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.District; + +public interface IGetDistrictUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/District/IListDistrictsUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/District/IListDistrictsUseCase.cs new file mode 100644 index 0000000..95dce2a --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/District/IListDistrictsUseCase.cs @@ -0,0 +1,9 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.District; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.District; + +public interface IListDistrictsUseCase +{ + Task> ExecuteAsync(DistrictListRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Province/IGetProvinceUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Province/IGetProvinceUseCase.cs new file mode 100644 index 0000000..55703ea --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Province/IGetProvinceUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Province; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Province; + +public interface IGetProvinceUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Province/IListProvincesUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Province/IListProvincesUseCase.cs new file mode 100644 index 0000000..5f134f3 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Province/IListProvincesUseCase.cs @@ -0,0 +1,9 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Province; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Province; + +public interface IListProvincesUseCase +{ + Task> ExecuteAsync(ProvinceListRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Subdistrict/IGetSubdistrictUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Subdistrict/IGetSubdistrictUseCase.cs new file mode 100644 index 0000000..df470c0 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Subdistrict/IGetSubdistrictUseCase.cs @@ -0,0 +1,8 @@ +using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Subdistrict; + +public interface IGetSubdistrictUseCase +{ + Task ExecuteAsync(Guid id, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Subdistrict/IListSubdistrictsUseCase.cs b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Subdistrict/IListSubdistrictsUseCase.cs new file mode 100644 index 0000000..998c512 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Applications/UseCases/MasterData/Subdistrict/IListSubdistrictsUseCase.cs @@ -0,0 +1,9 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; + +namespace AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Subdistrict; + +public interface IListSubdistrictsUseCase +{ + Task> ExecuteAsync(SubdistrictListRequest req, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IAllergenRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IAllergenRepository.cs new file mode 100644 index 0000000..2dde78d --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IAllergenRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IAllergenRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, AllergenListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Allergen entity, CancellationToken ct = default); + Task UpdateAsync(Allergen entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IBrandRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IBrandRepository.cs new file mode 100644 index 0000000..19d8052 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IBrandRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IBrandRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, BrandListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Brand entity, CancellationToken ct = default); + Task UpdateAsync(Brand entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICategoryRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICategoryRepository.cs new file mode 100644 index 0000000..d2cc323 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICategoryRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Category; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ICategoryRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, CategoryListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Category entity, CancellationToken ct = default); + Task UpdateAsync(Category entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IComplianceStatusRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IComplianceStatusRepository.cs new file mode 100644 index 0000000..dfd37fc --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IComplianceStatusRepository.cs @@ -0,0 +1,6 @@ +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IComplianceStatusRepository +{ + +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICountryRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICountryRepository.cs new file mode 100644 index 0000000..4eb6659 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICountryRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Country; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ICountryRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, CountryListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Country entity, CancellationToken ct = default); + Task UpdateAsync(Country entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICurrencyRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICurrencyRepository.cs new file mode 100644 index 0000000..e3d1063 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ICurrencyRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Currency; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ICurrencyRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, CurrencyListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Currency entity, CancellationToken ct = default); + Task UpdateAsync(Currency entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IDistrictRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IDistrictRepository.cs new file mode 100644 index 0000000..ebab555 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IDistrictRepository.cs @@ -0,0 +1,23 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.District; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IDistrictRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + + Task> SearchEffectiveAsync( + Guid tenantId, + DistrictListRequest req, + CancellationToken ct = default); + + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + + Task AddAsync(District entity, CancellationToken ct = default); + + Task UpdateAsync(District entity, CancellationToken ct = default); + + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IDocControlStatusRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IDocControlStatusRepository.cs new file mode 100644 index 0000000..cd833b8 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IDocControlStatusRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IDocControlStatusRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, DocControlStatusListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(DocControlStatus entity, CancellationToken ct = default); + Task UpdateAsync(DocControlStatus entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IFuncTestRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IFuncTestRepository.cs new file mode 100644 index 0000000..2274e85 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IFuncTestRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IFuncTestRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, FuncTestListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(FuncTest entity, CancellationToken ct = default); + Task UpdateAsync(FuncTest entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IHazardClassRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IHazardClassRepository.cs new file mode 100644 index 0000000..26baae1 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IHazardClassRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IHazardClassRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, HazardClassListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(HazardClass entity, CancellationToken ct = default); + Task UpdateAsync(HazardClass entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ILanguageRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ILanguageRepository.cs new file mode 100644 index 0000000..a41953b --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ILanguageRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Language; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ILanguageRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, LanguageListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Language entity, CancellationToken ct = default); + Task UpdateAsync(Language entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IManufacturerRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IManufacturerRepository.cs new file mode 100644 index 0000000..cefcfdf --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IManufacturerRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IManufacturerRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, ManufacturerListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Manufacturer entity, CancellationToken ct = default); + Task UpdateAsync(Manufacturer entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IMarketRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IMarketRepository.cs new file mode 100644 index 0000000..61fb728 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IMarketRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Market; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IMarketRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, MarketListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Market entity, CancellationToken ct = default); + Task UpdateAsync(Market entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IPackingGroupRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IPackingGroupRepository.cs new file mode 100644 index 0000000..ad16eb0 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IPackingGroupRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IPackingGroupRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, PackingGroupListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(PackingGroup entity, CancellationToken ct = default); + Task UpdateAsync(PackingGroup entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IProvinceRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IProvinceRepository.cs new file mode 100644 index 0000000..cbdcbef --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IProvinceRepository.cs @@ -0,0 +1,23 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Province; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IProvinceRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + + Task> SearchEffectiveAsync( + Guid tenantId, + ProvinceListRequest req, + CancellationToken ct = default); + + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + + Task AddAsync(Province entity, CancellationToken ct = default); + + Task UpdateAsync(Province entity, CancellationToken ct = default); + + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IQaStageRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IQaStageRepository.cs new file mode 100644 index 0000000..e795ab8 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IQaStageRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.QaStage; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IQaStageRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, QaStageListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(QaStage entity, CancellationToken ct = default); + Task UpdateAsync(QaStage entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IQcStatusRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IQcStatusRepository.cs new file mode 100644 index 0000000..8ed3a59 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IQcStatusRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IQcStatusRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, QcStatusListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(QcStatus entity, CancellationToken ct = default); + Task UpdateAsync(QcStatus entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRecallClassRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRecallClassRepository.cs new file mode 100644 index 0000000..e1fe83a --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRecallClassRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IRecallClassRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, RecallClassListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(RecallClass entity, CancellationToken ct = default); + Task UpdateAsync(RecallClass entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRiskClassRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRiskClassRepository.cs new file mode 100644 index 0000000..d498fef --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRiskClassRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IRiskClassRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, RiskClassListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(RiskClass entity, CancellationToken ct = default); + Task UpdateAsync(RiskClass entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRouteRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRouteRepository.cs new file mode 100644 index 0000000..29c2093 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRouteRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Route; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IRouteRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, RouteListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Route entity, CancellationToken ct = default); + Task UpdateAsync(Route entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRxScheduleRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRxScheduleRepository.cs new file mode 100644 index 0000000..f199f41 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IRxScheduleRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IRxScheduleRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, RxScheduleListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(RxSchedule entity, CancellationToken ct = default); + Task UpdateAsync(RxSchedule entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISpeciesRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISpeciesRepository.cs new file mode 100644 index 0000000..48db984 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISpeciesRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Species; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ISpeciesRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, SpeciesListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Species entity, CancellationToken ct = default); + Task UpdateAsync(Species entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IStabilityStatusRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IStabilityStatusRepository.cs new file mode 100644 index 0000000..5aeee85 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IStabilityStatusRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IStabilityStatusRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, StabilityStatusListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(StabilityStatus entity, CancellationToken ct = default); + Task UpdateAsync(StabilityStatus entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISterilizationMethodRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISterilizationMethodRepository.cs new file mode 100644 index 0000000..52640a2 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISterilizationMethodRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ISterilizationMethodRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, SterilizationMethodListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(SterilizationMethod entity, CancellationToken ct = default); + Task UpdateAsync(SterilizationMethod entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISubdistrictRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISubdistrictRepository.cs new file mode 100644 index 0000000..a934290 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/ISubdistrictRepository.cs @@ -0,0 +1,23 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface ISubdistrictRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + + Task> SearchEffectiveAsync( + Guid tenantId, + SubdistrictListRequest req, + CancellationToken ct = default); + + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + + Task AddAsync(Subdistrict entity, CancellationToken ct = default); + + Task UpdateAsync(Subdistrict entity, CancellationToken ct = default); + + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IUomRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IUomRepository.cs new file mode 100644 index 0000000..58c2ee8 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IUomRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Uom; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IUomRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, UomListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Uom entity, CancellationToken ct = default); + Task UpdateAsync(Uom entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IVvmRepository.cs b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IVvmRepository.cs new file mode 100644 index 0000000..53e58d8 --- /dev/null +++ b/AMREZ.EOP.Abstractions/Infrastructures/Repositories/IVvmRepository.cs @@ -0,0 +1,15 @@ +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Vvm; +using AMREZ.EOP.Domain.Entities.MasterData; + +namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories; + +public interface IVvmRepository +{ + Task GetAsync(Guid id, CancellationToken ct = default); + Task> SearchEffectiveAsync(Guid tenantId, VvmListRequest req, CancellationToken ct = default); + Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default); + Task AddAsync(Vvm entity, CancellationToken ct = default); + Task UpdateAsync(Vvm entity, CancellationToken ct = default); + Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/AMREZ.EOP.Application.csproj b/AMREZ.EOP.Application/AMREZ.EOP.Application.csproj index eef6ff9..d0d9053 100644 --- a/AMREZ.EOP.Application/AMREZ.EOP.Application.csproj +++ b/AMREZ.EOP.Application/AMREZ.EOP.Application.csproj @@ -11,13 +11,11 @@ + - - - diff --git a/AMREZ.EOP.Application/UseCases/Authentications/IssueTokenPairUseCase.cs b/AMREZ.EOP.Application/UseCases/Authentications/IssueTokenPairUseCase.cs index 0497627..96225e5 100644 --- a/AMREZ.EOP.Application/UseCases/Authentications/IssueTokenPairUseCase.cs +++ b/AMREZ.EOP.Application/UseCases/Authentications/IssueTokenPairUseCase.cs @@ -31,72 +31,71 @@ public sealed class IssueTokenPairUseCase : IIssueTokenPairUseCase _http = http; } - public async Task ExecuteAsync(IssueTokenPairRequest request, CancellationToken ct = default) -{ - var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); - - var tenantId = request.TenantId; - - // ---- สร้าง/บันทึก refresh session ---- - var refreshRaw = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); - var refreshHash = Sha256(refreshRaw); - var now = DateTimeOffset.UtcNow; - - var session = await _users.CreateSessionAsync(new UserSession + public async Task ExecuteAsync(IssueTokenPairRequest request, + CancellationToken ct = default) { - Id = Guid.NewGuid(), - TenantId = tenantId, - UserId = request.UserId, - RefreshTokenHash = refreshHash, - IssuedAt = now, - ExpiresAt = now.AddDays(RefreshDays), - DeviceId = http.Request.Headers["X-Device-Id"].FirstOrDefault(), - UserAgent = http.Request.Headers["User-Agent"].FirstOrDefault(), - IpAddress = http.Connection.RemoteIpAddress?.ToString() - }, ct); + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); - // ---- เวอร์ชัน/สแตมป์ความปลอดภัย ---- - var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct); - var sstamp = await _users.GetUserSecurityStampAsync(request.UserId, ct) ?? string.Empty; + var tenantId = request.TenantId; - // ---- เตรียม claims พื้นฐาน ---- - var claims = new List - { - new("sub", request.UserId.ToString()), - new("email", request.Email), - new("tenant", request.Tenant), // tenantKey ที่ FE ใช้ - new("tenant_id", tenantId.ToString()), - new("sid", session.Id.ToString()), - new("jti", Guid.NewGuid().ToString("N")), - new("tv", tv), - new("sstamp", sstamp), - new("iat", now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) - }; + // ---- สร้าง/บันทึก refresh session ---- + var refreshRaw = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); + var refreshHash = Sha256(refreshRaw); + var now = DateTimeOffset.UtcNow; - string[] roles = request.Roles ?? Array.Empty(); - if (roles.Length == 0) - { - roles = await _users.GetRoleCodesByUserIdAsync(request.UserId, tenantId, ct); + var session = await _users.CreateSessionAsync(new UserSession + { + Id = Guid.NewGuid(), + TenantId = tenantId, + UserId = request.UserId, + RefreshTokenHash = refreshHash, + IssuedAt = now, + ExpiresAt = now.AddDays(RefreshDays), + DeviceId = http.Request.Headers["X-Device-Id"].FirstOrDefault(), + UserAgent = http.Request.Headers["User-Agent"].FirstOrDefault(), + IpAddress = http.Connection.RemoteIpAddress?.ToString() + }, ct); + + var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct); + var sstamp = await _users.GetUserSecurityStampAsync(request.UserId, ct) ?? string.Empty; + + // ---- เตรียม claims พื้นฐาน ---- + var claims = new List + { + new("sub", request.UserId.ToString()), + new("email", request.Email), + new("tenant", request.Tenant), // tenantKey ที่ FE ใช้ + new("tenant_id", tenantId.ToString()), + new("sid", session.Id.ToString()), + new("jti", Guid.NewGuid().ToString("N")), + new("tv", tv), + new("sstamp", sstamp), + new("iat", now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) + }; + + string[] roles = request.Roles ?? Array.Empty(); + if (roles.Length == 0) + { + roles = await _users.GetRoleCodesByUserIdAsync(request.UserId, tenantId, ct); + } + + foreach (var r in roles.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct(StringComparer.OrdinalIgnoreCase)) + { + claims.Add(new Claim("role", r)); + claims.Add(new Claim(ClaimTypes.Role, r)); + } + + var (access, accessExp) = _jwt.CreateAccessToken(claims); + + return new IssueTokenPairResponse + { + AccessToken = access, + AccessExpiresAt = accessExp, + RefreshToken = refreshRaw, + RefreshExpiresAt = session.ExpiresAt + }; } - foreach (var r in roles.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct(StringComparer.OrdinalIgnoreCase)) - { - claims.Add(new Claim("role", r)); - claims.Add(new Claim(ClaimTypes.Role, r)); - } - - // ---- ออก access token ---- - var (access, accessExp) = _jwt.CreateAccessToken(claims); - - return new IssueTokenPairResponse - { - AccessToken = access, - AccessExpiresAt = accessExp, - RefreshToken = refreshRaw, - RefreshExpiresAt = session.ExpiresAt - }; -} - private static string Sha256(string raw) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw)); diff --git a/AMREZ.EOP.Application/UseCases/ImportData/Location/LocationImportUseCase.cs b/AMREZ.EOP.Application/UseCases/ImportData/Location/LocationImportUseCase.cs new file mode 100644 index 0000000..091b6eb --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/ImportData/Location/LocationImportUseCase.cs @@ -0,0 +1,413 @@ +using System.Data; +using System.Text; +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Applications.UseCases.ImportData.Location; +using AMREZ.EOP.Abstractions.Infrastructures.Common; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Contracts.DTOs.ImportData.Location; +using AMREZ.EOP.Contracts.DTOs.MasterData.District; +using AMREZ.EOP.Contracts.DTOs.MasterData.Province; +using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Domain.Entities.Tenancy; +using ClosedXML.Excel; +using Microsoft.AspNetCore.Http; + +namespace AMREZ.EOP.Application.UseCases.ImportData.Location; + +public sealed class LocationImportUseCase : ILocationImportUseCase +{ + private readonly IProvinceRepository _provinceRepo; + private readonly IDistrictRepository _districtRepo; + private readonly ISubdistrictRepository _subdistrictRepo; + private readonly ITenantRepository _tenants; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + private readonly IUnitOfWork _uow; + + public LocationImportUseCase( + IProvinceRepository provinceRepo, + IDistrictRepository districtRepo, + ISubdistrictRepository subdistrictRepo, + ITenantRepository tenants, + ITenantResolver tenantResolver, + IHttpContextAccessor http, + IUnitOfWork uow) + { + _provinceRepo = provinceRepo; + _districtRepo = districtRepo; + _subdistrictRepo = subdistrictRepo; + _tenants = tenants; + _tenantResolver = tenantResolver; + _http = http; + _uow = uow; + } + + public async Task ExecuteAsync(IFormFile file, CancellationToken ct = default) + { + if (file == null || file.Length == 0) + throw new InvalidOperationException("Excel file is required"); + + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + + var tn = await _tenants.GetAsync(tc.Id) ?? throw new InvalidOperationException("Tenant config not found"); + var tenantId = tn.TenantId; + + var result = new LocationImportResultDto(); + + await _uow.BeginAsync(tc, IsolationLevel.ReadCommitted, ct); + try + { + var provincesByCode = await LoadGlobalProvincesAsync(tenantId, ct); + var districtsByCode = await LoadGlobalDistrictsAsync(tenantId, ct); + var subdistrictsByCode = await LoadGlobalSubdistrictsAsync(tenantId, ct); + + using var stream = new MemoryStream(); + await file.CopyToAsync(stream, ct); + stream.Position = 0; + + using var wb = new XLWorkbook(stream); + var ws = wb.Worksheets.First(); + var lastRow = ws.LastRowUsed().RowNumber(); + + // row 1 = header + for (var row = 2; row <= lastRow; row++) + { + ct.ThrowIfCancellationRequested(); + result.RowsRead++; + + string Get(int col) + { + try + { + return ws.Cell(row, col).GetString().Trim(); + } + catch + { + return string.Empty; + } + } + + // 1: CC, 2: AA, 3: TT, 4: CCAATT, 5: จังหวัด, 6: อำเภอ, 7: ตำบล, 8: รหัสไปรษณีย์ + var cc = Get(1); + var aa = Get(2); + var tt = Get(3); + var locCode = Get(4); + + var provinceName = Get(5); + var districtName = Get(6); + var subdistrictName = Get(7); + var postcode = Get(8); + + string? code = null; + + if (!string.IsNullOrWhiteSpace(cc) && + !string.IsNullOrWhiteSpace(aa) && + !string.IsNullOrWhiteSpace(tt)) + { + code = + cc.PadLeft(2, '0') + + aa.PadLeft(2, '0') + + tt.PadLeft(2, '0'); + } + else if (!string.IsNullOrWhiteSpace(locCode)) + { + code = locCode; + } + + if (string.IsNullOrWhiteSpace(code)) + continue; + + code = code.Trim(); + if (code.Length < 6) + code = code.PadLeft(6, '0'); + else if (code.Length > 6) + code = code[..6]; + + var isProvince = code.EndsWith("0000", StringComparison.Ordinal); + var isDistrict = !isProvince && code.EndsWith("00", StringComparison.Ordinal); + var isSubdistrict = !isProvince && !isDistrict; + + if (isProvince) + { + await ImportProvinceAsync(code, provinceName, provincesByCode, tenantId, result, ct); + } + else if (isDistrict) + { + await ImportDistrictAsync( + code, + provinceName, + districtName, + provincesByCode, + districtsByCode, + tenantId, + result, + ct); + } + else if (isSubdistrict) + { + await ImportSubdistrictAsync( + code, + provinceName, + districtName, + subdistrictName, + postcode, + provincesByCode, + districtsByCode, + subdistrictsByCode, + tenantId, + result, + ct); + } + } + + await _uow.CommitAsync(ct); + return result; + } + catch + { + await _uow.RollbackAsync(ct); + throw; + } + } + + // ========== preload helpers ========== + + private async Task> LoadGlobalProvincesAsync(Guid tenantId, CancellationToken ct) + { + var req = new ProvinceListRequest + { + Page = 1, + PageSize = 500, + IncludeInactive = true, + Search = null + }; + + var page = await _provinceRepo.SearchEffectiveAsync(tenantId, req, ct); + + return page.Items + .Where(x => x.Scope == "global" && !string.IsNullOrWhiteSpace(x.Code)) + .GroupBy(x => x.Code!) + .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); + } + + private async Task> LoadGlobalDistrictsAsync(Guid tenantId, CancellationToken ct) + { + var req = new DistrictListRequest + { + Page = 1, + PageSize = 5000, + IncludeInactive = true, + Search = null + }; + + var page = await _districtRepo.SearchEffectiveAsync(tenantId, req, ct); + + return page.Items + .Where(x => x.Scope == "global" && !string.IsNullOrWhiteSpace(x.Code)) + .GroupBy(x => x.Code!) + .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); + } + + private async Task> LoadGlobalSubdistrictsAsync(Guid tenantId, CancellationToken ct) + { + var req = new SubdistrictListRequest + { + Page = 1, + PageSize = 10000, + IncludeInactive = true, + Search = null + }; + + var page = await _subdistrictRepo.SearchEffectiveAsync(tenantId, req, ct); + + return page.Items + .Where(x => x.Scope == "global" && !string.IsNullOrWhiteSpace(x.Code)) + .GroupBy(x => x.Code!) + .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); + } + + // ========== import helpers ========== + + private async Task ImportProvinceAsync( + string ccaatt, + string? provinceName, + Dictionary provincesByCode, + Guid tenantId, + LocationImportResultDto result, + CancellationToken ct) + { + var provCode = ccaatt[..2]; + + if (!provincesByCode.TryGetValue(provCode, out var prov)) + { + prov = new Province + { + Id = Guid.NewGuid(), + TenantId = tenantId, + Scope = "global", + Code = provCode, + Name = string.IsNullOrWhiteSpace(provinceName) ? provCode : provinceName, + IsActive = true, + IsSystem = true + }; + + await _provinceRepo.AddAsync(prov, ct); + provincesByCode[provCode] = prov; + result.ProvincesCreated++; + return; + } + + var updated = false; + + if (!string.IsNullOrWhiteSpace(provinceName) && prov.Name != provinceName) + { + prov.Name = provinceName; + updated = true; + } + + if (updated) + { + await _provinceRepo.UpdateAsync(prov, ct); + result.ProvincesUpdated++; + } + } + + private async Task ImportDistrictAsync( + string ccaatt, + string? provinceName, + string? districtName, + Dictionary provincesByCode, + Dictionary districtsByCode, + Guid tenantId, + LocationImportResultDto result, + CancellationToken ct) + { + var provCode = ccaatt[..2]; + var distCode = ccaatt[..4]; + + if (!provincesByCode.TryGetValue(provCode, out var prov)) + { + await ImportProvinceAsync(provCode + "0000", provinceName, provincesByCode, tenantId, result, ct); + prov = provincesByCode[provCode]; + } + + if (!districtsByCode.TryGetValue(distCode, out var dist)) + { + dist = new District + { + Id = Guid.NewGuid(), + TenantId = tenantId, + Scope = "global", + Code = distCode, + Name = string.IsNullOrWhiteSpace(districtName) ? distCode : districtName, + ProvinceId = prov.Id, + IsActive = true, + IsSystem = true + }; + + await _districtRepo.AddAsync(dist, ct); + districtsByCode[distCode] = dist; + result.DistrictsCreated++; + return; + } + + var updated = false; + + if (!string.IsNullOrWhiteSpace(districtName) && dist.Name != districtName) + { + dist.Name = districtName; + updated = true; + } + + if (dist.ProvinceId != prov.Id) + { + dist.ProvinceId = prov.Id; + updated = true; + } + + if (updated) + { + await _districtRepo.UpdateAsync(dist, ct); + result.DistrictsUpdated++; + } + } + + private async Task ImportSubdistrictAsync( + string ccaatt, + string? provinceName, + string? districtName, + string? subdistrictName, + string? postcode, + Dictionary provincesByCode, + Dictionary districtsByCode, + Dictionary subdistrictsByCode, + Guid tenantId, + LocationImportResultDto result, + CancellationToken ct) + { + var provCode = ccaatt[..2]; + var distCode = ccaatt[..4]; + var subCode = ccaatt[..6]; + + if (!provincesByCode.TryGetValue(provCode, out var prov)) + { + await ImportProvinceAsync(provCode + "0000", provinceName, provincesByCode, tenantId, result, ct); + prov = provincesByCode[provCode]; + } + + if (!districtsByCode.TryGetValue(distCode, out var dist)) + { + await ImportDistrictAsync(distCode + "00", provinceName, districtName, provincesByCode, districtsByCode, tenantId, result, ct); + dist = districtsByCode[distCode]; + } + + if (!subdistrictsByCode.TryGetValue(subCode, out var sub)) + { + sub = new Subdistrict + { + Id = Guid.NewGuid(), + TenantId = tenantId, + Scope = "global", + Code = subCode, + Name = string.IsNullOrWhiteSpace(subdistrictName) ? subCode : subdistrictName, + DistrictId = dist.Id, + Postcode = string.IsNullOrWhiteSpace(postcode) ? null : postcode, + IsActive = true, + IsSystem = true + }; + + await _subdistrictRepo.AddAsync(sub, ct); + subdistrictsByCode[subCode] = sub; + result.SubdistrictsCreated++; + return; + } + + var updated = false; + + if (!string.IsNullOrWhiteSpace(subdistrictName) && sub.Name != subdistrictName) + { + sub.Name = subdistrictName; + updated = true; + } + + if (sub.DistrictId != dist.Id) + { + sub.DistrictId = dist.Id; + updated = true; + } + + if (!string.IsNullOrWhiteSpace(postcode) && sub.Postcode != postcode) + { + sub.Postcode = postcode; + updated = true; + } + + if (updated) + { + await _subdistrictRepo.UpdateAsync(sub, ct); + result.SubdistrictsUpdated++; + } + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Allergen/CreateAllergenUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/CreateAllergenUseCase.cs new file mode 100644 index 0000000..1527654 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/CreateAllergenUseCase.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Allergen/DeleteAllergenUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/DeleteAllergenUseCase.cs new file mode 100644 index 0000000..6ec40a3 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/DeleteAllergenUseCase.cs @@ -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 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; + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Allergen/GetAllergenUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/GetAllergenUseCase.cs new file mode 100644 index 0000000..d7d30c1 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/GetAllergenUseCase.cs @@ -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 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); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Allergen/ListAllergenUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/ListAllergenUseCase.cs new file mode 100644 index 0000000..7018c5c --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/ListAllergenUseCase.cs @@ -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> 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(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); +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Allergen/UpdateAllergenUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/UpdateAllergenUseCase.cs new file mode 100644 index 0000000..cbe4963 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Allergen/UpdateAllergenUseCase.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Brand/CreateBrandUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Brand/CreateBrandUseCase.cs new file mode 100644 index 0000000..ed3c101 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Brand/CreateBrandUseCase.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Brand/DeleteBrandUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Brand/DeleteBrandUseCase.cs new file mode 100644 index 0000000..6b67d6e --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Brand/DeleteBrandUseCase.cs @@ -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 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; + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Brand/GetBrandUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Brand/GetBrandUseCase.cs new file mode 100644 index 0000000..dc9e434 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Brand/GetBrandUseCase.cs @@ -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 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); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Brand/ListBrandsUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Brand/ListBrandsUseCase.cs new file mode 100644 index 0000000..1906967 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Brand/ListBrandsUseCase.cs @@ -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> 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(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); +} diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Brand/UpdateBrandUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Brand/UpdateBrandUseCase.cs new file mode 100644 index 0000000..0a0036f --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Brand/UpdateBrandUseCase.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/District/GetDistrictUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/District/GetDistrictUseCase.cs new file mode 100644 index 0000000..6dcce27 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/District/GetDistrictUseCase.cs @@ -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 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 + ); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/District/ListDistrictsUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/District/ListDistrictsUseCase.cs new file mode 100644 index 0000000..f22f9e9 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/District/ListDistrictsUseCase.cs @@ -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> 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(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 + ); +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Province/GetProvinceUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Province/GetProvinceUseCase.cs new file mode 100644 index 0000000..603d001 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Province/GetProvinceUseCase.cs @@ -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 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 + ); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Province/ListProvincesUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Province/ListProvincesUseCase.cs new file mode 100644 index 0000000..a3687b3 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Province/ListProvincesUseCase.cs @@ -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> 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(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 + ); +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Subdistricts/GetSubdistrictUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Subdistricts/GetSubdistrictUseCase.cs new file mode 100644 index 0000000..80a80d1 --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Subdistricts/GetSubdistrictUseCase.cs @@ -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 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 + ); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Application/UseCases/MasterData/Subdistricts/ListSubdistrictsUseCase.cs b/AMREZ.EOP.Application/UseCases/MasterData/Subdistricts/ListSubdistrictsUseCase.cs new file mode 100644 index 0000000..e7da4ec --- /dev/null +++ b/AMREZ.EOP.Application/UseCases/MasterData/Subdistricts/ListSubdistrictsUseCase.cs @@ -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> 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(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 + ); +} \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/Common/PagedResponse.cs b/AMREZ.EOP.Contracts/DTOs/Common/PagedResponse.cs new file mode 100644 index 0000000..37bea5e --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/Common/PagedResponse.cs @@ -0,0 +1,3 @@ +namespace AMREZ.EOP.Contracts.DTOs.Common; + +public sealed record PagedResponse(int Page, int PageSize, int Total, IReadOnlyList Items); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/ImportData/Location/LocationImportResultDto.cs b/AMREZ.EOP.Contracts/DTOs/ImportData/Location/LocationImportResultDto.cs new file mode 100644 index 0000000..566b9bc --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/ImportData/Location/LocationImportResultDto.cs @@ -0,0 +1,15 @@ +namespace AMREZ.EOP.Contracts.DTOs.ImportData.Location; + +public sealed class LocationImportResultDto +{ + public int RowsRead { get; set; } + + public int ProvincesCreated { get; set; } + public int ProvincesUpdated { get; set; } + + public int DistrictsCreated { get; set; } + public int DistrictsUpdated { get; set; } + + public int SubdistrictsCreated { get; set; } + public int SubdistrictsUpdated { get; set; } +} \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenCreateRequest.cs new file mode 100644 index 0000000..ad5e55d --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +public sealed record AllergenCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenListRequest.cs new file mode 100644 index 0000000..51487e4 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +public sealed record AllergenListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenResponse.cs new file mode 100644 index 0000000..04615d2 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +public sealed record AllergenResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenUpdateRequest.cs new file mode 100644 index 0000000..dc4946a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Allergen/AllergenUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; + +public sealed record AllergenUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandCreateRequest.cs new file mode 100644 index 0000000..91060b8 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +public sealed record BrandCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandListRequest.cs new file mode 100644 index 0000000..e3604fe --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +public sealed record BrandListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandResponse.cs new file mode 100644 index 0000000..bba9e9f --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +public sealed record BrandResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandUpdateRequest.cs new file mode 100644 index 0000000..a724ef5 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Brand/BrandUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Brand; + +public sealed record BrandUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryCreateRequest.cs new file mode 100644 index 0000000..fb035b0 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Category; + +public sealed record CategoryCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryListRequest.cs new file mode 100644 index 0000000..07fcd33 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Category; + +public sealed record CategoryListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryResponse.cs new file mode 100644 index 0000000..454cc52 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Category; + +public sealed record CategoryResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryUpdateRequest.cs new file mode 100644 index 0000000..93abd0d --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Category/CategoryUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Category; + +public sealed record CategoryUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusCreateRequest.cs new file mode 100644 index 0000000..49dbbcd --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.ComplianceStatus; + +public sealed record ComplianceStatusCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusListRequest.cs new file mode 100644 index 0000000..b5a3ac7 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.ComplianceStatus; + +public sealed record ComplianceStatusListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusResponse.cs new file mode 100644 index 0000000..3943c40 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.ComplianceStatus; + +public sealed record ComplianceStatusResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusUpdateRequest.cs new file mode 100644 index 0000000..5942070 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/ComplianceStatus/ComplianceStatusUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.ComplianceStatus; + +public sealed record ComplianceStatusUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryCreateRequest.cs new file mode 100644 index 0000000..75d8397 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Country; + +public sealed record CountryCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryListRequest.cs new file mode 100644 index 0000000..c2cb911 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Country; + +public sealed record CountryListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryResponse.cs new file mode 100644 index 0000000..9bed6f1 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Country; + +public sealed record CountryResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryUpdateRequest.cs new file mode 100644 index 0000000..aa46efe --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Country/CountryUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Country; + +public sealed record CountryUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyCreateRequest.cs new file mode 100644 index 0000000..63c5366 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Currency; + +public sealed record CurrencyCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyListRequest.cs new file mode 100644 index 0000000..708a957 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Currency; + +public sealed record CurrencyListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyResponse.cs new file mode 100644 index 0000000..5dbfff4 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Currency; + +public sealed record CurrencyResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyUpdateRequest.cs new file mode 100644 index 0000000..cca5fca --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Currency/CurrencyUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Currency; + +public sealed record CurrencyUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/District/DistrictListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/District/DistrictListRequest.cs new file mode 100644 index 0000000..00afc7c --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/District/DistrictListRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.District; + +public sealed record DistrictListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + Guid? ProvinceId = default, + string? ProvinceCode = default, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/District/DistrictResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/District/DistrictResponse.cs new file mode 100644 index 0000000..d248626 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/District/DistrictResponse.cs @@ -0,0 +1,17 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.District; + +public sealed record DistrictResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta, + string DopaCode, + Guid ProvinceId, + string ProvinceName, + string ProvinceDopaCode +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusCreateRequest.cs new file mode 100644 index 0000000..adc0dfa --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus; + +public sealed record DocControlStatusCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusListRequest.cs new file mode 100644 index 0000000..a1a362c --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus; + +public sealed record DocControlStatusListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusResponse.cs new file mode 100644 index 0000000..5246845 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus; + +public sealed record DocControlStatusResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusUpdateRequest.cs new file mode 100644 index 0000000..c204c9f --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/DocControlStatus/DocControlStatusUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus; + +public sealed record DocControlStatusUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestCreateRequest.cs new file mode 100644 index 0000000..fdabb39 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest; + +public sealed record FuncTestCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestListRequest.cs new file mode 100644 index 0000000..a50e690 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest; + +public sealed record FuncTestListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestResponse.cs new file mode 100644 index 0000000..34ad69c --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest; + +public sealed record FuncTestResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestUpdateRequest.cs new file mode 100644 index 0000000..e9c029c --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/FuncTest/FuncTestUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest; + +public sealed record FuncTestUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassCreateRequest.cs new file mode 100644 index 0000000..3d353be --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass; + +public sealed record HazardClassCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassListRequest.cs new file mode 100644 index 0000000..184fc0e --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass; + +public sealed record HazardClassListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassResponse.cs new file mode 100644 index 0000000..d154a2b --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass; + +public sealed record HazardClassResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassUpdateRequest.cs new file mode 100644 index 0000000..dd8eb4e --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/HazardClass/HazardClassUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass; + +public sealed record HazardClassUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageCreateRequest.cs new file mode 100644 index 0000000..40bfa8a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Language; + +public sealed record LanguageCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageListRequest.cs new file mode 100644 index 0000000..96af190 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Language; + +public sealed record LanguageListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageResponse.cs new file mode 100644 index 0000000..209b164 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Language; + +public sealed record LanguageResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageUpdateRequest.cs new file mode 100644 index 0000000..cf750f7 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Language/LanguageUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Language; + +public sealed record LanguageUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerCreateRequest.cs new file mode 100644 index 0000000..2eea741 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; + +public sealed record ManufacturerCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true + ); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerListRequest.cs new file mode 100644 index 0000000..44183c9 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; + +public sealed record ManufacturerListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerResponse.cs new file mode 100644 index 0000000..a6e6ba5 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; + +public sealed record ManufacturerResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerUpdateRequest.cs new file mode 100644 index 0000000..fc60d68 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Manufacturer/ManufacturerUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; + +public sealed record ManufacturerUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketCreateRequest.cs new file mode 100644 index 0000000..beed05b --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Market; + +public sealed record MarketCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketListRequest.cs new file mode 100644 index 0000000..a8f0af8 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Market; + +public sealed record MarketListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketResponse.cs new file mode 100644 index 0000000..eda3ebc --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Market; + +public sealed record MarketResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketUpdateRequest.cs new file mode 100644 index 0000000..9e3cb52 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Market/MarketUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Market; + +public sealed record MarketUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupCreateRequest.cs new file mode 100644 index 0000000..a008599 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; + +public sealed record PackingGroupCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupListRequest.cs new file mode 100644 index 0000000..4505dda --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; + +public sealed record PackingGroupListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupResponse.cs new file mode 100644 index 0000000..4aeeffd --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; + +public sealed record PackingGroupResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupUpdateRequest.cs new file mode 100644 index 0000000..18ee57a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/PackingGroup/PackingGroupUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; + +public sealed record PackingGroupUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Province/ProvinceListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Province/ProvinceListRequest.cs new file mode 100644 index 0000000..2476c1e --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Province/ProvinceListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Province; + +public sealed record ProvinceListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Province/ProvinceResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Province/ProvinceResponse.cs new file mode 100644 index 0000000..fb93c34 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Province/ProvinceResponse.cs @@ -0,0 +1,14 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Province; + +public sealed record ProvinceResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta, + string DopaCode +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageCreateRequest.cs new file mode 100644 index 0000000..700e554 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QaStage; + +public sealed record QaStageCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageListRequest.cs new file mode 100644 index 0000000..1a2def9 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QaStage; + +public sealed record QaStageListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageResponse.cs new file mode 100644 index 0000000..cf7cc4d --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QaStage; + +public sealed record QaStageResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageUpdateRequest.cs new file mode 100644 index 0000000..841ef16 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QaStage/QaStageUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QaStage; + +public sealed record QaStageUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusCreateRequest.cs new file mode 100644 index 0000000..8c2ffcc --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; + +public sealed record QcStatusCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusListRequest.cs new file mode 100644 index 0000000..83f61d8 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; + +public sealed record QcStatusListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusResponse.cs new file mode 100644 index 0000000..534b2e0 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; + +public sealed record QcStatusResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusUpdateRequest.cs new file mode 100644 index 0000000..0d5e9f6 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/QcStatus/QcStatusUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; + +public sealed record QcStatusUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassCreateRequest.cs new file mode 100644 index 0000000..500dcac --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; + +public sealed record RecallClassCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassListRequest.cs new file mode 100644 index 0000000..27324f0 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; + +public sealed record RecallClassListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassResponse.cs new file mode 100644 index 0000000..8867c15 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; + +public sealed record RecallClassResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassUpdateRequest.cs new file mode 100644 index 0000000..10d5e6a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RecallClass/RecallClassUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; + +public sealed record RecallClassUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassCreateRequest.cs new file mode 100644 index 0000000..e84f36a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; + +public sealed record RiskClassCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassListRequest.cs new file mode 100644 index 0000000..e9480c3 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; + +public sealed record RiskClassListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassResponse.cs new file mode 100644 index 0000000..aa14e5a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; + +public sealed record RiskClassResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassUpdateRequest.cs new file mode 100644 index 0000000..af3c3e3 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RiskClass/RiskClassUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; + +public sealed record RiskClassUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteCreateRequest.cs new file mode 100644 index 0000000..e56b04b --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Route; + +public sealed record RouteCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteListRequest.cs new file mode 100644 index 0000000..d791a1f --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Route; + +public sealed record RouteListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteResponse.cs new file mode 100644 index 0000000..0284d04 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Route; + +public sealed record RouteResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteUpdateRequest.cs new file mode 100644 index 0000000..87422ed --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Route/RouteUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Route; + +public sealed record RouteUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleCreateRequest.cs new file mode 100644 index 0000000..3e43794 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; + +public sealed record RxScheduleCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleListRequest.cs new file mode 100644 index 0000000..9f87c50 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; + +public sealed record RxScheduleListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleResponse.cs new file mode 100644 index 0000000..4b5cc3c --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; + +public sealed record RxScheduleResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleUpdateRequest.cs new file mode 100644 index 0000000..4963b96 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/RxSchedule/RxScheduleUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; + +public sealed record RxScheduleUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesCreateRequest.cs new file mode 100644 index 0000000..a64dae2 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Species; + +public sealed record SpeciesCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesListRequest.cs new file mode 100644 index 0000000..52afdc6 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Species; + +public sealed record SpeciesListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesResponse.cs new file mode 100644 index 0000000..bdc24d2 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Species; + +public sealed record SpeciesResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesUpdateRequest.cs new file mode 100644 index 0000000..dd2be7d --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Species/SpeciesUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Species; + +public sealed record SpeciesUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusCreateRequest.cs new file mode 100644 index 0000000..6c54d30 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; + +public sealed record StabilityStatusCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusListRequest.cs new file mode 100644 index 0000000..e58709c --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; + +public sealed record StabilityStatusListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusResponse.cs new file mode 100644 index 0000000..f42b261 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; + +public sealed record StabilityStatusResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusUpdateRequest.cs new file mode 100644 index 0000000..5276753 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/StabilityStatus/StabilityStatusUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; + +public sealed record StabilityStatusUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodCreateRequest.cs new file mode 100644 index 0000000..312ab7f --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; + +public sealed record SterilizationMethodCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodListRequest.cs new file mode 100644 index 0000000..1c652cf --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; + +public sealed record SterilizationMethodListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodResponse.cs new file mode 100644 index 0000000..45e587b --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; + +public sealed record SterilizationMethodResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodUpdateRequest.cs new file mode 100644 index 0000000..f405906 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/SterilizationMethod/SterilizationMethodUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; + +public sealed record SterilizationMethodUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Subdistrict/SubdistrictListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Subdistrict/SubdistrictListRequest.cs new file mode 100644 index 0000000..69662eb --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Subdistrict/SubdistrictListRequest.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; + +public sealed record SubdistrictListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + Guid? DistrictId = default, + string? DistrictCode = default, + Guid? ProvinceId = default, + string? ProvinceCode = default, + bool IncludeInactive = false, + bool IncludeOverrides = true + ); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Subdistrict/SubdistrictResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Subdistrict/SubdistrictResponse.cs new file mode 100644 index 0000000..a9551db --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Subdistrict/SubdistrictResponse.cs @@ -0,0 +1,21 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; + +public sealed record SubdistrictResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta, + string DopaCode, + string? Postcode, + Guid DistrictId, + string DistrictName, + string DistrictDopaCode, + Guid ProvinceId, + string ProvinceName, + string ProvinceDopaCode +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomCreateRequest.cs new file mode 100644 index 0000000..5f8e09f --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Uom; + +public sealed record UomCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomListRequest.cs new file mode 100644 index 0000000..62e348a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Uom; + +public sealed record UomListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomResponse.cs new file mode 100644 index 0000000..146f3b1 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Uom; + +public sealed record UomResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomUpdateRequest.cs new file mode 100644 index 0000000..c89980e --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Uom/UomUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Uom; + +public sealed record UomUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmCreateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmCreateRequest.cs new file mode 100644 index 0000000..4bd923a --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmCreateRequest.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Vvm; + +public sealed record VvmCreateRequest( + string Code, + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + string Scope = "tenant", + Guid? OverridesGlobalId = null, + bool IsActive = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmListRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmListRequest.cs new file mode 100644 index 0000000..e856538 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmListRequest.cs @@ -0,0 +1,9 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Vvm; + +public sealed record VvmListRequest( + int Page = 1, + int PageSize = 20, + string? Search = null, + bool IncludeInactive = false, + bool IncludeOverrides = true +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmResponse.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmResponse.cs new file mode 100644 index 0000000..a244a28 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmResponse.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Vvm; + +public sealed record VvmResponse( + Guid Id, + string Scope, + string Code, + string Name, + Dictionary? NameI18n, + Guid? OverridesGlobalId, + bool IsActive, + bool IsSystem, + Dictionary? Meta +); \ No newline at end of file diff --git a/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmUpdateRequest.cs b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmUpdateRequest.cs new file mode 100644 index 0000000..9386bf5 --- /dev/null +++ b/AMREZ.EOP.Contracts/DTOs/MasterData/Vvm/VvmUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace AMREZ.EOP.Contracts.DTOs.MasterData.Vvm; + +public sealed record VvmUpdateRequest( + string Name, + Dictionary? NameI18n, + Dictionary? Meta, + bool IsActive +); \ No newline at end of file diff --git a/AMREZ.EOP.Domain/Entities/MasterData/District.cs b/AMREZ.EOP.Domain/Entities/MasterData/District.cs new file mode 100644 index 0000000..4f29ca5 --- /dev/null +++ b/AMREZ.EOP.Domain/Entities/MasterData/District.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Domain.Entities.MasterData; + +public class District : MasterBase +{ + public string Code { get; set; } = null!; + public Guid ProvinceId { get; set; } + public Province Province { get; set; } = null!; + public ICollection Subdistricts { get; set; } = new List(); +} + +public class DistrictBlock : MasterBlockBase +{ +} \ No newline at end of file diff --git a/AMREZ.EOP.Domain/Entities/MasterData/Province.cs b/AMREZ.EOP.Domain/Entities/MasterData/Province.cs new file mode 100644 index 0000000..0613ac7 --- /dev/null +++ b/AMREZ.EOP.Domain/Entities/MasterData/Province.cs @@ -0,0 +1,11 @@ +namespace AMREZ.EOP.Domain.Entities.MasterData; + +public class Province : MasterBase +{ + public string Code { get; set; } = null!; + public ICollection Districts { get; set; } = new List(); +} + +public class ProvinceBlock : MasterBlockBase +{ +} \ No newline at end of file diff --git a/AMREZ.EOP.Domain/Entities/MasterData/Subdistrict.cs b/AMREZ.EOP.Domain/Entities/MasterData/Subdistrict.cs new file mode 100644 index 0000000..c772062 --- /dev/null +++ b/AMREZ.EOP.Domain/Entities/MasterData/Subdistrict.cs @@ -0,0 +1,13 @@ +namespace AMREZ.EOP.Domain.Entities.MasterData; + +public class Subdistrict : MasterBase +{ + public string Code { get; set; } = null!; + public string? Postcode { get; set; } + public Guid DistrictId { get; set; } + public District District { get; set; } = null!; +} + +public class SubdistrictBlock : MasterBlockBase +{ +} \ No newline at end of file diff --git a/AMREZ.EOP.Domain/Shared/Data/ThaiAddressParser.cs b/AMREZ.EOP.Domain/Shared/Data/ThaiAddressParser.cs new file mode 100644 index 0000000..d4973a9 --- /dev/null +++ b/AMREZ.EOP.Domain/Shared/Data/ThaiAddressParser.cs @@ -0,0 +1,231 @@ +using System.Text.RegularExpressions; + +namespace AMREZ.EOP.Domain.Shared.Data; + +using System.Text.RegularExpressions; + +public record ParsedAddress( + string Name, + string Phone, + string AddressOnly, // ที่อยู่เต็ม ไม่รวมชื่อ/เบอร์ + string AddressMain, // ส่วนต้นของที่อยู่ เช่น เลขที่ / หมู่ / ซอย / ถนน + string Subdistrict, // ตำบล / แขวง + string District, // อำเภอ / เขต + string Province, // จังหวัด + string PostalCode // รหัสไปรษณีย์ 5 หลัก +); + +public static class ThaiAddressParser +{ + public static ParsedAddress Parse(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + { + return new ParsedAddress("", "", "", "", "", "", "", ""); + } + + var original = raw; + + // normalize space + เอา emoji / สัญลักษณ์กวน ๆ ออก + var sNorm = Regex.Replace(original, "[ \\t]+", " "); + sNorm = sNorm + .Replace("🏠", " ") + .Replace("☎️", " ") + .Replace("📍", " "); + sNorm = Regex.Replace(sNorm.Trim(), "\\s*\\n\\s*", "\n"); + + // ====================== PHONE ====================== + string? phoneDigits = null; + var phoneMatches = Regex.Matches(original.Replace("-", ""), @"0\d{8,9}"); + if (phoneMatches.Count > 0) + { + // ส่วนใหญ่เบอร์จะอยู่ท้าย เอาอันสุดท้าย + phoneDigits = phoneMatches[^1].Value; + } + + var sWithoutPhone = sNorm; + + if (!string.IsNullOrWhiteSpace(phoneDigits)) + { + // pattern ยอมให้มี "-" หรือ space คั่นตัวเลขแต่ละตัว + var phoneChars = phoneDigits.ToCharArray(); + var phonePattern = string.Join("[- ]?", phoneChars); + var phoneRegex = new Regex(phonePattern); + + sWithoutPhone = phoneRegex.Replace(sWithoutPhone, ""); + + // ตัดคำ "โทร", "Tel", "เบอร์" ที่ติดกับเบอร์ออก + sWithoutPhone = Regex.Replace( + sWithoutPhone, + @"(โทร\.?|Tel\.?|tel\.?|เบอร์|[Tt]el ?:)\s*", + "", + RegexOptions.CultureInvariant + ); + } + + // ====================== NAME ====================== + var lines = sWithoutPhone + .Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + var firstLine = lines.Length > 0 ? lines[0] : string.Empty; + + // หา index สิ้นสุดชื่อ (ก่อนเจอเลขหรือคำว่า "ที่อยู่"/"ร้าน") + var nameEndIndex = firstLine.Length; + + var digitMatch = Regex.Match(firstLine, @"\d"); + if (digitMatch.Success && digitMatch.Index < nameEndIndex) + { + nameEndIndex = digitMatch.Index; + } + + var addrWordMatch = Regex.Match(firstLine, "(ที่อยู่|ร้าน)"); + if (addrWordMatch.Success && addrWordMatch.Index < nameEndIndex) + { + nameEndIndex = addrWordMatch.Index; + } + + var name = firstLine[..nameEndIndex].Trim(' ', ':', '-', '(', ')'); + // ถ้ามีวงเล็บท้ายชื่อ และไม่มีเลขข้างใน ตัดออก + name = Regex.Replace(name, @"\([^0-9)]*\)$", "").Trim(); + + // ====================== ADDRESS TEXT (เต็ม) ====================== + var allText = string.Join(" ", lines); + var addrText = allText; + + if (!string.IsNullOrWhiteSpace(name) && addrText.StartsWith(name)) + { + addrText = addrText[name.Length..].Trim(' ', ',', ':', '-'); + } + + addrText = Regex.Replace(addrText, @"\s{2,}", " "); + + // เคลียร์วงเล็บเปล่า ๆ "()" ที่บางแถวมี + addrText = Regex.Replace(addrText, @"\(\s*\)", "").Trim(); + + // ====================== POSTAL CODE ====================== + string postal = ""; + var postalMatch = Regex.Match(addrText, @"(\d{5})(?!.*\d{5})"); + if (postalMatch.Success) + { + postal = postalMatch.Groups[1].Value; + } + + // helper เลือกกลุ่มแรกที่ไม่ว่าง + static string Pick(params Group[] groups) + { + foreach (var g in groups) + { + if (g.Success && !string.IsNullOrWhiteSpace(g.Value)) + return g.Value.Trim(); + } + + return ""; + } + + // ====================== SUBDISTRICT (ตำบล/แขวง) ====================== + string subdistrict = ""; + var subMatch = Regex.Match( + addrText, + @"ต\. ?(?[^ \d]+)|ตำบล ?(?[^ \d]+)|แขวง ?(?[^ \d]+)" + ); + if (subMatch.Success) + { + subdistrict = Pick( + subMatch.Groups["t1"], + subMatch.Groups["t2"], + subMatch.Groups["t3"] + ); + } + + // ====================== DISTRICT (อำเภอ/เขต) ====================== + string district = ""; + var distMatch = Regex.Match( + addrText, + @"อ\. ?(?[^ \d]+)|อำเภอ ?(?[^ \d]+)|เขต ?(?[^ \d]+)" + ); + if (distMatch.Success) + { + district = Pick( + distMatch.Groups["d1"], + distMatch.Groups["d2"], + distMatch.Groups["d3"] + ); + } + + // ====================== PROVINCE ====================== + string province = ""; + if (Regex.IsMatch(addrText, @"กรุงเทพมหานคร|กรุงเทพฯ|กทม\.?|กรุงเทพ(?![กษ])")) + { + province = "กรุงเทพมหานคร"; + } + else + { + var provMatch = Regex.Match( + addrText, + @"จ\. ?(?[^0-9 ]+)|จังหวัด ?(?[^0-9 ]+)" + ); + if (provMatch.Success) + { + province = Pick( + provMatch.Groups["p1"], + provMatch.Groups["p2"] + ); + } + } + + // special rule: ถ้าอำเภอ = "เมือง" แล้วมีจังหวัด ให้กลายเป็น "เมือง{จังหวัด}" + // เช่น เมือง + ลำพูน = เมืองลำพูน + if (district == "เมือง" && !string.IsNullOrWhiteSpace(province)) + { + district = $"เมือง{province}"; + } + + // ====================== ADDRESS MAIN ====================== + var mainAddress = addrText; + + // ลบรหัสไปรษณีย์ออกจาก address main + if (!string.IsNullOrEmpty(postal)) + { + mainAddress = mainAddress.Replace(postal, "").Trim(); + } + + // ลบจังหวัดออก + mainAddress = Regex.Replace( + mainAddress, + @"(กรุงเทพมหานคร|กรุงเทพฯ|กทม\.?|กรุงเทพ(?![กษ])|จ\. ?[^0-9 ]+|จังหวัด ?[^0-9 ]+)", + "", + RegexOptions.CultureInvariant + ); + + // ลบตำบล/แขวง + อำเภอ/เขต ออกจาก address main + mainAddress = Regex.Replace( + mainAddress, + @"(ต\. ?[^ \d]+|ตำบล ?[^ \d]+|แขวง ?[^ \d]+|อ\. ?[^ \d]+|อำเภอ ?[^ \d]+|เขต ?[^ \d]+)", + "", + RegexOptions.CultureInvariant + ); + + // ลบ "กรุงเทพ / กทม" ที่ยังหลุดท้าย ๆ อีกที กันเคสแบบ "กรุงเทพ ." + mainAddress = Regex.Replace( + mainAddress, + @"(กรุงเทพมหานคร|กรุงเทพฯ|กรุงเทพ|กทม\.?)\s*\.?", + "", + RegexOptions.CultureInvariant + ); + + // เคลียร์ space / comma ซ้ำ + mainAddress = Regex.Replace(mainAddress, @"\s{2,}", " "); + mainAddress = mainAddress.Trim(' ', ',', '.'); + + return new ParsedAddress( + Name: name, + Phone: phoneDigits ?? "", + AddressOnly: addrText, + AddressMain: mainAddress, + Subdistrict: subdistrict, + District: district, + Province: province, + PostalCode: postal + ); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Data/AppDbContext.cs b/AMREZ.EOP.Infrastructures/Data/AppDbContext.cs index a8df3cb..bedb5a3 100644 --- a/AMREZ.EOP.Infrastructures/Data/AppDbContext.cs +++ b/AMREZ.EOP.Infrastructures/Data/AppDbContext.cs @@ -126,6 +126,15 @@ public class AppDbContext : DbContext public DbSet Allergens => Set(); public DbSet AllergenBlocks => Set(); + public DbSet Provinces => Set(); + public DbSet ProvinceBlocks => Set(); + + public DbSet Districts => Set(); + public DbSet DistrictBlocks => Set(); + + public DbSet Subdistricts => Set(); + public DbSet SubdistrictBlocks => Set(); + protected override void OnModelCreating(ModelBuilder model) { // JSON converters/comparers @@ -182,7 +191,6 @@ public class AppDbContext : DbContext metaProp.HasColumnType("jsonb"); b.HasIndex(x => new { x.Scope, x.TenantId, x.Code }).IsUnique(); - // b.HasIndex(x => new { x.TenantId, x.OverridesGlobalId }).IsUnique().HasFilter("\"OverridesGlobalId\" IS NOT NULL"); } void ConfigureBlockBase(string table, string schema = "master") @@ -780,6 +788,62 @@ public class AppDbContext : DbContext ConfigureMasterBase("allergens"); ConfigureBlockBase("allergen_blocks"); + ConfigureMasterBase("provinces"); + ConfigureBlockBase("province_blocks"); + + model.Entity(b => + { + b.Property(x => x.Code) + .IsRequired() + .HasMaxLength(2); + + b.HasIndex(x => x.Code); + }); + + ConfigureMasterBase("districts"); + ConfigureBlockBase("district_blocks"); + + model.Entity(b => + { + b.Property(x => x.Code) + .IsRequired() + .HasMaxLength(4); + + b.HasIndex(x => x.Code); + + b.HasOne(x => x.Province) + .WithMany(p => p.Districts) + .HasForeignKey(x => new { x.TenantId, x.ProvinceId }) + .HasPrincipalKey(p => new { p.TenantId, p.Id }) + .OnDelete(DeleteBehavior.Restrict); + + b.HasIndex(x => new { x.TenantId, x.ProvinceId }); + }); + + ConfigureMasterBase("subdistricts"); + ConfigureBlockBase("subdistrict_blocks"); + + model.Entity(b => + { + b.Property(x => x.Code) + .IsRequired() + .HasMaxLength(6); + + b.Property(x => x.Postcode) + .HasMaxLength(10); + + b.HasIndex(x => x.Code); + b.HasIndex(x => x.Postcode); + + b.HasOne(x => x.District) + .WithMany(d => d.Subdistricts) + .HasForeignKey(x => new { x.TenantId, x.DistrictId }) + .HasPrincipalKey(d => new { d.TenantId, d.Id }) + .OnDelete(DeleteBehavior.Restrict); + + b.HasIndex(x => new { x.TenantId, x.DistrictId }); + }); + // ====== Enum conversions ====== model.Entity().Property(x => x.Type).HasConversion(); model.Entity().Property(x => x.Type).HasConversion(); diff --git a/AMREZ.EOP.Infrastructures/DependencyInjections/ServiceCollectionExtensions.cs b/AMREZ.EOP.Infrastructures/DependencyInjections/ServiceCollectionExtensions.cs index 86d9068..fbe8129 100644 --- a/AMREZ.EOP.Infrastructures/DependencyInjections/ServiceCollectionExtensions.cs +++ b/AMREZ.EOP.Infrastructures/DependencyInjections/ServiceCollectionExtensions.cs @@ -1,6 +1,8 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications; using AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources; +using AMREZ.EOP.Abstractions.Applications.UseCases.ImportData.Location; +using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand; using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification; using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification.QrDecode; using AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy; @@ -11,10 +13,13 @@ using AMREZ.EOP.Abstractions.Security; using AMREZ.EOP.Abstractions.Storage; using AMREZ.EOP.Application.UseCases.Authentications; using AMREZ.EOP.Application.UseCases.HumanResources; +using AMREZ.EOP.Application.UseCases.ImportData.Location; +using AMREZ.EOP.Application.UseCases.MasterData.Brand; using AMREZ.EOP.Application.UseCases.Payments.SlipVerification; using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.BankDetect; using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode; using AMREZ.EOP.Application.UseCases.Tenancy; +using AMREZ.EOP.Domain.Entities.MasterData; using AMREZ.EOP.Domain.Shared.Payments; using AMREZ.EOP.Domain.Shared.Tenancy; using AMREZ.EOP.Infrastructures.Data; @@ -139,6 +144,8 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); + services.AddMasterDataBrands(); + services.AddMasterDataLocations(); // Redis (optional) services.AddSingleton(sp => { @@ -160,4 +167,33 @@ public static class ServiceCollectionExtensions return services; } + + + public static IServiceCollection AddMasterDataBrands(this IServiceCollection services) + { + // Repo + services.AddScoped(); + + // UseCases + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + + public static IServiceCollection AddMasterDataLocations(this IServiceCollection services) + { + // Repo + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // UseCases + services.AddScoped(); + + return services; + } } \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Migrations/AppDbContextModelSnapshot.cs b/AMREZ.EOP.Infrastructures/Migrations/AppDbContextModelSnapshot.cs index 8668aea..5db7c24 100644 --- a/AMREZ.EOP.Infrastructures/Migrations/AppDbContextModelSnapshot.cs +++ b/AMREZ.EOP.Infrastructures/Migrations/AppDbContextModelSnapshot.cs @@ -2600,6 +2600,150 @@ namespace AMREZ.EOP.Infrastructures.Migrations }); }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.District", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(4) + .HasColumnType("character varying(4)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("is_deleted"); + + b.Property("IsSystem") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Meta") + .HasColumnType("jsonb"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NameI18n") + .HasColumnType("jsonb"); + + b.Property("OverridesGlobalId") + .HasColumnType("uuid"); + + b.Property("ProvinceId") + .HasColumnType("uuid"); + + b.Property("Scope") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("global"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpdatedBy") + .HasColumnType("text") + .HasColumnName("updated_by"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "ProvinceId"); + + b.HasIndex("Scope", "TenantId", "Code") + .IsUnique(); + + b.ToTable("districts", "master", t => + { + t.HasCheckConstraint("ck_districts_tenant_not_null", "tenant_id is not null"); + + t.HasCheckConstraint("ck_districts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); + }); + }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DistrictBlock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("GlobalId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("is_deleted"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpdatedBy") + .HasColumnType("text") + .HasColumnName("updated_by"); + + b.HasKey("Id"); + + b.HasIndex("GlobalId"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "GlobalId") + .IsUnique(); + + b.ToTable("district_blocks", "master", t => + { + t.HasCheckConstraint("ck_district_blocks_tenant_not_null", "tenant_id is not null"); + + t.HasCheckConstraint("ck_district_blocks_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); + }); + }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DocControlStatus", b => { b.Property("Id") @@ -3569,6 +3713,145 @@ namespace AMREZ.EOP.Infrastructures.Migrations }); }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Province", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("is_deleted"); + + b.Property("IsSystem") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Meta") + .HasColumnType("jsonb"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NameI18n") + .HasColumnType("jsonb"); + + b.Property("OverridesGlobalId") + .HasColumnType("uuid"); + + b.Property("Scope") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("global"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpdatedBy") + .HasColumnType("text") + .HasColumnName("updated_by"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("TenantId"); + + b.HasIndex("Scope", "TenantId", "Code") + .IsUnique(); + + b.ToTable("provinces", "master", t => + { + t.HasCheckConstraint("ck_provinces_tenant_not_null", "tenant_id is not null"); + + t.HasCheckConstraint("ck_provinces_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); + }); + }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.ProvinceBlock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("GlobalId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("is_deleted"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpdatedBy") + .HasColumnType("text") + .HasColumnName("updated_by"); + + b.HasKey("Id"); + + b.HasIndex("GlobalId"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "GlobalId") + .IsUnique(); + + b.ToTable("province_blocks", "master", t => + { + t.HasCheckConstraint("ck_province_blocks_tenant_not_null", "tenant_id is not null"); + + t.HasCheckConstraint("ck_province_blocks_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); + }); + }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.QaStage", b => { b.Property("Id") @@ -4811,6 +5094,158 @@ namespace AMREZ.EOP.Infrastructures.Migrations }); }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Subdistrict", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(6) + .HasColumnType("character varying(6)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("DistrictId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("is_deleted"); + + b.Property("IsSystem") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Meta") + .HasColumnType("jsonb"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NameI18n") + .HasColumnType("jsonb"); + + b.Property("OverridesGlobalId") + .HasColumnType("uuid"); + + b.Property("Postcode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Scope") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("global"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpdatedBy") + .HasColumnType("text") + .HasColumnName("updated_by"); + + b.HasKey("Id"); + + b.HasAlternateKey("TenantId", "Id"); + + b.HasIndex("Code"); + + b.HasIndex("Postcode"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "DistrictId"); + + b.HasIndex("Scope", "TenantId", "Code") + .IsUnique(); + + b.ToTable("subdistricts", "master", t => + { + t.HasCheckConstraint("ck_subdistricts_tenant_not_null", "tenant_id is not null"); + + t.HasCheckConstraint("ck_subdistricts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); + }); + }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.SubdistrictBlock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("CreatedBy") + .HasColumnType("text") + .HasColumnName("created_by"); + + b.Property("GlobalId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("is_deleted"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UpdatedBy") + .HasColumnType("text") + .HasColumnName("updated_by"); + + b.HasKey("Id"); + + b.HasIndex("GlobalId"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "GlobalId") + .IsUnique(); + + b.ToTable("subdistrict_blocks", "master", t => + { + t.HasCheckConstraint("ck_subdistrict_blocks_tenant_not_null", "tenant_id is not null"); + + t.HasCheckConstraint("ck_subdistrict_blocks_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); + }); + }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Uom", b => { b.Property("Id") @@ -5538,6 +5973,27 @@ namespace AMREZ.EOP.Infrastructures.Migrations .IsRequired(); }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.District", b => + { + b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Province", "Province") + .WithMany("Districts") + .HasForeignKey("TenantId", "ProvinceId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Province"); + }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DistrictBlock", b => + { + b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.District", null) + .WithMany() + .HasForeignKey("GlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DocControlStatusBlock", b => { b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.DocControlStatus", null) @@ -5601,6 +6057,15 @@ namespace AMREZ.EOP.Infrastructures.Migrations .IsRequired(); }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.ProvinceBlock", b => + { + b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Province", null) + .WithMany() + .HasForeignKey("GlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.QaStageBlock", b => { b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.QaStage", null) @@ -5682,6 +6147,27 @@ namespace AMREZ.EOP.Infrastructures.Migrations .IsRequired(); }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Subdistrict", b => + { + b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.District", "District") + .WithMany("Subdistricts") + .HasForeignKey("TenantId", "DistrictId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("District"); + }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.SubdistrictBlock", b => + { + b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Subdistrict", null) + .WithMany() + .HasForeignKey("GlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.UomBlock", b => { b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Uom", null) @@ -5780,6 +6266,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations { b.Navigation("Children"); }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.District", b => + { + b.Navigation("Subdistricts"); + }); + + modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Province", b => + { + b.Navigation("Districts"); + }); #pragma warning restore 612, 618 } } diff --git a/AMREZ.EOP.Infrastructures/Repositories/AllergenRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/AllergenRepository.cs new file mode 100644 index 0000000..fa62952 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/AllergenRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class AllergenRepository : IAllergenRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public AllergenRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Allergens.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, AllergenListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Allergens.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Allergens + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.AllergenBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Allergens.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Allergens.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Allergen entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Allergens.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Allergen entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Allergens.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Allergens.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.AllergenBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new AllergenBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.AllergenBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/BrandRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/BrandRepository.cs new file mode 100644 index 0000000..f6c625c --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/BrandRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class BrandRepository : IBrandRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public BrandRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Brands.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, BrandListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Brands.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Brands + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.BrandBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Brands.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Brands.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Brand entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Brands.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Brand entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Brands.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Brands.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.BrandBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new BrandBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.BrandBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/CategoryRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/CategoryRepository.cs new file mode 100644 index 0000000..2e4020d --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/CategoryRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Category; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class CategoryRepository : ICategoryRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public CategoryRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Categories.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, CategoryListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Categories.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Categories + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.CategoryBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Categories.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Categories.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Category entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Categories.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Category entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Categories.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Categories.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.CategoryBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new CategoryBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.CategoryBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/ComplianceStatusRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/ComplianceStatusRepository.cs new file mode 100644 index 0000000..957a126 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/ComplianceStatusRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.ComplianceStatus; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class ComplianceStatusRepository : IComplianceStatusRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public ComplianceStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.ComplianceStatuses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, ComplianceStatusListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.ComplianceStatuses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.ComplianceStatuses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.ComplianceStatusBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.ComplianceStatuses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.ComplianceStatuses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(ComplianceStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.ComplianceStatuses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(ComplianceStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.ComplianceStatuses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.ComplianceStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.ComplianceStatusBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new ComplianceStatusBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.ComplianceStatusBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/CountryRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/CountryRepository.cs new file mode 100644 index 0000000..705bf0f --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/CountryRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Country; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class CountryRepository : ICountryRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public CountryRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Countries.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, CountryListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Countries.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Countries + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.CountryBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Countries.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Countries.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Country entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Countries.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Country entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Countries.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Countries.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.CountryBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new CountryBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.CountryBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/DistrictRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/DistrictRepository.cs new file mode 100644 index 0000000..cd4b936 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/DistrictRepository.cs @@ -0,0 +1,173 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.District; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class DistrictRepository : IDistrictRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public DistrictRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + return await db.Districts.AsNoTracking() + .Include(x => x.Province) + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync( + Guid tenantId, + DistrictListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Districts.AsNoTracking() + .Include(d => d.Province) + .Where(d => d.Scope == "tenant" && d.TenantId == tid && !d.IsDeleted); + + var overriddenIds = db.Districts + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.DistrictBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Districts.AsNoTracking() + .Include(d => d.Province) + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) + q = q.Where(x => x.IsActive); + + // Filter by Province + if (req.ProvinceId.HasValue && req.ProvinceId.Value != Guid.Empty) + q = q.Where(x => x.ProvinceId == req.ProvinceId.Value); + + if (!string.IsNullOrWhiteSpace(req.ProvinceCode)) + { + var pd = req.ProvinceCode.Trim(); + q = q.Where(x => x.Province.Code == pd); + } + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => + x.Code.Contains(s) || + x.Name.Contains(s) || + x.Code.Contains(s)); + } + + var total = await q.CountAsync(ct); + + var items = await q + .OrderBy(x => x.Province.Code) + .ThenBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + + return await db.Districts.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(District entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Districts.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(District entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Districts.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var d = await db.Districts.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (d is null) return 0; + + if (d.Scope == "global") + { + var exists = await db.DistrictBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new DistrictBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.DistrictBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (d.TenantId != tid) return 0; + + d.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/DocControlStatusRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/DocControlStatusRepository.cs new file mode 100644 index 0000000..738edd3 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/DocControlStatusRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Brand; +using AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class DocControlStatusRepository : IDocControlStatusRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public DocControlStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.DocControlStatuses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, DocControlStatusListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.DocControlStatuses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.DocControlStatuses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.DocControlStatusBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.DocControlStatuses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.DocControlStatuses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(DocControlStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.DocControlStatuses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(DocControlStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.DocControlStatuses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.DocControlStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.DocControlStatusBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new DocControlStatusBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.DocControlStatusBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/FuncTestRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/FuncTestRepository.cs new file mode 100644 index 0000000..d89ace6 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/FuncTestRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class FuncTestRepository : IFuncTestRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public FuncTestRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.FuncTests.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, FuncTestListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.FuncTests.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.FuncTests + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.FuncTestBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.FuncTests.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.FuncTests.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(FuncTest entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.FuncTests.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(FuncTest entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.FuncTests.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.FuncTests.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.FuncTestBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new FuncTestBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.FuncTestBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/HazardClassRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/HazardClassRepository.cs new file mode 100644 index 0000000..97551af --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/HazardClassRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class HazardClassRepository : IHazardClassRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public HazardClassRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.HazardClasses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, HazardClassListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.HazardClasses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.HazardClasses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.HazardClassBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.HazardClasses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.HazardClasses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(HazardClass entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.HazardClasses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(HazardClass entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.HazardClasses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.HazardClasses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.HazardClassBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new HazardClassBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.HazardClassBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/LanguageRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/LanguageRepository.cs new file mode 100644 index 0000000..89c072e --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/LanguageRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Language; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class LanguageRepository : ILanguageRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public LanguageRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Languages.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, LanguageListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Languages.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Languages + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.LanguageBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Languages.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Languages.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Language entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Languages.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Language entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Languages.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Languages.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.LanguageBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new LanguageBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.LanguageBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/ManufacturerRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/ManufacturerRepository.cs new file mode 100644 index 0000000..d43342e --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/ManufacturerRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Language; +using AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class ManufacturerRepository : IManufacturerRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public ManufacturerRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Manufacturers.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, ManufacturerListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Manufacturers.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Manufacturers + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.ManufacturerBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Manufacturers.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Manufacturers.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Manufacturer entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Manufacturers.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Manufacturer entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Manufacturers.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Manufacturers.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.ManufacturerBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new ManufacturerBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.ManufacturerBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/MarketRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/MarketRepository.cs new file mode 100644 index 0000000..6089c79 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/MarketRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer; +using AMREZ.EOP.Contracts.DTOs.MasterData.Market; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class MarketRepository : IMarketRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public MarketRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Markets.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, MarketListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Markets.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Markets + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.MarketBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Markets.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Markets.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Market entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Markets.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Market entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Markets.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Markets.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.MarketBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new MarketBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.MarketBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/PackingGroupRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/PackingGroupRepository.cs new file mode 100644 index 0000000..b822ffc --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/PackingGroupRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Market; +using AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class PackingGroupRepository : IPackingGroupRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public PackingGroupRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.PackingGroups.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, PackingGroupListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.PackingGroups.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.PackingGroups + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.PackingGroupBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.PackingGroups.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.PackingGroups.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(PackingGroup entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.PackingGroups.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(PackingGroup entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.PackingGroups.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.PackingGroups.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.PackingGroupBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new PackingGroupBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.PackingGroupBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/ProvinceRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/ProvinceRepository.cs new file mode 100644 index 0000000..70d3c7d --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/ProvinceRepository.cs @@ -0,0 +1,164 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Province; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class ProvinceRepository : IProvinceRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public ProvinceRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + return await db.Provinces.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync( + Guid tenantId, + ProvinceListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + // tenant scoped + var tenantQ = db.Provinces.AsNoTracking() + .Where(p => p.Scope == "tenant" && p.TenantId == tid && !p.IsDeleted); + + // overridden globals + var overriddenIds = db.Provinces + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + // blocked globals + var blockedIds = db.ProvinceBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + // global scoped (effective) + var globalsQ = db.Provinces.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) + q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => + x.Code.Contains(s) || + x.Name.Contains(s) || + x.Code.Contains(s)); + } + + var total = await q.CountAsync(ct); + + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + + return await db.Provinces.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Province entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Provinces.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Province entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Provinces.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var p = await db.Provinces.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (p is null) return 0; + + if (p.Scope == "global") + { + var exists = await db.ProvinceBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new ProvinceBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + + await db.ProvinceBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (p.TenantId != tid) return 0; + + p.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/QaStageRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/QaStageRepository.cs new file mode 100644 index 0000000..c408077 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/QaStageRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup; +using AMREZ.EOP.Contracts.DTOs.MasterData.QaStage; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class QaStageRepository : IQaStageRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public QaStageRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.QaStages.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, QaStageListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.QaStages.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.QaStages + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.QaStageBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.QaStages.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.QaStages.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(QaStage entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.QaStages.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(QaStage entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.QaStages.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.QaStages.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.QaStageBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new QaStageBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.QaStageBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/QcStatusRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/QcStatusRepository.cs new file mode 100644 index 0000000..8e44293 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/QcStatusRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class QcStatusRepository : IQcStatusRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public QcStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.QcStatuses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, QcStatusListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.QcStatuses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.QcStatuses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.QcStatusBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.QcStatuses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.QcStatuses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(QcStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.QcStatuses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(QcStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.QcStatuses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.QcStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.QcStatusBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new QcStatusBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.QcStatusBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/RecallClassRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/RecallClassRepository.cs new file mode 100644 index 0000000..bdbc699 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/RecallClassRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus; +using AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class RecallClassRepository : IRecallClassRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public RecallClassRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.RecallClasses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, RecallClassListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.RecallClasses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.RecallClasses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.RecallClassBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.RecallClasses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.RecallClasses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(RecallClass entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.RecallClasses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(RecallClass entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.RecallClasses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.RecallClasses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.RecallClassBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new RecallClassBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.RecallClassBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/RiskClassRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/RiskClassRepository.cs new file mode 100644 index 0000000..0e329c2 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/RiskClassRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass; +using AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class RiskClassRepository : IRiskClassRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public RiskClassRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.RiskClasses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, RiskClassListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.RiskClasses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.RiskClasses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.RiskClassBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.RiskClasses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.RiskClasses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(RiskClass entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.RiskClasses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(RiskClass entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.RiskClasses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.RiskClasses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.RiskClassBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new RiskClassBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.RiskClassBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/RouteRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/RouteRepository.cs new file mode 100644 index 0000000..54d1551 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/RouteRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass; +using AMREZ.EOP.Contracts.DTOs.MasterData.Route; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class RouteRepository : IRouteRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public RouteRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Routes.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, RouteListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Routes.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Routes + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.RouteBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Routes.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Routes.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Route entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Routes.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Route entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Routes.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Routes.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.RouteBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new RouteBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.RouteBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/RxScheduleRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/RxScheduleRepository.cs new file mode 100644 index 0000000..86c4781 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/RxScheduleRepository.cs @@ -0,0 +1,148 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Route; +using AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class RxScheduleRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public RxScheduleRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.RxSchedules.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, RxScheduleListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.RxSchedules.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.RxSchedules + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.RxScheduleBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.RxSchedules.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.RxSchedules.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(RxSchedule entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.RxSchedules.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(RxSchedule entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.RxSchedules.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.RxSchedules.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.RxScheduleBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new RxScheduleBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.RxScheduleBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/SpeciesRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/SpeciesRepository.cs new file mode 100644 index 0000000..7123977 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/SpeciesRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule; +using AMREZ.EOP.Contracts.DTOs.MasterData.Species; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class SpeciesRepository : ISpeciesRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public SpeciesRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Species.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, SpeciesListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Species.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Species + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.SpeciesBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Species.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Species.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Species entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Species.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Species entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Species.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Species.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.SpeciesBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new SpeciesBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.SpeciesBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/StabilityStatusRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/StabilityStatusRepository.cs new file mode 100644 index 0000000..04ec979 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/StabilityStatusRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Species; +using AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class StabilityStatusRepository : IStabilityStatusRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public StabilityStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.StabilityStatuses.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, StabilityStatusListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.StabilityStatuses.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.StabilityStatuses + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.StabilityStatusBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.StabilityStatuses.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.StabilityStatuses.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(StabilityStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.StabilityStatuses.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(StabilityStatus entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.StabilityStatuses.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.StabilityStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.StabilityStatusBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new StabilityStatusBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.StabilityStatusBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/SterilizationMethodRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/SterilizationMethodRepository.cs new file mode 100644 index 0000000..5be6f46 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/SterilizationMethodRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus; +using AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public class SterilizationMethodRepository : ISterilizationMethodRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public SterilizationMethodRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.SterilizationMethods.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, SterilizationMethodListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.SterilizationMethods.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.SterilizationMethods + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.SterilizationMethodBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.SterilizationMethods.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.SterilizationMethods.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(SterilizationMethod entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.SterilizationMethods.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(SterilizationMethod entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.SterilizationMethods.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.SterilizationMethods.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.SterilizationMethodBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new SterilizationMethodBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.SterilizationMethodBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/SubdistrictRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/SubdistrictRepository.cs new file mode 100644 index 0000000..1b42465 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/SubdistrictRepository.cs @@ -0,0 +1,187 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class SubdistrictRepository : ISubdistrictRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public SubdistrictRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + return await db.Subdistricts.AsNoTracking() + .Include(x => x.District) + .ThenInclude(d => d.Province) + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync( + Guid tenantId, + SubdistrictListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Subdistricts.AsNoTracking() + .Include(s => s.District) + .ThenInclude(d => d.Province) + .Where(s => s.Scope == "tenant" && s.TenantId == tid && !s.IsDeleted); + + var overriddenIds = db.Subdistricts + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.SubdistrictBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Subdistricts.AsNoTracking() + .Include(s => s.District) + .ThenInclude(d => d.Province) + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) + q = q.Where(x => x.IsActive); + + // Filter by District + if (req.DistrictId.HasValue && req.DistrictId.Value != Guid.Empty) + q = q.Where(x => x.DistrictId == req.DistrictId.Value); + + if (!string.IsNullOrWhiteSpace(req.DistrictCode)) + { + var dd = req.DistrictCode.Trim(); + q = q.Where(x => x.District.Code == dd); + } + + // Filter by Province + if (req.ProvinceId.HasValue && req.ProvinceId.Value != Guid.Empty) + q = q.Where(x => x.District.ProvinceId == req.ProvinceId.Value); + + if (!string.IsNullOrWhiteSpace(req.ProvinceCode)) + { + var pd = req.ProvinceCode.Trim(); + q = q.Where(x => x.District.Province.Code == pd); + } + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => + x.Code.Contains(s) || + x.Name.Contains(s) || + x.Code.Contains(s)); + } + + var total = await q.CountAsync(ct); + + var items = await q + .OrderBy(x => x.District.Province.Code) + .ThenBy(x => x.District.Code) + .ThenBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + + return await db.Subdistricts.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Subdistrict entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Subdistricts.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Subdistrict entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Subdistricts.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var s = await db.Subdistricts.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (s is null) return 0; + + if (s.Scope == "global") + { + var exists = await db.SubdistrictBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new SubdistrictBlock + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.SubdistrictBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (s.TenantId != tid) return 0; + + s.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/UomRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/UomRepository.cs new file mode 100644 index 0000000..44a57c6 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/UomRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod; +using AMREZ.EOP.Contracts.DTOs.MasterData.Uom; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public sealed class UomRepository : IUomRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public UomRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Uoms.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, UomListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Uoms.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Uoms + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.UomBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Uoms.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Uoms.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Uom entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Uoms.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Uom entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Uoms.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Uoms.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.UomBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new UomBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.UomBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file diff --git a/AMREZ.EOP.Infrastructures/Repositories/VvmRepository.cs b/AMREZ.EOP.Infrastructures/Repositories/VvmRepository.cs new file mode 100644 index 0000000..41b49b7 --- /dev/null +++ b/AMREZ.EOP.Infrastructures/Repositories/VvmRepository.cs @@ -0,0 +1,149 @@ +using AMREZ.EOP.Abstractions.Applications.Tenancy; +using AMREZ.EOP.Abstractions.Infrastructures.Repositories; +using AMREZ.EOP.Abstractions.Storage; +using AMREZ.EOP.Contracts.DTOs.Common; +using AMREZ.EOP.Contracts.DTOs.MasterData.Uom; +using AMREZ.EOP.Contracts.DTOs.MasterData.Vvm; +using AMREZ.EOP.Domain.Entities.MasterData; +using AMREZ.EOP.Infrastructures.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AMREZ.EOP.Infrastructures.Repositories; + +public class VvmRepository : IVvmRepository +{ + private readonly IDbScope _scope; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _http; + + public VvmRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http) + { + _scope = scope; + _tenantResolver = tenantResolver; + _http = http; + } + + private Guid EnsureTenant(out AppDbContext db) + { + var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); + var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant"); + _scope.EnsureForTenant(tc); + db = _scope.Get(); + + if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty) + return g; + + var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id; + if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing"); + + var cfg = db.Set() + .AsNoTracking() + .FirstOrDefault(x => x.TenantKey == key); + if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); + return cfg.TenantId; + } + + public async Task GetAsync(Guid id, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + return await db.Vvms.AsNoTracking() + .Where(x => x.Id == id && !x.IsDeleted) + .Where(x => x.Scope == "tenant" ? x.TenantId == tid : true) + .FirstOrDefaultAsync(ct); + } + + public async Task> SearchEffectiveAsync(Guid tenantId, VvmListRequest req, + CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var tenantQ = db.Vvms.AsNoTracking() + .Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted); + + var overriddenIds = db.Vvms + .Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null) + .Select(t => t.OverridesGlobalId!.Value); + + var blockedIds = db.VvmBlocks + .Where(b => b.TenantId == tid) + .Select(b => b.GlobalId); + + var globalsQ = db.Vvms.AsNoTracking() + .Where(g => g.Scope == "global" && !g.IsDeleted) + .Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id)); + + var q = tenantQ.Union(globalsQ); + + if (!req.IncludeInactive) q = q.Where(x => x.IsActive); + + if (!string.IsNullOrWhiteSpace(req.Search)) + { + var s = req.Search.Trim(); + q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s)); + } + + var total = await q.CountAsync(ct); + var items = await q + .OrderBy(x => x.Code) + .ThenBy(x => x.Name) + .Skip((req.Page - 1) * req.PageSize) + .Take(req.PageSize) + .ToListAsync(ct); + + return new PagedResponse(req.Page, req.PageSize, total, items); + } + + public async Task CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + var norm = code.Trim(); + return await db.Vvms.AnyAsync(b => + !b.IsDeleted && + b.Code == norm && + (b.Scope == "tenant" && b.TenantId == tid), ct); + } + + public async Task AddAsync(Vvm entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + await db.Vvms.AddAsync(entity, ct); + await db.SaveChangesAsync(ct); + } + + public async Task UpdateAsync(Vvm entity, CancellationToken ct = default) + { + EnsureTenant(out var db); + db.Vvms.Update(entity); + await db.SaveChangesAsync(ct); + } + + public async Task SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default) + { + var tid = EnsureTenant(out var db); + + var b = await db.Vvms.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct); + if (b is null) return 0; + + if (b.Scope == "global") + { + var exists = await db.VvmBlocks + .AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct); + if (exists) return 0; + + var block = new VvmBlock() + { + Id = Guid.NewGuid(), + TenantId = tid, + GlobalId = id + }; + await db.VvmBlocks.AddAsync(block, ct); + return await db.SaveChangesAsync(ct); + } + + if (b.TenantId != tid) return 0; + + b.IsDeleted = true; + return await db.SaveChangesAsync(ct); + } +} \ No newline at end of file