152 lines
6.6 KiB
C#
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);
|
|
}
|