Add Optima document attributes to place_order
This commit is contained in:
@@ -3,6 +3,7 @@ using SmartB2B.Optima.Adapter;
|
||||
using SmartB2B.Sync.Contracts;
|
||||
using SmartB2B.Sync.Service.Orders;
|
||||
using SmartB2B.Sync.Service.Rpc;
|
||||
using WampSharp.V2.Core.Contracts;
|
||||
|
||||
var tests = new (string Name, Func<Task> Run)[]
|
||||
{
|
||||
@@ -14,6 +15,11 @@ var tests = new (string Name, Func<Task> Run)[]
|
||||
("Tryb brutto wybiera price_brutto", GrossDocumentSelectsGrossPrice),
|
||||
("Brak ceny wymaganej przez definicję jest stabilnym błędem", RequiredPriceIsEnforced),
|
||||
("Handler przekazuje pełny kontrakt do Optimy", HandlerMapsOptimaContract),
|
||||
("Handler przyjmuje i pomija opcjonalne extraFields", HandlerMapsOptionalExtraFields),
|
||||
("Handler odrzuca błędne extraFields", HandlerRejectsInvalidExtraFields),
|
||||
("Fasada przypisuje atrybuty nagłówka przed zapisem", FacadeAddsDocumentAttributes),
|
||||
("Fasada odrzuca nieznany kod atrybutu", FacadeRejectsMissingAttribute),
|
||||
("Fasada zgłasza odrzuconą wartość atrybutu", FacadeRejectsAttributeValue),
|
||||
("Wynik Optimy zachowuje kwargs WAMP", HandlerReturnsKeywordResult),
|
||||
("Operacje zamówień są serializowane", ConcurrentOrdersAreSerialized),
|
||||
("Fasada COM działa na jednym wątku STA", FacadeRunsOnSingleStaThread),
|
||||
@@ -102,6 +108,105 @@ static async Task HandlerMapsOptimaContract()
|
||||
AssertEqual(123m, request.Items[0].UnitPriceGross, "brutto");
|
||||
}
|
||||
|
||||
static async Task HandlerMapsOptionalExtraFields()
|
||||
{
|
||||
using var adapter = new CapturingAdapter();
|
||||
var handler = new PlaceOrderHandler(adapter, CreateConfiguration());
|
||||
await handler.HandleAsync(CreateInvocation(new Dictionary<string, object?>
|
||||
{
|
||||
["SOURCE"] = "B2B",
|
||||
["NOTE"] = ""
|
||||
}), CancellationToken.None);
|
||||
AssertEqual("B2B", adapter.LastRequest?.ExtraFields["SOURCE"], "wartość SOURCE");
|
||||
AssertEqual("", adapter.LastRequest?.ExtraFields["NOTE"], "pusta wartość");
|
||||
|
||||
await handler.HandleAsync(CreateInvocation(), CancellationToken.None);
|
||||
AssertEqual(0, adapter.LastRequest?.ExtraFields.Count, "brak extraFields");
|
||||
await handler.HandleAsync(CreateInvocation(new Dictionary<string, string>()), CancellationToken.None);
|
||||
AssertEqual(0, adapter.LastRequest?.ExtraFields.Count, "puste extraFields");
|
||||
}
|
||||
|
||||
static async Task HandlerRejectsInvalidExtraFields()
|
||||
{
|
||||
foreach (var invalid in new object?[]
|
||||
{
|
||||
null,
|
||||
"text",
|
||||
new[] { "SOURCE" },
|
||||
new Dictionary<string, object?> { ["SOURCE"] = 123 },
|
||||
new Dictionary<string, object?> { [" "] = "value" },
|
||||
new Dictionary<string, object?> { ["SOURCE"] = "one", [" SOURCE "] = "two" }
|
||||
})
|
||||
{
|
||||
using var adapter = new CapturingAdapter();
|
||||
var handler = new PlaceOrderHandler(adapter, CreateConfiguration());
|
||||
try
|
||||
{
|
||||
await handler.HandleAsync(CreateInvocation(invalid, includeExtraFields: true), CancellationToken.None);
|
||||
throw new InvalidOperationException("Oczekiwano odrzucenia extraFields.");
|
||||
}
|
||||
catch (WampException exception) when (exception.ErrorUri == "eu.smartb2b.erp.invalid_order_lines")
|
||||
{
|
||||
AssertEqual(null, adapter.LastRequest, "brak zapisu zamówienia");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Task FacadeAddsDocumentAttributes()
|
||||
{
|
||||
var session = new FakeAttributeSession(new Dictionary<string, int> { ["SOURCE"] = 7, ["NOTE"] = 8 });
|
||||
var document = new FakeAttributeDocument();
|
||||
LateBoundOptimaComFacade.AddDocumentAttributes(session, document, new Dictionary<string, string>
|
||||
{
|
||||
["SOURCE"] = "B2B",
|
||||
["NOTE"] = ""
|
||||
});
|
||||
AssertEqual(2, document.Atrybuty.Items.Count, "liczba atrybutów");
|
||||
AssertEqual(7, document.Atrybuty.Items[0].DeAID, "ID SOURCE");
|
||||
AssertEqual("B2B", document.Atrybuty.Items[0].Wartosc, "wartość SOURCE");
|
||||
AssertEqual(8, document.Atrybuty.Items[1].DeAID, "ID NOTE");
|
||||
AssertEqual("", document.Atrybuty.Items[1].Wartosc, "pusta wartość NOTE");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
static Task FacadeRejectsMissingAttribute()
|
||||
{
|
||||
var session = new FakeAttributeSession(new Dictionary<string, int>());
|
||||
var document = new FakeAttributeDocument();
|
||||
try
|
||||
{
|
||||
LateBoundOptimaComFacade.AddDocumentAttributes(session, document, new Dictionary<string, string>
|
||||
{
|
||||
["MISSING"] = "value"
|
||||
});
|
||||
throw new InvalidOperationException("Oczekiwano błędu nieznanego kodu.");
|
||||
}
|
||||
catch (ErpOperationException exception) when (exception.ErrorUri == "eu.smartb2b.erp.document_attribute_not_found")
|
||||
{
|
||||
AssertEqual(0, document.Atrybuty.Items.Count, "brak dodanego atrybutu");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
static Task FacadeRejectsAttributeValue()
|
||||
{
|
||||
var session = new FakeAttributeSession(new Dictionary<string, int> { ["SOURCE"] = 7 });
|
||||
var document = new FakeAttributeDocument();
|
||||
try
|
||||
{
|
||||
LateBoundOptimaComFacade.AddDocumentAttributes(session, document, new Dictionary<string, string>
|
||||
{
|
||||
["SOURCE"] = "REJECTED"
|
||||
});
|
||||
throw new InvalidOperationException("Oczekiwano błędu wartości atrybutu.");
|
||||
}
|
||||
catch (ErpOperationException exception) when (exception.ErrorUri == "eu.smartb2b.erp.invalid_document_attribute")
|
||||
{
|
||||
AssertEqual("SOURCE", exception.Details["attributeCode"], "kod w błędzie");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
static async Task HandlerReturnsKeywordResult()
|
||||
{
|
||||
using var adapter = new CapturingAdapter();
|
||||
@@ -135,6 +240,7 @@ static Task FacadeRunsOnSingleStaThread()
|
||||
AssertEqual(1, facade.ThreadIds.Distinct().Count(), "liczba wątków COM");
|
||||
AssertEqual(ApartmentState.STA, facade.ApartmentStates.Distinct().Single(), "apartment COM");
|
||||
AssertEqual("Buffer", info.Details?["Save mode"], "tryb zapisu w diagnostyce");
|
||||
AssertEqual(true, info.Capabilities.Contains("extraFields"), "możliwość extraFields");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -152,9 +258,9 @@ static async Task AdapterSerializesComCalls()
|
||||
AssertEqual(1, facade.MaximumConcurrency, "maksymalna równoległość fasady COM");
|
||||
}
|
||||
|
||||
static RpcInvocation CreateInvocation() => new(
|
||||
[],
|
||||
new Dictionary<string, object?>
|
||||
static RpcInvocation CreateInvocation(object? extraFields = null, bool includeExtraFields = false)
|
||||
{
|
||||
var arguments = new Dictionary<string, object?>
|
||||
{
|
||||
["currency_iso"] = "eur",
|
||||
["companyErpId"] = "123",
|
||||
@@ -172,7 +278,10 @@ static RpcInvocation CreateInvocation() => new(
|
||||
Discount = 12.5m
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (includeExtraFields || extraFields is not null) arguments["extraFields"] = extraFields;
|
||||
return new RpcInvocation([], arguments);
|
||||
}
|
||||
|
||||
static ErpAdapterConfiguration CreateConfiguration() => new(
|
||||
new ErpConnectionOptions("Firma demo", "ADMIN", "", "Server=.;Database=demo"),
|
||||
@@ -317,3 +426,69 @@ file sealed class CapturingComFacade : IOptimaComFacade
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public sealed class FakeAttributeSession
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, int> _definitions;
|
||||
|
||||
public FakeAttributeSession(IReadOnlyDictionary<string, int> definitions) => _definitions = definitions;
|
||||
|
||||
public FakeDefinitionCollection CreateObject(string name)
|
||||
{
|
||||
if (name != "CDN.DefAtrybuty") throw new InvalidOperationException($"Nieoczekiwana kolekcja: {name}");
|
||||
return new FakeDefinitionCollection(_definitions);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FakeDefinitionCollection
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, int> _definitions;
|
||||
|
||||
public FakeDefinitionCollection(IReadOnlyDictionary<string, int> definitions) => _definitions = definitions;
|
||||
|
||||
public FakeDefinition? Item(string filter)
|
||||
{
|
||||
if (!filter.StartsWith("DeA_Typ=4 AND DeA_Kod='", StringComparison.Ordinal))
|
||||
throw new InvalidOperationException($"Niepoprawny filtr: {filter}");
|
||||
var code = filter["DeA_Typ=4 AND DeA_Kod='".Length..^1].Replace("''", "'", StringComparison.Ordinal);
|
||||
return _definitions.TryGetValue(code, out var id) ? new FakeDefinition(id) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FakeDefinition
|
||||
{
|
||||
public FakeDefinition(int id) => ID = id;
|
||||
public int ID { get; }
|
||||
}
|
||||
|
||||
public sealed class FakeAttributeDocument
|
||||
{
|
||||
public FakeAttributeCollection Atrybuty { get; } = new();
|
||||
}
|
||||
|
||||
public sealed class FakeAttributeCollection
|
||||
{
|
||||
public List<FakeDocumentAttribute> Items { get; } = [];
|
||||
|
||||
public FakeDocumentAttribute AddNew()
|
||||
{
|
||||
var attribute = new FakeDocumentAttribute();
|
||||
Items.Add(attribute);
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FakeDocumentAttribute
|
||||
{
|
||||
public int DeAID { get; set; }
|
||||
private string? _value;
|
||||
public string? Wartosc
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (value == "REJECTED") throw new ArgumentException("Niepoprawna wartość.");
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user