Add NbtPercent column to GoodsReceipts and GoodsReceiptItems
This commit is contained in:
@@ -7,3 +7,6 @@ public record SysConfigResponse(string Key, string Value, DateTime UpdatedAt, Gu
|
|||||||
public record SetSysConfigRequest(string Value, Guid? UserId);
|
public record SetSysConfigRequest(string Value, Guid? UserId);
|
||||||
|
|
||||||
public record SetInvoiceCreationModeRequest(InvoiceCreationMode Mode);
|
public record SetInvoiceCreationModeRequest(InvoiceCreationMode Mode);
|
||||||
|
|
||||||
|
/// <summary>Which taxes apply to a GRN, whether each must be filled in, and the rate new GRNs start with.</summary>
|
||||||
|
public record GrnTaxSettings(TaxMode VatMode, decimal VatDefaultRate, TaxMode NbtMode, decimal NbtDefaultRate);
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow)
|
|||||||
public const string KeyCustomWarrantyOverrideEnabled = "Feature.CustomWarrantyOverride";
|
public const string KeyCustomWarrantyOverrideEnabled = "Feature.CustomWarrantyOverride";
|
||||||
public const string KeyDefaultWarrantyPeriodDays = "Feature.DefaultWarrantyPeriodDays";
|
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 =
|
private static readonly string[] BooleanFeatureKeys =
|
||||||
[
|
[
|
||||||
KeySerialNumberTrackingEnabled,
|
KeySerialNumberTrackingEnabled,
|
||||||
@@ -83,6 +91,33 @@ public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow)
|
|||||||
return config is not null && int.TryParse(config.Value, out var days) ? days : 0;
|
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)
|
private async Task<bool> GetBoolAsync(string key)
|
||||||
{
|
{
|
||||||
var config = await repo.GetByKeyAsync(key);
|
var config = await repo.GetByKeyAsync(key);
|
||||||
@@ -98,6 +133,15 @@ public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow)
|
|||||||
if (BooleanFeatureKeys.Contains(key) && !bool.TryParse(value, out _))
|
if (BooleanFeatureKeys.Contains(key) && !bool.TryParse(value, out _))
|
||||||
throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Allowed values: true, false.");
|
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))
|
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.");
|
throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Must be a non-negative integer.");
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Distribution.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddGrnNbt : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<decimal>(
|
||||||
|
name: "NbtPercent",
|
||||||
|
table: "GoodsReceipts",
|
||||||
|
type: "decimal(5,2)",
|
||||||
|
precision: 5,
|
||||||
|
scale: 2,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<decimal>(
|
||||||
|
name: "NbtPercent",
|
||||||
|
table: "GoodsReceiptItems",
|
||||||
|
type: "decimal(5,2)",
|
||||||
|
precision: 5,
|
||||||
|
scale: 2,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "GoodsReceiptItems",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("44000000-0000-0000-0000-000000000001"),
|
||||||
|
column: "NbtPercent",
|
||||||
|
value: 0m);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "GoodsReceiptItems",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("44000000-0000-0000-0000-000000000002"),
|
||||||
|
column: "NbtPercent",
|
||||||
|
value: 0m);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "GoodsReceipts",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("43000000-0000-0000-0000-000000000001"),
|
||||||
|
column: "NbtPercent",
|
||||||
|
value: 0m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NbtPercent",
|
||||||
|
table: "GoodsReceipts");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NbtPercent",
|
||||||
|
table: "GoodsReceiptItems");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -446,6 +446,10 @@ namespace Distribution.Migrations
|
|||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<decimal>("NbtPercent")
|
||||||
|
.HasPrecision(5, 2)
|
||||||
|
.HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
b.Property<DateTime>("ReceivedAt")
|
b.Property<DateTime>("ReceivedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@@ -473,6 +477,7 @@ namespace Distribution.Migrations
|
|||||||
FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"),
|
FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"),
|
||||||
FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"),
|
FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"),
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
NbtPercent = 0m,
|
||||||
ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||||
VatPercent = 0m
|
VatPercent = 0m
|
||||||
});
|
});
|
||||||
@@ -509,6 +514,10 @@ namespace Distribution.Migrations
|
|||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<decimal>("NbtPercent")
|
||||||
|
.HasPrecision(5, 2)
|
||||||
|
.HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
b.Property<int>("RejectedQuantity")
|
b.Property<int>("RejectedQuantity")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -541,6 +550,7 @@ namespace Distribution.Migrations
|
|||||||
FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"),
|
FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"),
|
||||||
FreeQuantity = 0,
|
FreeQuantity = 0,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
NbtPercent = 0m,
|
||||||
RejectedQuantity = 0,
|
RejectedQuantity = 0,
|
||||||
UnitCost = 0m,
|
UnitCost = 0m,
|
||||||
VatPercent = 0m
|
VatPercent = 0m
|
||||||
@@ -555,6 +565,7 @@ namespace Distribution.Migrations
|
|||||||
FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"),
|
FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"),
|
||||||
FreeQuantity = 0,
|
FreeQuantity = 0,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
NbtPercent = 0m,
|
||||||
RejectedQuantity = 2,
|
RejectedQuantity = 2,
|
||||||
UnitCost = 0m,
|
UnitCost = 0m,
|
||||||
VatPercent = 0m
|
VatPercent = 0m
|
||||||
|
|||||||
Reference in New Issue
Block a user