Files
ERP_Distribution/Distribution/Features/SysConfig/SysConfigService.cs
T

152 lines
6.6 KiB
C#

using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.SysConfig;
public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow)
{
public const string KeyInvoiceCreationMode = "InvoiceCreationMode";
public const string KeySerialNumberTrackingEnabled = "Feature.SerialNumberTracking";
public const string KeyBatchTrackingEnabled = "Feature.BatchTracking";
public const string KeyExpiryDateTrackingEnabled = "Feature.ExpiryDateTracking";
public const string KeyWarrantyTrackingEnabled = "Feature.WarrantyTracking";
public const string KeyCustomWarrantyOverrideEnabled = "Feature.CustomWarrantyOverride";
public const string KeyDefaultWarrantyPeriodDays = "Feature.DefaultWarrantyPeriodDays";
public const string KeyGrnVatMode = "Grn.Tax.Vat.Mode";
public const string KeyGrnVatRate = "Grn.Tax.Vat.DefaultRate";
public const string KeyGrnNbtMode = "Grn.Tax.Nbt.Mode";
public const string KeyGrnNbtRate = "Grn.Tax.Nbt.DefaultRate";
private static readonly string[] TaxModeKeys = [KeyGrnVatMode, KeyGrnNbtMode];
private static readonly string[] TaxRateKeys = [KeyGrnVatRate, KeyGrnNbtRate];
private static readonly string[] BooleanFeatureKeys =
[
KeySerialNumberTrackingEnabled,
KeyBatchTrackingEnabled,
KeyExpiryDateTrackingEnabled,
KeyWarrantyTrackingEnabled,
KeyCustomWarrantyOverrideEnabled,
];
public async Task<List<SysConfigResponse>> GetAllAsync()
{
var configs = await repo.GetAllAsync();
return configs.Select(ToResponse).ToList();
}
public async Task<SysConfigResponse?> GetByKeyAsync(string key)
{
var config = await repo.GetByKeyAsync(key);
return config is null ? null : ToResponse(config);
}
public async Task<SysConfigResponse> SetAsync(string key, string value, Guid userId, CancellationToken ct = default)
{
ValidateValue(key, value);
var config = new Models.SysConfig
{
Key = key,
Value = value,
UpdatedAt = DateTime.Now,
UpdatedBy = userId
};
await uow.BeginAsync(ct);
try
{
await repo.UpsertAsync(config);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
var saved = await repo.GetByKeyAsync(key);
return ToResponse(saved!);
}
/// <summary>
/// Returns the current InvoiceCreationMode, defaulting to ModeB if not configured.
/// ModeA = invoice auto-created on ShopPO confirmation.
/// ModeB = invoice created as a separate explicit step.
/// </summary>
public async Task<InvoiceCreationMode> GetInvoiceCreationModeAsync()
{
var config = await repo.GetByKeyAsync(KeyInvoiceCreationMode);
if (config is null) return InvoiceCreationMode.ModeB;
return Enum.TryParse<InvoiceCreationMode>(config.Value, out var mode) ? mode : InvoiceCreationMode.ModeB;
}
public async Task<bool> GetSerialNumberTrackingEnabledAsync() => await GetBoolAsync(KeySerialNumberTrackingEnabled);
public async Task<bool> GetBatchTrackingEnabledAsync() => await GetBoolAsync(KeyBatchTrackingEnabled);
public async Task<bool> GetExpiryDateTrackingEnabledAsync() => await GetBoolAsync(KeyExpiryDateTrackingEnabled);
public async Task<bool> GetWarrantyTrackingEnabledAsync() => await GetBoolAsync(KeyWarrantyTrackingEnabled);
public async Task<bool> GetCustomWarrantyOverrideEnabledAsync() => await GetBoolAsync(KeyCustomWarrantyOverrideEnabled);
public async Task<int> GetDefaultWarrantyPeriodDaysAsync()
{
var config = await repo.GetByKeyAsync(KeyDefaultWarrantyPeriodDays);
return config is not null && int.TryParse(config.Value, out var days) ? days : 0;
}
/// <summary>
/// GRN tax settings. Unconfigured, VAT is Optional (how GRNs behaved before this setting existed)
/// and NBT is Disabled.
/// </summary>
public async Task<GrnTaxSettings> GetGrnTaxSettingsAsync() => new(
await GetTaxModeAsync(KeyGrnVatMode, TaxMode.Optional),
await GetRateAsync(KeyGrnVatRate),
await GetTaxModeAsync(KeyGrnNbtMode, TaxMode.Disabled),
await GetRateAsync(KeyGrnNbtRate));
private async Task<TaxMode> GetTaxModeAsync(string key, TaxMode fallback)
{
var config = await repo.GetByKeyAsync(key);
return config is not null && Enum.TryParse<TaxMode>(config.Value, out var mode) && Enum.IsDefined(mode)
? mode
: fallback;
}
private async Task<decimal> GetRateAsync(string key)
{
var config = await repo.GetByKeyAsync(key);
return config is not null
&& decimal.TryParse(config.Value, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var rate)
? rate
: 0m;
}
private async Task<bool> GetBoolAsync(string key)
{
var config = await repo.GetByKeyAsync(key);
return config is not null && bool.TryParse(config.Value, out var enabled) && enabled;
}
private static void ValidateValue(string key, string value)
{
if (key == KeyInvoiceCreationMode && !Enum.TryParse<InvoiceCreationMode>(value, out _))
throw new InvalidOperationException(
$"Invalid value '{value}' for '{key}'. Allowed values: {string.Join(", ", Enum.GetNames<InvoiceCreationMode>())}.");
if (BooleanFeatureKeys.Contains(key) && !bool.TryParse(value, out _))
throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Allowed values: true, false.");
if (TaxModeKeys.Contains(key) && (!Enum.TryParse<TaxMode>(value, out var taxMode) || !Enum.IsDefined(taxMode)))
throw new InvalidOperationException(
$"Invalid value '{value}' for '{key}'. Allowed values: {string.Join(", ", Enum.GetNames<TaxMode>())}.");
if (TaxRateKeys.Contains(key)
&& (!decimal.TryParse(value, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var rate)
|| rate < 0 || rate > 100))
throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Must be a percentage from 0 to 100.");
if (key == KeyDefaultWarrantyPeriodDays && (!int.TryParse(value, out var days) || days < 0))
throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Must be a non-negative integer.");
}
private static SysConfigResponse ToResponse(Models.SysConfig c) =>
new(c.Key, c.Value, c.UpdatedAt, c.UpdatedBy);
}