using BakeryErp.Infrastructure.Database; namespace BakeryErp.Infrastructure.Repositories; public sealed class ItemRepository { private readonly DatabaseOptions _options; public ItemRepository(DatabaseOptions options) { _options = options; } public IReadOnlyList List() => LocalDataStoreFile.Load(_options).Items .OrderByDescending(static item => item.IsActive) .ThenBy(static item => item.Sku) .ToList(); public LocalItem? Find(Guid id) => LocalDataStoreFile.Load(_options).Items.FirstOrDefault(item => item.Id == id); public SaveMasterDataResult Save(LocalItem item) { var store = LocalDataStoreFile.Load(_options); var sku = item.Sku?.Trim().ToUpperInvariant() ?? string.Empty; var name = item.Name?.Trim() ?? string.Empty; var categoryName = item.CategoryName?.Trim() ?? string.Empty; var baseUnit = item.BaseUnit?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(sku) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(baseUnit)) { return SaveMasterDataResult.ValidationError("品項代號、名稱與基本單位為必填。"); } if (store.Items.Any(existing => existing.Id != item.Id && string.Equals(existing.Sku, sku, StringComparison.OrdinalIgnoreCase))) { return SaveMasterDataResult.ValidationError("品項代號已存在。"); } var existingItem = store.Items.FirstOrDefault(existing => existing.Id == item.Id); if (existingItem is null) { item.Id = item.Id == Guid.Empty ? Guid.NewGuid() : item.Id; item.Sku = sku; item.Name = name; item.CategoryName = categoryName; item.BaseUnit = baseUnit; item.CreatedAt = DateTime.UtcNow; item.IsActive = true; store.Items.Add(item); } else { existingItem.Sku = sku; existingItem.Name = name; existingItem.ItemType = item.ItemType; existingItem.CategoryName = categoryName; existingItem.BaseUnit = baseUnit; existingItem.SafetyStockQuantity = item.SafetyStockQuantity; existingItem.ShelfLifeDays = item.ShelfLifeDays; existingItem.UpdatedAt = DateTime.UtcNow; } LocalDataStoreFile.Save(_options, store); return SaveMasterDataResult.Success(); } public void SetActive(Guid id, bool isActive) { var store = LocalDataStoreFile.Load(_options); var item = store.Items.FirstOrDefault(existing => existing.Id == id); if (item is null) { return; } item.IsActive = isActive; item.UpdatedAt = DateTime.UtcNow; LocalDataStoreFile.Save(_options, store); } } public sealed class StoreRepository { private readonly DatabaseOptions _options; public StoreRepository(DatabaseOptions options) { _options = options; } public IReadOnlyList List() => LocalDataStoreFile.Load(_options).Stores .OrderByDescending(static store => store.IsActive) .ThenBy(static store => store.Code) .ToList(); public LocalStore? Find(Guid id) => LocalDataStoreFile.Load(_options).Stores.FirstOrDefault(store => store.Id == id); public SaveMasterDataResult Save(LocalStore storeInfo) { var store = LocalDataStoreFile.Load(_options); var code = storeInfo.Code?.Trim().ToUpperInvariant() ?? string.Empty; var name = storeInfo.Name?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name)) { return SaveMasterDataResult.ValidationError("門市代號與名稱為必填。"); } if (store.Stores.Any(existing => existing.Id != storeInfo.Id && string.Equals(existing.Code, code, StringComparison.OrdinalIgnoreCase))) { return SaveMasterDataResult.ValidationError("門市代號已存在。"); } var existingStore = store.Stores.FirstOrDefault(existing => existing.Id == storeInfo.Id); if (existingStore is null) { storeInfo.Id = storeInfo.Id == Guid.Empty ? Guid.NewGuid() : storeInfo.Id; storeInfo.Code = code; storeInfo.Name = name; storeInfo.Phone = BlankToNull(storeInfo.Phone); storeInfo.Address = BlankToNull(storeInfo.Address); storeInfo.CreatedAt = DateTime.UtcNow; storeInfo.IsActive = true; store.Stores.Add(storeInfo); } else { existingStore.Code = code; existingStore.Name = name; existingStore.StoreType = storeInfo.StoreType; existingStore.Phone = BlankToNull(storeInfo.Phone); existingStore.Address = BlankToNull(storeInfo.Address); existingStore.UpdatedAt = DateTime.UtcNow; } LocalDataStoreFile.Save(_options, store); return SaveMasterDataResult.Success(); } public void SetActive(Guid id, bool isActive) { var store = LocalDataStoreFile.Load(_options); var storeInfo = store.Stores.FirstOrDefault(existing => existing.Id == id); if (storeInfo is null) { return; } storeInfo.IsActive = isActive; storeInfo.UpdatedAt = DateTime.UtcNow; LocalDataStoreFile.Save(_options, store); } private static string? BlankToNull(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } public sealed class WarehouseRepository { private readonly DatabaseOptions _options; public WarehouseRepository(DatabaseOptions options) { _options = options; } public IReadOnlyList List() => LocalDataStoreFile.Load(_options).Warehouses .OrderByDescending(static warehouse => warehouse.IsActive) .ThenBy(static warehouse => warehouse.Code) .ToList(); public LocalWarehouse? Find(Guid id) => LocalDataStoreFile.Load(_options).Warehouses.FirstOrDefault(warehouse => warehouse.Id == id); public IReadOnlyList ActiveStoreNames() => LocalDataStoreFile.Load(_options).Stores .Where(static store => store.IsActive) .Select(static store => store.Name) .ToList(); public SaveMasterDataResult Save(LocalWarehouse warehouse) { var store = LocalDataStoreFile.Load(_options); var code = warehouse.Code?.Trim().ToUpperInvariant() ?? string.Empty; var name = warehouse.Name?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(name)) { return SaveMasterDataResult.ValidationError("倉庫代號與名稱為必填。"); } if (store.Warehouses.Any(existing => existing.Id != warehouse.Id && string.Equals(existing.Code, code, StringComparison.OrdinalIgnoreCase))) { return SaveMasterDataResult.ValidationError("倉庫代號已存在。"); } var existingWarehouse = store.Warehouses.FirstOrDefault(existing => existing.Id == warehouse.Id); if (existingWarehouse is null) { warehouse.Id = warehouse.Id == Guid.Empty ? Guid.NewGuid() : warehouse.Id; warehouse.Code = code; warehouse.Name = name; warehouse.StoreName = BlankToNull(warehouse.StoreName); warehouse.CreatedAt = DateTime.UtcNow; warehouse.IsActive = true; store.Warehouses.Add(warehouse); } else { existingWarehouse.Code = code; existingWarehouse.Name = name; existingWarehouse.StoreName = BlankToNull(warehouse.StoreName); existingWarehouse.WarehouseType = warehouse.WarehouseType; existingWarehouse.UpdatedAt = DateTime.UtcNow; } LocalDataStoreFile.Save(_options, store); return SaveMasterDataResult.Success(); } public void SetActive(Guid id, bool isActive) { var store = LocalDataStoreFile.Load(_options); var warehouse = store.Warehouses.FirstOrDefault(existing => existing.Id == id); if (warehouse is null) { return; } warehouse.IsActive = isActive; warehouse.UpdatedAt = DateTime.UtcNow; LocalDataStoreFile.Save(_options, store); } private static string? BlankToNull(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } public sealed record SaveMasterDataResult(bool IsSuccess, string? ErrorMessage) { public static SaveMasterDataResult Success() => new(true, null); public static SaveMasterDataResult ValidationError(string message) => new(false, message); }