Compare commits
10 Commits
01ffd22fca
...
ba05b5f37e
| Author | SHA1 | Date |
|---|---|---|
|
|
ba05b5f37e | |
|
|
62e8294448 | |
|
|
fdcd47fa75 | |
|
|
056a69ecc8 | |
|
|
0d9ced990a | |
|
|
4ef318973f | |
|
|
346d433196 | |
|
|
1b6aae83f1 | |
|
|
e13e32dc57 | |
|
|
7e4d0a85e8 |
|
|
@ -10,11 +10,12 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
<!--<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.2.0" />-->
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -38,7 +39,7 @@
|
||||||
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\Debug\net9.0\AyCode.Services.Server.dll</HintPath>
|
<HintPath>..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\Debug\net9.0\AyCode.Services.Server.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Microsoft.AspNetCore.SignalR.Core">
|
<Reference Include="Microsoft.AspNetCore.SignalR.Core">
|
||||||
<HintPath>C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\9.0.10\ref\net9.0\Microsoft.AspNetCore.SignalR.Core.dll</HintPath>
|
<HintPath>C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\9.0.11\ref\net9.0\Microsoft.AspNetCore.SignalR.Core.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Mango.Nop.Core">
|
<Reference Include="Mango.Nop.Core">
|
||||||
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\Debug\net9.0\Mango.Nop.Core.dll</HintPath>
|
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\Debug\net9.0\Mango.Nop.Core.dll</HintPath>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
using FruitBank.Common.Interfaces;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Server.Interfaces;
|
||||||
|
|
||||||
|
public interface IStockSignalREndpointServer : IStockSignalREndpointCommon
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Loggers;
|
||||||
using AyCode.Models.Server.DynamicMethods;
|
using AyCode.Models.Server.DynamicMethods;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
|
|
@ -29,11 +30,15 @@ namespace FruitBank.Common.Server.Services.SignalRs;
|
||||||
public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Logger<DevAdminSignalRHub>>
|
public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Logger<DevAdminSignalRHub>>
|
||||||
{
|
{
|
||||||
public DevAdminSignalRHub(IConfiguration configuration, IFruitBankDataControllerServer fruitBankDataController/*, SessionService sessionService*/,
|
public DevAdminSignalRHub(IConfiguration configuration, IFruitBankDataControllerServer fruitBankDataController/*, SessionService sessionService*/,
|
||||||
ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IEnumerable<IAcLogWriterBase> logWriters)
|
ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IStockSignalREndpointServer stockSignalREndpointServer, IEnumerable<IAcLogWriterBase> logWriters)
|
||||||
: base(configuration, new Logger<DevAdminSignalRHub>(logWriters.ToArray()))
|
: base(configuration, new Logger<DevAdminSignalRHub>(logWriters.ToArray()))
|
||||||
{
|
{
|
||||||
|
SerializerOptions = new AcBinarySerializerOptions();
|
||||||
|
//SerializerOptions = new AcJsonSerializerOptions();
|
||||||
|
|
||||||
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(fruitBankDataController));
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(fruitBankDataController));
|
||||||
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(customOrderSignalREndpoint));
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(customOrderSignalREndpoint));
|
||||||
|
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(stockSignalREndpointServer));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LogContextUserNameAndId()
|
protected override void LogContextUserNameAndId()
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using Nop.Core;
|
||||||
//using Nop.Core.Domain.Catalog;
|
//using Nop.Core.Domain.Catalog;
|
||||||
using Nop.Core.Domain.Common;
|
using Nop.Core.Domain.Common;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
@ -21,7 +22,7 @@ namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
{
|
{
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
private static Expression<Func<OrderDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderDto, genericAttributeDto) =>
|
private static Expression<Func<OrderDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderDto, genericAttributeDto) =>
|
||||||
orderDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == nameof(Order);
|
orderDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == nameof(Order);
|
||||||
|
|
||||||
|
|
@ -29,21 +30,21 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(GenericAttribute.EntityId), ExpressionPredicate = nameof(RelationWithGenericAttribute), CanBeNull = true)]
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(GenericAttribute.EntityId), ExpressionPredicate = nameof(RelationWithGenericAttribute), CanBeNull = true)]
|
||||||
public List<GenericAttributeDto> GenericAttributes { get; set; }
|
public List<GenericAttributeDto> GenericAttributes { get; set; }
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsMeasured
|
public bool IsMeasured
|
||||||
{
|
{
|
||||||
get => IsMeasuredAndValid();
|
get => IsMeasuredAndValid();
|
||||||
set => throw new Exception($"OrderDto.IsMeasured not set");
|
set => throw new Exception($"OrderDto.IsMeasured not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsMeasurable
|
public bool IsMeasurable
|
||||||
{
|
{
|
||||||
get => OrderItemDtos.Any(oi => oi.IsMeasurable);
|
get => OrderItemDtos.Any(oi => oi.IsMeasurable);
|
||||||
set => throw new Exception($"OrderDto.IsMeasurable not set");
|
set => throw new Exception($"OrderDto.IsMeasurable not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public string TimeOfReceiptText
|
public string TimeOfReceiptText
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -60,25 +61,25 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public DateTime DateOfReceiptOrCreated => DateOfReceipt ?? CreatedOnUtc;
|
public DateTime DateOfReceiptOrCreated => DateOfReceipt ?? CreatedOnUtc;
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public DateTime? DateOfReceipt => GenericAttributes.GetValueOrNull<DateTime>(nameof(IOrderDto.DateOfReceipt));
|
public DateTime? DateOfReceipt => GenericAttributes.GetValueOrNull<DateTime>(nameof(IOrderDto.DateOfReceipt));
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public int RevisorId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0);
|
public int RevisorId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0);
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public int MeasurementOwnerId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.MeasurementOwnerId), 0);
|
public int MeasurementOwnerId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.MeasurementOwnerId), 0);
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsAllOrderItemAudited => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited);
|
public bool IsAllOrderItemAudited => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited);
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsAllOrderItemAvgWeightValid => OrderItemDtos.All(oi => oi.AverageWeightIsValid);
|
public bool IsAllOrderItemAvgWeightValid => OrderItemDtos.All(oi => oi.AverageWeightIsValid);
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public MeasuringStatus MeasuringStatus
|
public MeasuringStatus MeasuringStatus
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -99,7 +100,7 @@ public class OrderDto : MgOrderDto<OrderItemDto, ProductDto>, IOrderDto
|
||||||
public OrderDto(Order order) : base(order)
|
public OrderDto(Order order) : base(order)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsComplete => OrderStatus == OrderStatus.Complete;
|
public bool IsComplete => OrderStatus == OrderStatus.Complete;
|
||||||
|
|
||||||
public bool HasMeasuringAccess(int? customerId, bool isRevisorUser = false)
|
public bool HasMeasuringAccess(int? customerId, bool isRevisorUser = false)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Newtonsoft.Json;
|
||||||
using Nop.Core;
|
using Nop.Core;
|
||||||
using Nop.Core.Domain.Common;
|
using Nop.Core.Domain.Common;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
@ -15,7 +16,7 @@ namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
{
|
{
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
private static Expression<Func<OrderItemDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
|
private static Expression<Func<OrderItemDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
|
||||||
orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == nameof(OrderItem);
|
orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == nameof(OrderItem);
|
||||||
|
|
||||||
|
|
@ -29,28 +30,28 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
[Association(ThisKey = nameof(OrderId), OtherKey = nameof(OrderDto.Id), CanBeNull = false)]
|
[Association(ThisKey = nameof(OrderId), OtherKey = nameof(OrderDto.Id), CanBeNull = false)]
|
||||||
public OrderDto OrderDto { get; set; }
|
public OrderDto OrderDto { get; set; }
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsMeasured
|
public bool IsMeasured
|
||||||
{
|
{
|
||||||
get => IsMeasuredAndValid();
|
get => IsMeasuredAndValid();
|
||||||
set => throw new Exception($"OrderItemDto.IsMeasured not set");
|
set => throw new Exception($"OrderItemDto.IsMeasured not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsMeasurable
|
public bool IsMeasurable
|
||||||
{
|
{
|
||||||
get => ProductDto!.IsMeasurable;
|
get => ProductDto!.IsMeasurable;
|
||||||
set => throw new Exception($"OrderItemDto.IsMeasurable not set");
|
set => throw new Exception($"OrderItemDto.IsMeasurable not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public int TrayQuantity
|
public int TrayQuantity
|
||||||
{
|
{
|
||||||
get => OrderItemPallets.Sum(x => x.TrayQuantity);
|
get => OrderItemPallets.Sum(x => x.TrayQuantity);
|
||||||
set => throw new Exception($"OrderItemDto.TrayQuantity not set");
|
set => throw new Exception($"OrderItemDto.TrayQuantity not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double NetWeight
|
public double NetWeight
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -67,7 +68,7 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double GrossWeight
|
public double GrossWeight
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -84,20 +85,20 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double AverageWeight => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d;
|
public double AverageWeight => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d;
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double AverageWeightDifference => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0;
|
public double AverageWeightDifference => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0;
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool AverageWeightIsValid => !IsMeasurable ||
|
public bool AverageWeightIsValid => !IsMeasurable ||
|
||||||
(ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold);
|
(ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold);
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsAudited => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited);
|
public bool IsAudited => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited);
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public MeasuringStatus MeasuringStatus
|
public MeasuringStatus MeasuringStatus
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Dtos;
|
using Mango.Nop.Core.Dtos;
|
||||||
using Mango.Nop.Core.Extensions;
|
using Mango.Nop.Core.Extensions;
|
||||||
|
using Mango.Nop.Core.Interfaces.ForeignKeys;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
//using Nop.Core.Domain.Catalog;
|
//using Nop.Core.Domain.Catalog;
|
||||||
using Nop.Core.Domain.Common;
|
using Nop.Core.Domain.Common;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Mango.Nop.Core.Interfaces.ForeignKeys;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Dtos;
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
public class ProductDto : MgProductDto, IProductDto
|
public class ProductDto : MgProductDto, IProductDto
|
||||||
{
|
{
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
private static Expression<Func<ProductDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
|
private static Expression<Func<ProductDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
|
||||||
orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == "Product";// nameof(Product);
|
orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == "Product";// nameof(Product);
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
//public ProductDto(Product product) : base(product)
|
//public ProductDto(Product product) : base(product)
|
||||||
//{ }
|
//{ }
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsMeasurable
|
public bool IsMeasurable
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<bool>(nameof(IMeasurable.IsMeasurable));
|
get => GenericAttributes.GetValueOrDefault<bool>(nameof(IMeasurable.IsMeasurable));
|
||||||
|
|
@ -41,7 +42,7 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double Tare
|
public double Tare
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<double>(nameof(ITare.Tare));
|
get => GenericAttributes.GetValueOrDefault<double>(nameof(ITare.Tare));
|
||||||
|
|
@ -49,14 +50,14 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
set => throw new Exception($"ProductDto.Tare not set");
|
set => throw new Exception($"ProductDto.Tare not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double NetWeight
|
public double NetWeight
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<double>(nameof(IMeasuringNetWeight.NetWeight));
|
get => GenericAttributes.GetValueOrDefault<double>(nameof(IMeasuringNetWeight.NetWeight));
|
||||||
set => throw new Exception($"ProductDto.NetWeight not set");
|
set => throw new Exception($"ProductDto.NetWeight not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public int IncomingQuantity
|
public int IncomingQuantity
|
||||||
{
|
{
|
||||||
get => GenericAttributes.GetValueOrDefault<int>(nameof(IIncomingQuantity.IncomingQuantity));
|
get => GenericAttributes.GetValueOrDefault<int>(nameof(IIncomingQuantity.IncomingQuantity));
|
||||||
|
|
@ -70,13 +71,13 @@ public class ProductDto : MgProductDto, IProductDto
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public int AvailableQuantity => StockQuantity + IncomingQuantity;
|
public int AvailableQuantity => StockQuantity + IncomingQuantity;
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double AverageWeight => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeight));
|
public double AverageWeight => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeight));
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double AverageWeightTreshold => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeightTreshold));
|
public double AverageWeightTreshold => GenericAttributes.GetValueOrDefault<double>(nameof(IProductDto.AverageWeightTreshold));
|
||||||
|
|
||||||
public bool HasMeasuringValues() => Id > 0 && NetWeight != 0 && IsMeasurable;
|
public bool HasMeasuringValues() => Id > 0 && NetWeight != 0 && IsMeasurable;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Catalog;
|
using Nop.Core.Domain.Catalog;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -15,28 +16,28 @@ namespace FruitBank.Common.Dtos
|
||||||
{
|
{
|
||||||
public class StockQuantityHistoryDto : MgStockQuantityHistoryDto<ProductDto>, IStockQuantityHistoryDto
|
public class StockQuantityHistoryDto : MgStockQuantityHistoryDto<ProductDto>, IStockQuantityHistoryDto
|
||||||
{
|
{
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public int? StockQuantityHistoryId
|
public int? StockQuantityHistoryId
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.StockQuantityHistoryId;
|
get => StockQuantityHistoryExt?.StockQuantityHistoryId;
|
||||||
set => StockQuantityHistoryExt!.StockQuantityHistoryId = value!.Value;
|
set => StockQuantityHistoryExt!.StockQuantityHistoryId = value!.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double? NetWeightAdjustment
|
public double? NetWeightAdjustment
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.NetWeightAdjustment;
|
get => StockQuantityHistoryExt?.NetWeightAdjustment;
|
||||||
set => StockQuantityHistoryExt!.NetWeightAdjustment = value;
|
set => StockQuantityHistoryExt!.NetWeightAdjustment = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double? NetWeight
|
public double? NetWeight
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.NetWeight;
|
get => StockQuantityHistoryExt?.NetWeight;
|
||||||
set => StockQuantityHistoryExt!.NetWeight = value;
|
set => StockQuantityHistoryExt!.NetWeight = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public bool IsInconsistent
|
public bool IsInconsistent
|
||||||
{
|
{
|
||||||
get => StockQuantityHistoryExt?.IsInconsistent ?? false;
|
get => StockQuantityHistoryExt?.IsInconsistent ?? false;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
set => _palletWeight = double.Round(value, 0);
|
set => _palletWeight = double.Round(value, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double NetWeight
|
public double NetWeight
|
||||||
{
|
{
|
||||||
get => CalculateNetWeight();
|
get => CalculateNetWeight();
|
||||||
|
|
@ -59,8 +59,12 @@ public abstract class MeasuringItemPalletBase : MgEntityBase, IMeasuringItemPall
|
||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
public DateTime Modified { get; set; }
|
public DateTime Modified { get; set; }
|
||||||
|
|
||||||
|
[NotColumn, System.ComponentModel.DataAnnotations.Schema.NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public virtual MeasuringStatus MeasuringStatus => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted;
|
public virtual MeasuringStatus MeasuringStatus => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? MeasuringStatus.Started : MeasuringStatus.NotStarted;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void SetParentPropToNull();
|
||||||
|
|
||||||
public void SetForeignKey(int foreignKey) => ForeignItemId = foreignKey;
|
public void SetForeignKey(int foreignKey) => ForeignItemId = foreignKey;
|
||||||
public virtual double CalculateNetWeight() => double.Round(GrossWeight - PalletWeight - (TareWeight * TrayQuantity), 1);
|
public virtual double CalculateNetWeight() => double.Round(GrossWeight - PalletWeight - (TareWeight * TrayQuantity), 1);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Column = LinqToDB.Mapping.ColumnAttribute;
|
||||||
|
using Table = LinqToDB.Mapping.TableAttribute;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
|
@ -20,9 +23,11 @@ public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
public int RevisorId { get; set; }
|
public int RevisorId { get; set; }
|
||||||
public bool IsAudited => RevisorId > 0;
|
public bool IsAudited => RevisorId > 0;
|
||||||
|
|
||||||
|
//[JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[Association(ThisKey = nameof(OrderItemId), OtherKey = nameof(OrderItemDto.Id), CanBeNull = true)]
|
[Association(ThisKey = nameof(OrderItemId), OtherKey = nameof(OrderItemDto.Id), CanBeNull = true)]
|
||||||
public OrderItemDto? OrderItemDto { get; set; }
|
public OrderItemDto? OrderItemDto { get; set; }
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public override MeasuringStatus MeasuringStatus => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus;
|
public override MeasuringStatus MeasuringStatus => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus;
|
||||||
public override double CalculateNetWeight() => base.CalculateNetWeight();
|
public override double CalculateNetWeight() => base.CalculateNetWeight();
|
||||||
|
|
||||||
|
|
@ -31,8 +36,9 @@ public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
return OrderItemId > 0 && base.IsValidSafeMeasuringValues();
|
return OrderItemId > 0 && base.IsValidSafeMeasuringValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public double AverageWeight => double.Round(NetWeight / TrayQuantity, 1);
|
public double AverageWeight => double.Round(NetWeight / TrayQuantity, 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Szigorúbb" mint az IsValidSafeMeasuringValues()
|
/// "Szigorúbb" mint az IsValidSafeMeasuringValues()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -42,4 +48,9 @@ public class OrderItemPallet : MeasuringItemPalletBase, IOrderItemPallet
|
||||||
{
|
{
|
||||||
return OrderItemId > 0 && base.IsValidMeasuringValues(isMeasurable);
|
return OrderItemId > 0 && base.IsValidMeasuringValues(isMeasurable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void SetParentPropToNull()
|
||||||
|
{
|
||||||
|
OrderItemDto = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ public class ShippingDocumentToFiles : MgEntityBase, IShippingDocumentToFiles
|
||||||
|
|
||||||
public int DocumentTypeId { get; set; }
|
public int DocumentTypeId { get; set; }
|
||||||
|
|
||||||
[NotColumn, NotMapped]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public DocumentType DocumentType
|
public DocumentType DocumentType
|
||||||
{
|
{
|
||||||
get => (DocumentType)DocumentTypeId;
|
get => (DocumentType)DocumentTypeId;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
|
using FruitBank.Common.Enums;
|
||||||
using FruitBank.Common.Interfaces;
|
using FruitBank.Common.Interfaces;
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
using Mango.Nop.Core.Entities;
|
using Mango.Nop.Core.Entities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Customers;
|
using Nop.Core.Domain.Customers;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
//using Nop.Core.Domain.Catalog;
|
//using Nop.Core.Domain.Catalog;
|
||||||
using DataType = LinqToDB.DataType;
|
using DataType = LinqToDB.DataType;
|
||||||
using FruitBank.Common.Dtos;
|
using Column = LinqToDB.Mapping.ColumnAttribute;
|
||||||
using FruitBank.Common.Enums;
|
using Table = LinqToDB.Mapping.TableAttribute;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
|
@ -29,7 +32,7 @@ public class ShippingItem : MgEntityBase, IShippingItem
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// get => ProductDto?.Name ?? Name
|
/// get => ProductDto?.Name ?? Name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[NotColumn, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public string ProductName => ProductDto?.Name ?? Name;
|
public string ProductName => ProductDto?.Name ?? Name;
|
||||||
|
|
||||||
public int PalletsOnDocument { get; set; }
|
public int PalletsOnDocument { get; set; }
|
||||||
|
|
@ -79,6 +82,7 @@ public class ShippingItem : MgEntityBase, IShippingItem
|
||||||
[SkipValuesOnUpdate] public DateTime Created { get; set; }
|
[SkipValuesOnUpdate] public DateTime Created { get; set; }
|
||||||
public DateTime Modified { get; set; }
|
public DateTime Modified { get; set; }
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
public MeasuringStatus MeasuringStatus
|
public MeasuringStatus MeasuringStatus
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using FruitBank.Common.Interfaces;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using FruitBank.Common.Interfaces;
|
|
||||||
using LinqToDB.Mapping;
|
using LinqToDB.Mapping;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
namespace FruitBank.Common.Entities;
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
[Table(Name = FruitBankConstClient.ShippingItemPalletDbTableName)]
|
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.ShippingItemPalletDbTableName)]
|
||||||
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemPalletDbTableName)]
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingItemPalletDbTableName)]
|
||||||
public class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet
|
public class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +16,7 @@ public class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet
|
||||||
set => ForeignItemId = value;
|
set => ForeignItemId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//[Newtonsoft.Json.JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
[LinqToDB.Mapping.Association(ThisKey = nameof(ShippingItemId), OtherKey = nameof(ShippingItem.Id), CanBeNull = true)]
|
[LinqToDB.Mapping.Association(ThisKey = nameof(ShippingItemId), OtherKey = nameof(ShippingItem.Id), CanBeNull = true)]
|
||||||
public ShippingItem? ShippingItem { get; set; }
|
public ShippingItem? ShippingItem { get; set; }
|
||||||
|
|
||||||
|
|
@ -34,4 +36,9 @@ public class ShippingItemPallet : MeasuringItemPalletBase, IShippingItemPallet
|
||||||
{
|
{
|
||||||
return ShippingItemId > 0 && base.IsValidMeasuringValues(isMeasurable);
|
return ShippingItemId > 0 && base.IsValidMeasuringValues(isMeasurable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void SetParentPropToNull()
|
||||||
|
{
|
||||||
|
ShippingItem = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using LinqToDB.Mapping;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
[Table(Name = FruitBankConstClient.StockTakingDbTableName)]
|
||||||
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingDbTableName)]
|
||||||
|
public class StockTaking : MgStockTaking<StockTakingItem>
|
||||||
|
{
|
||||||
|
public override bool IsReadyForClose()
|
||||||
|
{
|
||||||
|
if (StockTakingItems == null || StockTakingItems.Count == 0) return false;
|
||||||
|
return StockTakingItems.Where(stockTakingItem => stockTakingItem is { IsRequiredForMeasuring: true, IsInvalid: false }).All(x => x.IsMeasured);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.Mapping;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Column = LinqToDB.Mapping.ColumnAttribute;
|
||||||
|
using Table = LinqToDB.Mapping.TableAttribute;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
[Table(Name = FruitBankConstClient.StockTakingItemDbTableName)]
|
||||||
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemDbTableName)]
|
||||||
|
public class StockTakingItem : MgStockTakingItem<StockTaking, ProductDto>
|
||||||
|
{
|
||||||
|
public bool IsMeasurable { get; set; }
|
||||||
|
|
||||||
|
[Column(DataType = DataType.DecFloat, CanBeNull = false)]
|
||||||
|
public double OriginalNetWeight { get; set; }
|
||||||
|
|
||||||
|
[Column(DataType = DataType.DecFloat, CanBeNull = false)]
|
||||||
|
public double MeasuredNetWeight { get; set; }
|
||||||
|
|
||||||
|
public int InProcessOrdersQuantity { get; set; }
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public int TotalOriginalQuantity => OriginalStockQuantity + InProcessOrdersQuantity;
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public int QuantityDiff => IsMeasured ? MeasuredStockQuantity - TotalOriginalQuantity : 0;
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public double NetWeightDiff => IsMeasurable && IsMeasured ? double.Round(MeasuredNetWeight - OriginalNetWeight, 1) : 0d;
|
||||||
|
|
||||||
|
[Association(ThisKey = nameof(Id), OtherKey = nameof(StockTakingItemPallet.StockTakingItemId), CanBeNull = true)]
|
||||||
|
public List<StockTakingItemPallet>? StockTakingItemPallets { get; set; }
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public bool IsRequiredForMeasuring => !IsInvalid && (TotalOriginalQuantity != 0 || OriginalNetWeight != 0);
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public bool IsInvalid => TotalOriginalQuantity < 0;
|
||||||
|
|
||||||
|
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public string DisplayText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsInvalid) return $"[HIBA] {Product!.Name}";
|
||||||
|
if (IsMeasured) return $"[KÉSZ] {Product!.Name}";
|
||||||
|
|
||||||
|
return IsRequiredForMeasuring ? $"[KÖT] {Product!.Name}" : $"{Product!.Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
|
using FruitBank.Common.Interfaces;
|
||||||
|
using LinqToDB.Mapping;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Entities;
|
||||||
|
|
||||||
|
public interface IStockTakingItemPallet : IMeasuringItemPalletBase
|
||||||
|
{
|
||||||
|
int StockTakingItemId { get; set; }
|
||||||
|
public StockTakingItem? StockTakingItem{ get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[LinqToDB.Mapping.Table(Name = FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
||||||
|
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.StockTakingItemPalletDbTableName)]
|
||||||
|
public class StockTakingItemPallet : MeasuringItemPalletBase, IStockTakingItemPallet
|
||||||
|
{
|
||||||
|
public int StockTakingItemId
|
||||||
|
{
|
||||||
|
get => ForeignItemId;
|
||||||
|
set => ForeignItemId = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//[Newtonsoft.Json.JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
[Association(ThisKey = nameof(StockTakingItemId), OtherKey = nameof(StockTakingItem.Id), CanBeNull = true)]
|
||||||
|
public StockTakingItem? StockTakingItem { get; set; }
|
||||||
|
|
||||||
|
public override double CalculateNetWeight() => base.CalculateNetWeight();
|
||||||
|
|
||||||
|
public override bool IsValidSafeMeasuringValues()
|
||||||
|
{
|
||||||
|
return StockTakingItemId > 0 && TrayQuantity >= 0 && TareWeight >= 0 && PalletWeight >= 0 && NetWeight >= 0 && GrossWeight >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsValidMeasuringValues(bool isMeasurable)
|
||||||
|
{
|
||||||
|
return StockTakingItemId > 0 && TrayQuantity >= 0 && ((!isMeasurable && NetWeight == 0 && GrossWeight == 0 && PalletWeight == 0 && TareWeight == 0)
|
||||||
|
|| (isMeasurable && NetWeight >= 0 && GrossWeight >= 0 && PalletWeight >= 0 && TareWeight >= 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetParentPropToNull()
|
||||||
|
{
|
||||||
|
StockTakingItem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="linq2db.EntityFrameworkCore" Version="9.0.0" />
|
||||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using AyCode.Core.Consts;
|
using AyCode.Core.Consts;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
|
|
||||||
namespace FruitBank.Common;
|
namespace FruitBank.Common;
|
||||||
|
|
@ -14,7 +14,7 @@ public static class FruitBankConstClient
|
||||||
// public static string BaseUrl = "https://shop.fruitbank.hu"; //FrutiBank nop
|
// public static string BaseUrl = "https://shop.fruitbank.hu"; //FrutiBank nop
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//public static string BaseUrl = "http://localhost:5000"; //FrutiBank nop
|
//public static string BaseUrl = "http://localhost:59579"; //FrutiBank nop
|
||||||
//public static string BaseUrl = "http://10.0.2.2:59579"; //FrutiBank (android) nop
|
//public static string BaseUrl = "http://10.0.2.2:59579"; //FrutiBank (android) nop
|
||||||
//public static string BaseUrl = "https://localhost:7144"; //HybridApp
|
//public static string BaseUrl = "https://localhost:7144"; //HybridApp
|
||||||
|
|
||||||
|
|
@ -39,6 +39,11 @@ public static class FruitBankConstClient
|
||||||
|
|
||||||
public const string StockQuantityHistoryExtDbTableName = "fbStockQuantityHistoryExt";
|
public const string StockQuantityHistoryExtDbTableName = "fbStockQuantityHistoryExt";
|
||||||
|
|
||||||
|
public const string StockTakingDbTableName = "fbStockTaking";
|
||||||
|
public const string StockTakingItemDbTableName = "fbStockTakingItem";
|
||||||
|
public const string StockTakingItemPalletDbTableName = "fbStockTakingItemPallet";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//public static Guid[] DevAdminIds = new Guid[2] { Guid.Parse("dcf451d2-cc4c-4ac2-8c1f-da00041be1fd"), Guid.Parse("4cbaed43-2465-4d99-84f1-c8bc6b7025f7") };
|
//public static Guid[] DevAdminIds = new Guid[2] { Guid.Parse("dcf451d2-cc4c-4ac2-8c1f-da00041be1fd"), Guid.Parse("4cbaed43-2465-4d99-84f1-c8bc6b7025f7") };
|
||||||
//public static Guid[] SysAdmins = new Guid[3]
|
//public static Guid[] SysAdmins = new Guid[3]
|
||||||
|
|
@ -107,3 +112,5 @@ public static string SystemEmailAddress = "test@touriam.com";
|
||||||
public static LogLevel DefaultLogLevelClient = LogLevel.Detail;
|
public static LogLevel DefaultLogLevelClient = LogLevel.Detail;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
using Mango.Nop.Core.Dtos;
|
using Mango.Nop.Core.Dtos;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
using Mango.Nop.Core.Models;
|
using Mango.Nop.Core.Models;
|
||||||
using Nop.Core.Domain.Customers;
|
using Nop.Core.Domain.Customers;
|
||||||
|
|
||||||
|
|
@ -72,4 +73,11 @@ public interface IFruitBankDataControllerCommon
|
||||||
|
|
||||||
Task<MgLoginModelResponse?> LoginMeasuringUser(MgLoginModelRequest loginModelRequest);
|
Task<MgLoginModelResponse?> LoginMeasuringUser(MgLoginModelRequest loginModelRequest);
|
||||||
Task<List<Partner>?> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId);
|
Task<List<Partner>?> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId);
|
||||||
|
|
||||||
|
public Task<List<GenericAttributeDto>?> GetGenericAttributeDtosByEntityIdAndKeyGroup(int productId, string keyGroup, int storeId);
|
||||||
|
|
||||||
|
public Task<GenericAttributeDto?> AddGenericAttributeDto(GenericAttributeDto genericAttributeDto);
|
||||||
|
|
||||||
|
public Task<GenericAttributeDto?> UpdateGenericAttributeDto(GenericAttributeDto genericAttributeDto);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,8 @@ public interface IMeasuringItemPalletBase : IEntityInt, IMeasuringValues, IMeasu
|
||||||
int? CreatorId { get; set; }
|
int? CreatorId { get; set; }
|
||||||
int? ModifierId { get; set; }
|
int? ModifierId { get; set; }
|
||||||
|
|
||||||
|
void SetParentPropToNull();
|
||||||
|
|
||||||
double CalculateNetWeight();
|
double CalculateNetWeight();
|
||||||
|
|
||||||
bool IsValidSafeMeasuringValues();
|
bool IsValidSafeMeasuringValues();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace FruitBank.Common.Interfaces;
|
||||||
|
|
||||||
|
public interface IStockSignalREndpointClient : IStockSignalREndpointCommon
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Interfaces;
|
||||||
|
|
||||||
|
public interface IStockSignalREndpointCommon
|
||||||
|
{
|
||||||
|
public Task<List<StockTaking>?> GetStockTakings(bool loadRelations);
|
||||||
|
public Task<List<StockTaking>?> GetStockTakingsByProductId(int productId);
|
||||||
|
public Task<StockTaking?> AddStockTaking(StockTaking stockTaking);
|
||||||
|
public Task<StockTaking?> UpdateStockTaking(StockTaking stockTaking);
|
||||||
|
|
||||||
|
public Task<List<StockTakingItem>?> GetStockTakingItems();
|
||||||
|
public Task<StockTakingItem?> GetStockTakingItemsById(int stockTakingItemId);
|
||||||
|
public Task<List<StockTakingItem>?> GetStockTakingItemsByProductId(int productId);
|
||||||
|
public Task<List<StockTakingItem>?> GetStockTakingItemsByStockTakingId(int stockTakingId);
|
||||||
|
public Task<StockTakingItem?> AddStockTakingItem(StockTakingItem stockTakingItem);
|
||||||
|
public Task<StockTakingItem?> UpdateStockTakingItem(StockTakingItem stockTakingItem);
|
||||||
|
|
||||||
|
public Task<List<StockTakingItemPallet>?> GetStockTakingItemPallets();
|
||||||
|
public Task<List<StockTakingItemPallet>?> GetStockTakingItemPalletsByProductId(int productId);
|
||||||
|
public Task<StockTakingItemPallet?> AddStockTakingItemPallet(StockTakingItemPallet stockTakingItemPallet);
|
||||||
|
public Task<StockTakingItemPallet?> UpdateStockTakingItemPallet(StockTakingItemPallet stockTakingItemPallet);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using AyCode.Core;
|
using AyCode.Core;
|
||||||
|
using FruitBank.Common.Services;
|
||||||
using Mango.Nop.Core.Dtos;
|
using Mango.Nop.Core.Dtos;
|
||||||
using Mango.Nop.Core.Models;
|
using Mango.Nop.Core.Models;
|
||||||
using Nop.Core.Domain.Customers;
|
using Nop.Core.Domain.Customers;
|
||||||
|
|
@ -7,6 +8,8 @@ namespace FruitBank.Common.Models;
|
||||||
|
|
||||||
public class LoggedInModel
|
public class LoggedInModel
|
||||||
{
|
{
|
||||||
|
private readonly ISecureCredentialService? _secureCredentialService;
|
||||||
|
|
||||||
public bool IsLoggedIn => CustomerDto != null;
|
public bool IsLoggedIn => CustomerDto != null;
|
||||||
public bool IsRevisor => IsLoggedIn && CustomerRoles.Any(x => x.SystemName.ToLowerInvariant() == "measuringrevisor");
|
public bool IsRevisor => IsLoggedIn && CustomerRoles.Any(x => x.SystemName.ToLowerInvariant() == "measuringrevisor");
|
||||||
public bool IsAdministrator => IsLoggedIn && CustomerRoles.Any(x => x.SystemName.ToLowerInvariant() == "administrators");
|
public bool IsAdministrator => IsLoggedIn && CustomerRoles.Any(x => x.SystemName.ToLowerInvariant() == "administrators");
|
||||||
|
|
@ -16,38 +19,123 @@ public class LoggedInModel
|
||||||
|
|
||||||
public CustomerDto? CustomerDto { get; private set; }
|
public CustomerDto? CustomerDto { get; private set; }
|
||||||
public List<CustomerRole> CustomerRoles { get; private set; } = [];
|
public List<CustomerRole> CustomerRoles { get; private set; } = [];
|
||||||
|
|
||||||
public List<CustomerDto> MeasuringUsers { get; set; } = [];
|
public List<CustomerDto> MeasuringUsers { get; set; } = [];
|
||||||
|
|
||||||
|
public Func<string, string, Task<MgLoginModelResponse?>>? LoginFunc { get; set; }
|
||||||
|
public Func<int, Task<List<CustomerRole>?>>? GetRolesFunc { get; set; }
|
||||||
|
|
||||||
public LoggedInModel()
|
public LoggedInModel()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoggedInModel(CustomerDto? customerDto)
|
public LoggedInModel(ISecureCredentialService secureCredentialService)
|
||||||
{
|
{
|
||||||
InitLoggedInCustomer(customerDto);
|
_secureCredentialService = secureCredentialService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoggedInModel(MgLoginModelResponse loginModelResponse) : this(loginModelResponse.CustomerDto)
|
/// <summary>
|
||||||
|
/// Tries to login - first checks if already logged in, then checks for stored credentials.
|
||||||
|
/// Call this on app startup. Only attempts auto-login once per session.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> TryAutoLoginAsync()
|
||||||
{
|
{
|
||||||
|
if (IsLoggedIn) return IsLoggedIn;
|
||||||
|
|
||||||
|
var credentials = await GetStoredCredentialsAsync();
|
||||||
|
if (credentials == null) return IsLoggedIn;
|
||||||
|
|
||||||
|
await LoginAsync(credentials.Email, credentials.Password, true);
|
||||||
|
|
||||||
|
return IsLoggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InitLoggedInCustomer(CustomerDto? customerDto)
|
/// <summary>
|
||||||
|
/// Performs manual login with the provided credentials.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> LoginAsync(string email, string password, bool saveCredentials = true)
|
||||||
{
|
{
|
||||||
LogOut();
|
if (IsLoggedIn || LoginFunc == null) return IsLoggedIn;
|
||||||
|
|
||||||
|
var loginResponse = await LoginFunc(email, password);
|
||||||
|
|
||||||
|
if (loginResponse is { IsSuccesLogin: true })
|
||||||
|
{
|
||||||
|
await SetupLoggedInUser(loginResponse.CustomerDto!);
|
||||||
|
|
||||||
|
if (saveCredentials)
|
||||||
|
{
|
||||||
|
await SaveCredentialsAsync(email, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsLoggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs out the user and clears stored credentials.
|
||||||
|
/// </summary>
|
||||||
|
public async Task LogOutAsync()
|
||||||
|
{
|
||||||
|
await ClearCredentialsAsync();
|
||||||
|
ClearCustomer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCustomer(CustomerDto? customerDto)
|
||||||
|
{
|
||||||
|
ClearCustomer();
|
||||||
if (customerDto != null) CustomerDto = customerDto;
|
if (customerDto != null) CustomerDto = customerDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InitCustomerRoles(List<CustomerRole> customerRoles)
|
public void SetCustomerRoles(List<CustomerRole> customerRoles)
|
||||||
{
|
{
|
||||||
CustomerRoles.Clear();
|
CustomerRoles.Clear();
|
||||||
CustomerRoles.AddRange(customerRoles);
|
CustomerRoles.AddRange(customerRoles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogOut()
|
public void ClearCustomer()
|
||||||
{
|
{
|
||||||
CustomerDto = null;
|
CustomerDto = null;
|
||||||
CustomerRoles.Clear();
|
CustomerRoles.Clear();
|
||||||
//MeasuringUsers.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LogOut() => ClearCustomer();
|
||||||
|
|
||||||
|
#region Credential Management
|
||||||
|
|
||||||
|
public async Task<StoredCredentials?> GetStoredCredentialsAsync()
|
||||||
|
{
|
||||||
|
if (_secureCredentialService == null) return null;
|
||||||
|
return await _secureCredentialService.GetCredentialsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveCredentialsAsync(string email, string password)
|
||||||
|
{
|
||||||
|
if (_secureCredentialService == null) return;
|
||||||
|
await _secureCredentialService.SaveCredentialsAsync(email, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearCredentialsAsync()
|
||||||
|
{
|
||||||
|
if (_secureCredentialService == null) return;
|
||||||
|
await _secureCredentialService.ClearCredentialsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private async Task SetupLoggedInUser(CustomerDto customerDto)
|
||||||
|
{
|
||||||
|
SetCustomer(customerDto);
|
||||||
|
|
||||||
|
if (GetRolesFunc != null)
|
||||||
|
{
|
||||||
|
var customerRoles = await GetRolesFunc(customerDto.Id);
|
||||||
|
if (customerRoles != null)
|
||||||
|
{
|
||||||
|
SetCustomerRoles(customerRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
namespace FruitBank.Common.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for securely storing and retrieving user credentials.
|
||||||
|
/// Platform-specific implementations handle the actual secure storage.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISecureCredentialService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the user credentials securely with a 2-day expiration from now.
|
||||||
|
/// </summary>
|
||||||
|
Task SaveCredentialsAsync(string email, string password);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the stored credentials if they exist and haven't expired.
|
||||||
|
/// Returns null if no credentials are stored or if they have expired.
|
||||||
|
/// </summary>
|
||||||
|
Task<StoredCredentials?> GetCredentialsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all stored credentials (used on logout).
|
||||||
|
/// </summary>
|
||||||
|
Task ClearCredentialsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents stored user credentials.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record StoredCredentials(string Email, string Password);
|
||||||
|
|
@ -90,6 +90,17 @@ public class SignalRTags : AcSignalRTags
|
||||||
public const int AddGenericAttributeDto = 168;
|
public const int AddGenericAttributeDto = 168;
|
||||||
public const int UpdateGenericAttributeDto = 169;
|
public const int UpdateGenericAttributeDto = 169;
|
||||||
|
|
||||||
|
public const int GetStockTakings = 170;
|
||||||
|
public const int AddStockTaking = 171;
|
||||||
|
public const int UpdateStockTaking = 172;
|
||||||
|
public const int CloseStockTaking = 173;
|
||||||
|
public const int GetStockTakingItems = 174;
|
||||||
|
public const int GetStockTakingItemsById = 175;
|
||||||
|
public const int GetStockTakingItemsByProductId = 176;
|
||||||
|
public const int GetStockTakingItemsByStockTakingId = 177;
|
||||||
|
public const int AddOrUpdateMeasuredStockTakingItemPallet = 178;
|
||||||
|
|
||||||
|
|
||||||
public const int AuthenticateUser = 195;
|
public const int AuthenticateUser = 195;
|
||||||
public const int RefreshToken = 200;
|
public const int RefreshToken = 200;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -430,16 +430,16 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
Assert.IsTrue(users.All(x => !x.Email.IsNullOrEmpty() && !x.Deleted));
|
Assert.IsTrue(users.All(x => !x.Email.IsNullOrEmpty() && !x.Deleted));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
//[TestMethod]
|
||||||
[DataRow(CustomerIdAasdDsserverCom)]
|
//[DataRow(CustomerIdAasdDsserverCom)]
|
||||||
public async Task GetCustomerRolesByCustomerIdTest(int customerId)
|
//public async Task GetCustomerRolesByCustomerIdTest(int customerId)
|
||||||
{
|
//{
|
||||||
var customerRoles = await _signalRClient.GetCustomerRolesByCustomerId(customerId);
|
// var customerRoles = await _signalRClient.GetCustomerRolesByCustomerId(customerId);
|
||||||
|
|
||||||
Assert.IsNotNull(customerRoles);
|
// Assert.IsNotNull(customerRoles);
|
||||||
Assert.IsTrue(customerRoles.Count > 0);
|
// Assert.IsTrue(customerRoles.Count > 0);
|
||||||
Assert.IsTrue(customerRoles.Any(cr => cr.SystemName == "Measuring"));
|
// Assert.IsTrue(customerRoles.Any(cr => cr.SystemName == "Measuring"));
|
||||||
}
|
//}
|
||||||
|
|
||||||
#endregion Customer
|
#endregion Customer
|
||||||
|
|
||||||
|
|
@ -461,7 +461,7 @@ namespace FruitBankHybrid.Shared.Tests
|
||||||
//[DataRow(6, false)]
|
//[DataRow(6, false)]
|
||||||
[DataRow(33, true)]
|
[DataRow(33, true)]
|
||||||
[DataRow(64, false)]
|
[DataRow(64, false)]
|
||||||
[DataRow(7, false)]
|
[DataRow(7, true)]
|
||||||
public async Task GetProductDtoByIdTest(int productId, bool isMeasurableExcepted)
|
public async Task GetProductDtoByIdTest(int productId, bool isMeasurableExcepted)
|
||||||
{
|
{
|
||||||
await GetProductDtoByIdAsync(productId, isMeasurableExcepted);
|
await GetProductDtoByIdAsync(productId, isMeasurableExcepted);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
<Project Sdk="MSTest.Sdk/3.6.4">
|
<Project Sdk="MSTest.Sdk/4.0.2">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<UseVSTest>false</UseVSTest>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Displays error on console in addition to the log file. Note that this feature comes with a performance impact.
|
Displays error on console in addition to the log file. Note that this feature comes with a performance impact.
|
||||||
For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
|
For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
|
||||||
|
|
@ -12,6 +14,11 @@
|
||||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="bunit" Version="2.2.2" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FruitBank.Common\FruitBank.Common.csproj" />
|
<ProjectReference Include="..\FruitBank.Common\FruitBank.Common.csproj" />
|
||||||
<ProjectReference Include="..\FruitBankHybrid.Shared.Common\FruitBankHybrid.Shared.Common.csproj" />
|
<ProjectReference Include="..\FruitBankHybrid.Shared.Common\FruitBankHybrid.Shared.Common.csproj" />
|
||||||
|
|
@ -40,7 +47,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -48,22 +55,27 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="Microsoft.Testing.Extensions.TrxReport" Version="2.0.1" />
|
<PackageReference Update="Microsoft.Testing.Extensions.TrxReport" Version="2.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="MSTest.Analyzers" Version="4.0.1">
|
<PackageReference Update="MSTest.Analyzers" Version="4.0.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="MSTest.TestAdapter" Version="4.0.1" />
|
<PackageReference Update="MSTest.TestAdapter" Version="4.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="MSTest.TestFramework" Version="4.0.1" />
|
<PackageReference Update="MSTest.TestFramework" Version="4.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Ensure Microsoft.Extensions.ObjectPool v9 is available at test runtime to satisfy SignalR dependency -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using FruitBankHybrid.Shared.Components.Grids.Partners;
|
||||||
|
using FruitBank.Common.SignalRs;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests
|
||||||
|
{
|
||||||
|
//[TestClass]
|
||||||
|
//public class GridPartnerBaseTests
|
||||||
|
//{
|
||||||
|
// [TestMethod]
|
||||||
|
// public void Constructor_InitializesMessageTags()
|
||||||
|
// {
|
||||||
|
// // Arrange & Act
|
||||||
|
// var grid = new GridPartnerBase();
|
||||||
|
|
||||||
|
// // Assert
|
||||||
|
// Assert.AreEqual(SignalRTags.GetPartners, grid.GetAllMessageTag);
|
||||||
|
// Assert.AreEqual(SignalRTags.AddPartner, grid.AddMessageTag);
|
||||||
|
// Assert.AreEqual(SignalRTags.UpdatePartner, grid.UpdateMessageTag);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Bunit;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using FruitBankHybrid.Shared.Components.Grids.Partners;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests
|
||||||
|
{
|
||||||
|
//[TestClass]
|
||||||
|
//public class GridPartnerRazorTests : Bunit.TestContext
|
||||||
|
//{
|
||||||
|
// [TestMethod]
|
||||||
|
// public void GridPartnerRendersWithoutError()
|
||||||
|
// {
|
||||||
|
// // Act
|
||||||
|
// var cut = RenderComponent<GridPartner>();
|
||||||
|
|
||||||
|
// // Assert
|
||||||
|
// Assert.IsNotNull(cut);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,714 @@
|
||||||
|
using AyCode.Core.Enums;
|
||||||
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Interfaces;
|
||||||
|
using AyCode.Core.Loggers;
|
||||||
|
using AyCode.Services.SignalRs;
|
||||||
|
using AyCode.Utils.Extensions;
|
||||||
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
|
using FruitBank.Common.Loggers;
|
||||||
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
using FruitBankHybrid.Shared.Tests;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Nop.Core.Domain.Orders;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests;
|
||||||
|
|
||||||
|
#region Test Models for Hybrid Reference Test
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Level 1 - Root entity implementing IId<int> (uses semantic ID: "Company_1")
|
||||||
|
/// </summary>
|
||||||
|
public class Company : IId<int>
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Collection of IId<int> items
|
||||||
|
public List<Department> Departments { get; set; } = new();
|
||||||
|
|
||||||
|
// Non-IId object (uses numeric ID)
|
||||||
|
public Address HeadquartersAddress { get; set; } = new();
|
||||||
|
|
||||||
|
// Array of IId<int> items
|
||||||
|
public Employee[] BoardMembers { get; set; } = Array.Empty<Employee>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Level 2 - Department implementing IId<int> (uses semantic ID: "Department_1")
|
||||||
|
/// </summary>
|
||||||
|
public class Department : IId<int>
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Back-reference to parent (should use $ref to Company semantic ID)
|
||||||
|
public Company? ParentCompany { get; set; }
|
||||||
|
|
||||||
|
// Collection of IId<int> items
|
||||||
|
public List<Employee> Employees { get; set; } = new();
|
||||||
|
|
||||||
|
// Non-IId object (uses numeric ID)
|
||||||
|
public Address? OfficeAddress { get; set; }
|
||||||
|
|
||||||
|
// Array of IId<Guid> items
|
||||||
|
public Project[] ActiveProjects { get; set; } = Array.Empty<Project>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Level 3 - Employee implementing IId<int> (uses semantic ID: "Employee_1")
|
||||||
|
/// </summary>
|
||||||
|
public class Employee : IId<int>
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string FullName { get; set; } = string.Empty;
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Back-reference to department (should use $ref to Department semantic ID)
|
||||||
|
public Department? Department { get; set; }
|
||||||
|
|
||||||
|
// Non-IId object (uses numeric ID)
|
||||||
|
public ContactInfo? Contact { get; set; }
|
||||||
|
|
||||||
|
// Collection of IId<Guid> items
|
||||||
|
public List<Project> AssignedProjects { get; set; } = new();
|
||||||
|
|
||||||
|
// Collection of non-IId items (uses numeric IDs)
|
||||||
|
public List<Skill> Skills { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Level 4 - Project implementing IId<Guid> (uses semantic ID: "Project_guid")
|
||||||
|
/// </summary>
|
||||||
|
public class Project : IId<Guid>
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string ProjectName { get; set; } = string.Empty;
|
||||||
|
public DateTime StartDate { get; set; }
|
||||||
|
|
||||||
|
// Back-reference to department (should use $ref to Department semantic ID)
|
||||||
|
public Department? OwningDepartment { get; set; }
|
||||||
|
|
||||||
|
// Collection of IId<int> - circular reference to employees
|
||||||
|
public List<Employee> TeamMembers { get; set; } = new();
|
||||||
|
|
||||||
|
// Array of non-IId items
|
||||||
|
public Milestone[] Milestones { get; set; } = Array.Empty<Milestone>();
|
||||||
|
|
||||||
|
// Collection of IId<long> items
|
||||||
|
public List<ProjectTask> Tasks { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Level 5 - ProjectTask implementing IId<long> (uses semantic ID: "ProjectTask_1")
|
||||||
|
/// Renamed from Task to avoid conflict with System.Threading.Tasks.Task
|
||||||
|
/// </summary>
|
||||||
|
public class ProjectTask : IId<long>
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
|
||||||
|
// Back-reference to project (should use $ref to Project semantic ID)
|
||||||
|
public Project? ParentProject { get; set; }
|
||||||
|
|
||||||
|
// Reference to employee (should use $ref to Employee semantic ID)
|
||||||
|
public Employee? AssignedTo { get; set; }
|
||||||
|
|
||||||
|
// Non-IId object
|
||||||
|
public TaskMetadata? Metadata { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Non-IId class - Address (uses standard numeric $id)
|
||||||
|
/// </summary>
|
||||||
|
public class Address
|
||||||
|
{
|
||||||
|
public string Street { get; set; } = string.Empty;
|
||||||
|
public string City { get; set; } = string.Empty;
|
||||||
|
public string Country { get; set; } = string.Empty;
|
||||||
|
public string PostalCode { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Non-IId class - ContactInfo (uses standard numeric $id)
|
||||||
|
/// </summary>
|
||||||
|
public class ContactInfo
|
||||||
|
{
|
||||||
|
public string Phone { get; set; } = string.Empty;
|
||||||
|
public string Mobile { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Shared address reference (should use $ref to numeric ID)
|
||||||
|
public Address? HomeAddress { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Non-IId class - Skill (uses standard numeric $id)
|
||||||
|
/// </summary>
|
||||||
|
public class Skill
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public int Level { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Non-IId class - Milestone (uses standard numeric $id)
|
||||||
|
/// </summary>
|
||||||
|
public class Milestone
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public DateTime DueDate { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Non-IId class - TaskMetadata (uses standard numeric $id)
|
||||||
|
/// </summary>
|
||||||
|
public class TaskMetadata
|
||||||
|
{
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime? ModifiedAt { get; set; }
|
||||||
|
public string CreatedBy { get; set; } = string.Empty;
|
||||||
|
public int Priority { get; set; }
|
||||||
|
public string[] Tags { get; set; } = Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class JsonExtensionTests
|
||||||
|
{
|
||||||
|
private FruitBankSignalRClient _signalRClient = null!;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInit()
|
||||||
|
{
|
||||||
|
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
|
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||||
|
{
|
||||||
|
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task GetMeasuringUsersTest()
|
||||||
|
{
|
||||||
|
var users = await _signalRClient.GetMeasuringUsers();
|
||||||
|
|
||||||
|
Assert.IsNotNull(users);
|
||||||
|
Assert.IsTrue(users.Count != 0);
|
||||||
|
Assert.IsTrue(users.All(x => !x.Email.IsNullOrEmpty() && !x.Deleted));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task RealOrderDto_JSON_Merge_Ref_Test()
|
||||||
|
{
|
||||||
|
var initialCount = 28;
|
||||||
|
List<OrderDto>? pendingOrderDtos = (await _signalRClient.GetPendingOrderDtos())?.Take(initialCount).ToList();
|
||||||
|
|
||||||
|
Assert.IsNotNull(pendingOrderDtos);
|
||||||
|
Assert.IsTrue(pendingOrderDtos.Count != 0);
|
||||||
|
|
||||||
|
var itemToDuplicate = pendingOrderDtos[0];
|
||||||
|
var listWithDuplication = pendingOrderDtos.ToList();
|
||||||
|
listWithDuplication.Add(itemToDuplicate);
|
||||||
|
|
||||||
|
var settings = SerializeObjectExtensions.Options;
|
||||||
|
var dtNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
listWithDuplication[0].PaidDateUtc = dtNow;
|
||||||
|
|
||||||
|
var itemWithGenericAttributes = listWithDuplication.FirstOrDefault(x => x.GenericAttributes?.Count > 0);
|
||||||
|
Assert.IsNotNull(itemWithGenericAttributes, "Nincs olyan OrderDto a listában, amelynek lenne GenericAttributes eleme!");
|
||||||
|
|
||||||
|
itemWithGenericAttributes.GenericAttributes[0].CreatedOrUpdatedDateUTC = dtNow;
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(listWithDuplication, settings);
|
||||||
|
|
||||||
|
Assert.IsTrue(json.Contains("$id"), "JSON-nak tartalmaznia kell $id tokeneket");
|
||||||
|
Assert.IsTrue(json.Contains("$ref"), "JSON-nak tartalmaznia kell $ref tokeneket");
|
||||||
|
|
||||||
|
pendingOrderDtos.DeepPopulateWithMerge(json, settings);
|
||||||
|
|
||||||
|
Assert.AreEqual(initialCount, pendingOrderDtos.Count);
|
||||||
|
Assert.IsTrue(itemToDuplicate.PaidDateUtc == dtNow);
|
||||||
|
Assert.IsTrue(itemWithGenericAttributes.GenericAttributes[0].CreatedOrUpdatedDateUTC == dtNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comprehensive test for hybrid reference handling:
|
||||||
|
/// - 5 levels deep hierarchy
|
||||||
|
/// - IId<int>, IId<Guid>, IId<long> types (semantic IDs)
|
||||||
|
/// - Non-IId types (numeric IDs)
|
||||||
|
/// - Collections (List<T>), Arrays, and single object properties
|
||||||
|
/// - Back-references to parent objects
|
||||||
|
/// - Shared object references
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void HybridReferenceHandling_DeepHierarchy_Test()
|
||||||
|
{
|
||||||
|
// Arrange - Create test data with 5 levels of depth
|
||||||
|
var sharedAddress = new Address
|
||||||
|
{
|
||||||
|
Street = "123 Shared Street",
|
||||||
|
City = "Budapest",
|
||||||
|
Country = "Hungary",
|
||||||
|
PostalCode = "1111"
|
||||||
|
};
|
||||||
|
|
||||||
|
var company = new Company
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Acme Corporation",
|
||||||
|
HeadquartersAddress = sharedAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
var department1 = new Department
|
||||||
|
{
|
||||||
|
Id = 101,
|
||||||
|
Name = "Engineering",
|
||||||
|
ParentCompany = company, // Back-reference to Level 1
|
||||||
|
OfficeAddress = new Address
|
||||||
|
{
|
||||||
|
Street = "456 Tech Ave",
|
||||||
|
City = "Budapest",
|
||||||
|
Country = "Hungary",
|
||||||
|
PostalCode = "2222"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var department2 = new Department
|
||||||
|
{
|
||||||
|
Id = 102,
|
||||||
|
Name = "Marketing",
|
||||||
|
ParentCompany = company, // Back-reference to Level 1 (same company)
|
||||||
|
OfficeAddress = sharedAddress // Shared address reference
|
||||||
|
};
|
||||||
|
|
||||||
|
company.Departments.Add(department1);
|
||||||
|
company.Departments.Add(department2);
|
||||||
|
|
||||||
|
var employee1 = new Employee
|
||||||
|
{
|
||||||
|
Id = 1001,
|
||||||
|
FullName = "John Doe",
|
||||||
|
Email = "john.doe@acme.com",
|
||||||
|
Department = department1, // Back-reference to Level 2
|
||||||
|
Contact = new ContactInfo
|
||||||
|
{
|
||||||
|
Phone = "+36-1-111-1111",
|
||||||
|
Mobile = "+36-30-111-1111",
|
||||||
|
HomeAddress = sharedAddress // Shared address reference
|
||||||
|
},
|
||||||
|
Skills = new List<Skill>
|
||||||
|
{
|
||||||
|
new() { Name = "C#", Level = 5 },
|
||||||
|
new() { Name = "Azure", Level = 4 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var employee2 = new Employee
|
||||||
|
{
|
||||||
|
Id = 1002,
|
||||||
|
FullName = "Jane Smith",
|
||||||
|
Email = "jane.smith@acme.com",
|
||||||
|
Department = department1, // Back-reference to same department
|
||||||
|
Contact = new ContactInfo
|
||||||
|
{
|
||||||
|
Phone = "+36-1-222-2222",
|
||||||
|
Mobile = "+36-30-222-2222",
|
||||||
|
HomeAddress = new Address
|
||||||
|
{
|
||||||
|
Street = "789 Home Lane",
|
||||||
|
City = "Debrecen",
|
||||||
|
Country = "Hungary",
|
||||||
|
PostalCode = "3333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Skills = new List<Skill>
|
||||||
|
{
|
||||||
|
new() { Name = "JavaScript", Level = 5 },
|
||||||
|
new() { Name = "React", Level = 4 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var employee3 = new Employee
|
||||||
|
{
|
||||||
|
Id = 1003,
|
||||||
|
FullName = "Bob Wilson",
|
||||||
|
Email = "bob.wilson@acme.com",
|
||||||
|
Department = department2, // Different department
|
||||||
|
Skills = new List<Skill>
|
||||||
|
{
|
||||||
|
new() { Name = "Marketing", Level = 5 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
department1.Employees.Add(employee1);
|
||||||
|
department1.Employees.Add(employee2);
|
||||||
|
department2.Employees.Add(employee3);
|
||||||
|
|
||||||
|
// Board members array
|
||||||
|
company.BoardMembers = new[] { employee1, employee3 }; // Shared employee references
|
||||||
|
|
||||||
|
var project1 = new Project
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
ProjectName = "Project Alpha",
|
||||||
|
StartDate = DateTime.UtcNow.AddMonths(-3),
|
||||||
|
OwningDepartment = department1, // Back-reference to Level 2
|
||||||
|
TeamMembers = new List<Employee> { employee1, employee2 }, // Shared employee references
|
||||||
|
Milestones = new[]
|
||||||
|
{
|
||||||
|
new Milestone { Name = "Phase 1", DueDate = DateTime.UtcNow.AddMonths(-2), IsCompleted = true },
|
||||||
|
new Milestone { Name = "Phase 2", DueDate = DateTime.UtcNow.AddMonths(-1), IsCompleted = true },
|
||||||
|
new Milestone { Name = "Phase 3", DueDate = DateTime.UtcNow.AddMonths(1), IsCompleted = false }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var project2 = new Project
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
ProjectName = "Project Beta",
|
||||||
|
StartDate = DateTime.UtcNow.AddMonths(-1),
|
||||||
|
OwningDepartment = department1, // Same department
|
||||||
|
TeamMembers = new List<Employee> { employee2 }, // Shared employee reference
|
||||||
|
Milestones = new[]
|
||||||
|
{
|
||||||
|
new Milestone { Name = "Initial", DueDate = DateTime.UtcNow.AddMonths(2), IsCompleted = false }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
department1.ActiveProjects = new[] { project1, project2 };
|
||||||
|
|
||||||
|
employee1.AssignedProjects.Add(project1);
|
||||||
|
employee2.AssignedProjects.Add(project1);
|
||||||
|
employee2.AssignedProjects.Add(project2);
|
||||||
|
|
||||||
|
var task1 = new ProjectTask
|
||||||
|
{
|
||||||
|
Id = 10001L,
|
||||||
|
Title = "Implement Feature X",
|
||||||
|
Description = "Detailed implementation of Feature X",
|
||||||
|
IsCompleted = false,
|
||||||
|
ParentProject = project1, // Back-reference to Level 4
|
||||||
|
AssignedTo = employee1, // Reference to Level 3
|
||||||
|
Metadata = new TaskMetadata
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow.AddDays(-10),
|
||||||
|
CreatedBy = "admin",
|
||||||
|
Priority = 1,
|
||||||
|
Tags = new[] { "urgent", "feature", "backend" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var task2 = new ProjectTask
|
||||||
|
{
|
||||||
|
Id = 10002L,
|
||||||
|
Title = "Write Tests",
|
||||||
|
Description = "Unit tests for Feature X",
|
||||||
|
IsCompleted = false,
|
||||||
|
ParentProject = project1, // Same project
|
||||||
|
AssignedTo = employee2, // Different employee
|
||||||
|
Metadata = new TaskMetadata
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow.AddDays(-5),
|
||||||
|
CreatedBy = "admin",
|
||||||
|
Priority = 2,
|
||||||
|
Tags = new[] { "testing", "quality" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var task3 = new ProjectTask
|
||||||
|
{
|
||||||
|
Id = 10003L,
|
||||||
|
Title = "Review Code",
|
||||||
|
Description = "Code review for Feature X",
|
||||||
|
IsCompleted = false,
|
||||||
|
ParentProject = project1,
|
||||||
|
AssignedTo = employee1, // Same employee as task1 (circular reference)
|
||||||
|
Metadata = new TaskMetadata
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow.AddDays(-3),
|
||||||
|
CreatedBy = "lead",
|
||||||
|
Priority = 1,
|
||||||
|
Tags = new[] { "review" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
project1.Tasks.Add(task1);
|
||||||
|
project1.Tasks.Add(task2);
|
||||||
|
project1.Tasks.Add(task3);
|
||||||
|
|
||||||
|
var settings = SerializeObjectExtensions.Options;
|
||||||
|
|
||||||
|
// Act - Serialize
|
||||||
|
var json = JsonConvert.SerializeObject(company, settings);
|
||||||
|
|
||||||
|
// Assert - JSON structure
|
||||||
|
Assert.IsNotNull(json);
|
||||||
|
Assert.IsTrue(json.Length > 0, "JSON should not be empty");
|
||||||
|
|
||||||
|
// Check for semantic IDs (IId<T> types)
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"Company_1\""), "Should contain semantic ID for Company");
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"Department_101\""), "Should contain semantic ID for Department 101");
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"Department_102\""), "Should contain semantic ID for Department 102");
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"Employee_1001\""), "Should contain semantic ID for Employee 1001");
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"Employee_1002\""), "Should contain semantic ID for Employee 1002");
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"Employee_1003\""), "Should contain semantic ID for Employee 1003");
|
||||||
|
Assert.IsTrue(json.Contains("\"$id\":\"ProjectTask_10001\""), "Should contain semantic ID for ProjectTask 10001");
|
||||||
|
|
||||||
|
// Check for $ref tokens (back-references)
|
||||||
|
Assert.IsTrue(json.Contains("\"$ref\":\"Company_1\""), "Should contain $ref to Company");
|
||||||
|
Assert.IsTrue(json.Contains("\"$ref\":\"Department_101\""), "Should contain $ref to Department");
|
||||||
|
Assert.IsTrue(json.Contains("\"$ref\":\"Employee_1001\""), "Should contain $ref to Employee 1001");
|
||||||
|
Assert.IsTrue(json.Contains("\"$ref\":\"Employee_1002\""), "Should contain $ref to Employee 1002");
|
||||||
|
|
||||||
|
// Check for numeric IDs (non-IId types like Address, ContactInfo, etc.)
|
||||||
|
// These should have simple numeric $id values
|
||||||
|
Assert.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(json, @"\$id"":""[0-9]+"""),
|
||||||
|
"Should contain numeric $id for non-IId types");
|
||||||
|
|
||||||
|
// Act - Deserialize
|
||||||
|
var deserializedCompany = JsonConvert.DeserializeObject<Company>(json, settings);
|
||||||
|
|
||||||
|
// Assert - Deserialized structure
|
||||||
|
Assert.IsNotNull(deserializedCompany);
|
||||||
|
Assert.AreEqual(1, deserializedCompany.Id);
|
||||||
|
Assert.AreEqual("Acme Corporation", deserializedCompany.Name);
|
||||||
|
|
||||||
|
// Verify departments
|
||||||
|
Assert.AreEqual(2, deserializedCompany.Departments.Count);
|
||||||
|
var deserializedDept1 = deserializedCompany.Departments.First(d => d.Id == 101);
|
||||||
|
var deserializedDept2 = deserializedCompany.Departments.First(d => d.Id == 102);
|
||||||
|
|
||||||
|
// Verify back-references to company
|
||||||
|
Assert.AreSame(deserializedCompany, deserializedDept1.ParentCompany,
|
||||||
|
"Department1's ParentCompany should be the same instance as deserializedCompany");
|
||||||
|
Assert.AreSame(deserializedCompany, deserializedDept2.ParentCompany,
|
||||||
|
"Department2's ParentCompany should be the same instance as deserializedCompany");
|
||||||
|
|
||||||
|
// Verify employees
|
||||||
|
Assert.AreEqual(2, deserializedDept1.Employees.Count);
|
||||||
|
var deserializedEmp1 = deserializedDept1.Employees.First(e => e.Id == 1001);
|
||||||
|
var deserializedEmp2 = deserializedDept1.Employees.First(e => e.Id == 1002);
|
||||||
|
|
||||||
|
// Verify employee back-references to department
|
||||||
|
Assert.AreSame(deserializedDept1, deserializedEmp1.Department,
|
||||||
|
"Employee1's Department should be the same instance as deserializedDept1");
|
||||||
|
Assert.AreSame(deserializedDept1, deserializedEmp2.Department,
|
||||||
|
"Employee2's Department should be the same instance as deserializedDept1");
|
||||||
|
|
||||||
|
// Verify board members are same instances as department employees
|
||||||
|
Assert.AreEqual(2, deserializedCompany.BoardMembers.Length);
|
||||||
|
Assert.AreSame(deserializedEmp1, deserializedCompany.BoardMembers.First(e => e.Id == 1001),
|
||||||
|
"BoardMember should be the same instance as deserializedEmp1");
|
||||||
|
|
||||||
|
// Verify projects
|
||||||
|
Assert.AreEqual(2, deserializedDept1.ActiveProjects.Length);
|
||||||
|
var deserializedProject1 = deserializedDept1.ActiveProjects.First(p => p.ProjectName == "Project Alpha");
|
||||||
|
|
||||||
|
// Verify project back-reference to department
|
||||||
|
Assert.AreSame(deserializedDept1, deserializedProject1.OwningDepartment,
|
||||||
|
"Project1's OwningDepartment should be the same instance as deserializedDept1");
|
||||||
|
|
||||||
|
// Verify project team members are same instances
|
||||||
|
Assert.IsTrue(deserializedProject1.TeamMembers.Any(e => ReferenceEquals(e, deserializedEmp1)),
|
||||||
|
"Project1's TeamMembers should contain the same instance as deserializedEmp1");
|
||||||
|
Assert.IsTrue(deserializedProject1.TeamMembers.Any(e => ReferenceEquals(e, deserializedEmp2)),
|
||||||
|
"Project1's TeamMembers should contain the same instance as deserializedEmp2");
|
||||||
|
|
||||||
|
// Verify tasks
|
||||||
|
Assert.AreEqual(3, deserializedProject1.Tasks.Count);
|
||||||
|
var deserializedTask1 = deserializedProject1.Tasks.First(t => t.Id == 10001L);
|
||||||
|
|
||||||
|
// Verify task back-references
|
||||||
|
Assert.AreSame(deserializedProject1, deserializedTask1.ParentProject,
|
||||||
|
"Task1's ParentProject should be the same instance as deserializedProject1");
|
||||||
|
Assert.AreSame(deserializedEmp1, deserializedTask1.AssignedTo,
|
||||||
|
"Task1's AssignedTo should be the same instance as deserializedEmp1");
|
||||||
|
|
||||||
|
// Verify shared Address instances (non-IId type)
|
||||||
|
Assert.AreSame(deserializedCompany.HeadquartersAddress, deserializedDept2.OfficeAddress,
|
||||||
|
"HeadquartersAddress and Dept2's OfficeAddress should be the same instance (shared Address)");
|
||||||
|
Assert.AreSame(deserializedCompany.HeadquartersAddress, deserializedEmp1.Contact?.HomeAddress,
|
||||||
|
"HeadquartersAddress and Emp1's HomeAddress should be the same instance (shared Address)");
|
||||||
|
|
||||||
|
// Verify milestones (non-IId array)
|
||||||
|
Assert.AreEqual(3, deserializedProject1.Milestones.Length);
|
||||||
|
Assert.IsTrue(deserializedProject1.Milestones.Any(m => m.Name == "Phase 1" && m.IsCompleted));
|
||||||
|
|
||||||
|
// Verify skills (non-IId collection)
|
||||||
|
Assert.AreEqual(2, deserializedEmp1.Skills.Count);
|
||||||
|
Assert.IsTrue(deserializedEmp1.Skills.Any(s => s.Name == "C#" && s.Level == 5));
|
||||||
|
|
||||||
|
// Verify task metadata (non-IId object)
|
||||||
|
Assert.IsNotNull(deserializedTask1.Metadata);
|
||||||
|
Assert.AreEqual("admin", deserializedTask1.Metadata.CreatedBy);
|
||||||
|
Assert.AreEqual(1, deserializedTask1.Metadata.Priority);
|
||||||
|
Assert.AreEqual(3, deserializedTask1.Metadata.Tags.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test for DeepPopulateWithMerge with hybrid references
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void HybridReferenceHandling_DeepPopulateWithMerge_Test()
|
||||||
|
{
|
||||||
|
// Arrange - Create initial data
|
||||||
|
var company = new Company
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Original Company Name",
|
||||||
|
HeadquartersAddress = new Address { City = "Original City" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var department = new Department
|
||||||
|
{
|
||||||
|
Id = 101,
|
||||||
|
Name = "Original Department",
|
||||||
|
ParentCompany = company
|
||||||
|
};
|
||||||
|
company.Departments.Add(department);
|
||||||
|
|
||||||
|
var employee = new Employee
|
||||||
|
{
|
||||||
|
Id = 1001,
|
||||||
|
FullName = "Original Name",
|
||||||
|
Email = "original@email.com",
|
||||||
|
Department = department
|
||||||
|
};
|
||||||
|
department.Employees.Add(employee);
|
||||||
|
|
||||||
|
// Create modified version
|
||||||
|
var modifiedCompany = new Company
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Modified Company Name",
|
||||||
|
HeadquartersAddress = new Address { City = "Modified City" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var modifiedDepartment = new Department
|
||||||
|
{
|
||||||
|
Id = 101,
|
||||||
|
Name = "Modified Department",
|
||||||
|
ParentCompany = modifiedCompany
|
||||||
|
};
|
||||||
|
modifiedCompany.Departments.Add(modifiedDepartment);
|
||||||
|
|
||||||
|
var modifiedEmployee = new Employee
|
||||||
|
{
|
||||||
|
Id = 1001,
|
||||||
|
FullName = "Modified Name",
|
||||||
|
Email = "modified@email.com",
|
||||||
|
Department = modifiedDepartment
|
||||||
|
};
|
||||||
|
modifiedDepartment.Employees.Add(modifiedEmployee);
|
||||||
|
|
||||||
|
// Add a new employee in the modified version
|
||||||
|
var newEmployee = new Employee
|
||||||
|
{
|
||||||
|
Id = 1002,
|
||||||
|
FullName = "New Employee",
|
||||||
|
Email = "new@email.com",
|
||||||
|
Department = modifiedDepartment
|
||||||
|
};
|
||||||
|
modifiedDepartment.Employees.Add(newEmployee);
|
||||||
|
|
||||||
|
var settings = SerializeObjectExtensions.Options;
|
||||||
|
var modifiedJson = JsonConvert.SerializeObject(modifiedCompany, settings);
|
||||||
|
|
||||||
|
// Store original references
|
||||||
|
var originalCompanyRef = company;
|
||||||
|
var originalDepartmentRef = department;
|
||||||
|
var originalEmployeeRef = employee;
|
||||||
|
|
||||||
|
// Act - Deep populate with merge
|
||||||
|
company.DeepPopulateWithMerge(modifiedJson, settings);
|
||||||
|
|
||||||
|
// Assert - Same instances should be updated, not replaced
|
||||||
|
Assert.AreSame(originalCompanyRef, company, "Company instance should be the same");
|
||||||
|
Assert.AreEqual("Modified Company Name", company.Name, "Company name should be updated");
|
||||||
|
Assert.AreEqual("Modified City", company.HeadquartersAddress.City, "Address should be updated");
|
||||||
|
|
||||||
|
Assert.AreEqual(1, company.Departments.Count, "Should still have 1 department");
|
||||||
|
Assert.AreSame(originalDepartmentRef, company.Departments[0], "Department instance should be the same");
|
||||||
|
Assert.AreEqual("Modified Department", company.Departments[0].Name, "Department name should be updated");
|
||||||
|
|
||||||
|
// The original employee should be updated
|
||||||
|
var updatedEmployee = company.Departments[0].Employees.FirstOrDefault(e => e.Id == 1001);
|
||||||
|
Assert.IsNotNull(updatedEmployee);
|
||||||
|
Assert.AreSame(originalEmployeeRef, updatedEmployee, "Original employee instance should be the same");
|
||||||
|
Assert.AreEqual("Modified Name", updatedEmployee.FullName, "Employee name should be updated");
|
||||||
|
Assert.AreEqual("modified@email.com", updatedEmployee.Email, "Employee email should be updated");
|
||||||
|
|
||||||
|
// New employee should be added
|
||||||
|
Assert.AreEqual(2, company.Departments[0].Employees.Count, "Should have 2 employees after merge");
|
||||||
|
var addedEmployee = company.Departments[0].Employees.FirstOrDefault(e => e.Id == 1002);
|
||||||
|
Assert.IsNotNull(addedEmployee, "New employee should be added");
|
||||||
|
Assert.AreEqual("New Employee", addedEmployee.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that verifies circular references don't cause infinite loops
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void HybridReferenceHandling_CircularReferences_NoInfiniteLoop_Test()
|
||||||
|
{
|
||||||
|
// Arrange - Create circular reference structure
|
||||||
|
var company = new Company { Id = 1, Name = "Test Company" };
|
||||||
|
var department = new Department { Id = 101, Name = "Test Dept", ParentCompany = company };
|
||||||
|
company.Departments.Add(department);
|
||||||
|
|
||||||
|
var employee = new Employee { Id = 1001, FullName = "Test Employee", Department = department };
|
||||||
|
department.Employees.Add(employee);
|
||||||
|
|
||||||
|
var project = new Project
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
ProjectName = "Test Project",
|
||||||
|
OwningDepartment = department,
|
||||||
|
TeamMembers = new List<Employee> { employee }
|
||||||
|
};
|
||||||
|
department.ActiveProjects = new[] { project };
|
||||||
|
employee.AssignedProjects.Add(project);
|
||||||
|
|
||||||
|
var task = new ProjectTask
|
||||||
|
{
|
||||||
|
Id = 10001L,
|
||||||
|
Title = "Test Task",
|
||||||
|
ParentProject = project,
|
||||||
|
AssignedTo = employee // Circular: Task -> Employee -> Project -> Task's Project
|
||||||
|
};
|
||||||
|
project.Tasks.Add(task);
|
||||||
|
|
||||||
|
var settings = SerializeObjectExtensions.Options;
|
||||||
|
|
||||||
|
// Act - Should not throw StackOverflowException or timeout
|
||||||
|
var json = JsonConvert.SerializeObject(company, settings);
|
||||||
|
var deserialized = JsonConvert.DeserializeObject<Company>(json, settings);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(deserialized);
|
||||||
|
Assert.AreEqual(1, deserialized.Id);
|
||||||
|
|
||||||
|
var deserializedDept = deserialized.Departments[0];
|
||||||
|
var deserializedEmployee = deserializedDept.Employees[0];
|
||||||
|
var deserializedProject = deserializedDept.ActiveProjects[0];
|
||||||
|
var deserializedTask = deserializedProject.Tasks[0];
|
||||||
|
|
||||||
|
// Verify circular references are correctly resolved
|
||||||
|
Assert.AreSame(deserialized, deserializedDept.ParentCompany);
|
||||||
|
Assert.AreSame(deserializedDept, deserializedEmployee.Department);
|
||||||
|
Assert.AreSame(deserializedDept, deserializedProject.OwningDepartment);
|
||||||
|
Assert.AreSame(deserializedProject, deserializedTask.ParentProject);
|
||||||
|
Assert.AreSame(deserializedEmployee, deserializedTask.AssignedTo);
|
||||||
|
Assert.AreSame(deserializedEmployee, deserializedProject.TeamMembers[0]);
|
||||||
|
Assert.AreSame(deserializedProject, deserializedEmployee.AssignedProjects[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
using AyCode.Core.Enums;
|
using AyCode.Core.Enums;
|
||||||
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using FruitBank.Common;
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Loggers;
|
using FruitBank.Common.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
using Nop.Core.Domain.Payments;
|
using Nop.Core.Domain.Payments;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using Nop.Core.Domain.Common;
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Tests;
|
namespace FruitBankHybrid.Shared.Tests;
|
||||||
|
|
||||||
|
|
@ -28,6 +34,38 @@ public sealed class OrderClientTests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task GetAllStockTakings()
|
||||||
|
{
|
||||||
|
var stockTakings = await _signalRClient.GetStockTakings(true);
|
||||||
|
|
||||||
|
Assert.IsNotNull(stockTakings);
|
||||||
|
Assert.IsTrue(stockTakings.Count != 0);
|
||||||
|
|
||||||
|
Assert.IsTrue(stockTakings.All(o => o.StockTakingItems.All(oi => oi.Product != null && oi.Product.Id == oi.ProductId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task GetAllStockTakingItems()
|
||||||
|
{
|
||||||
|
var stockTakingItems = await _signalRClient.GetStockTakingItems();
|
||||||
|
|
||||||
|
Assert.IsNotNull(stockTakingItems);
|
||||||
|
Assert.IsTrue(stockTakingItems.Count != 0);
|
||||||
|
|
||||||
|
Assert.IsTrue(stockTakingItems.All(oi => oi.StockTaking != null && oi.Product != null && oi.Product.Id == oi.ProductId));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task GetAllStockTakingItemById()
|
||||||
|
{
|
||||||
|
var stockTakingItem = await _signalRClient.GetStockTakingItemsById(100);
|
||||||
|
|
||||||
|
Assert.IsNotNull(stockTakingItem);
|
||||||
|
Assert.IsNotNull(stockTakingItem.Product);
|
||||||
|
Assert.IsNotNull(stockTakingItem.StockTaking);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task GetAllOrderDtos()
|
public async Task GetAllOrderDtos()
|
||||||
{
|
{
|
||||||
|
|
@ -56,10 +94,53 @@ public sealed class OrderClientTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task GetPendingOrderDtos()
|
public async Task GetPendingOrderDtos()
|
||||||
{
|
{
|
||||||
var pendingOrderDtos = await _signalRClient.GetPendingOrderDtos();
|
var initialCount = 28;
|
||||||
|
List<OrderDto>? pendingOrderDtos = (await _signalRClient.GetPendingOrderDtos())?.Take(initialCount).ToList();
|
||||||
|
|
||||||
Assert.IsNotNull(pendingOrderDtos);
|
Assert.IsNotNull(pendingOrderDtos);
|
||||||
|
|
||||||
|
// Másolat létrehozása a frissítendő adatok generálásához
|
||||||
|
List<OrderDto>? pendingOrderDtos2 = pendingOrderDtos.ToList();
|
||||||
|
|
||||||
|
Assert.IsNotNull(pendingOrderDtos2);
|
||||||
|
Assert.AreEqual(initialCount, pendingOrderDtos2.Count);
|
||||||
|
|
||||||
|
// ÚJ BEÁLLÍTÁSOK A RESOLVER-REL ÉS CONTEXT-EL
|
||||||
|
//var settings = new JsonSerializerSettings
|
||||||
|
//{
|
||||||
|
// ContractResolver = new UnifiedMergeContractResolver(),
|
||||||
|
// ObjectCreationHandling = ObjectCreationHandling.Replace,
|
||||||
|
// Context = new StreamingContext(StreamingContextStates.All, new Dictionary<object, object>()),
|
||||||
|
|
||||||
|
// // Alapvető beállítások
|
||||||
|
// PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
||||||
|
// ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||||
|
// NullValueHandling = NullValueHandling.Ignore,
|
||||||
|
//};
|
||||||
|
|
||||||
|
var dtNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// Eredeti objektum referenciájának eltárolása
|
||||||
|
var orderDto1 = pendingOrderDtos[0];
|
||||||
|
|
||||||
|
// Frissítjük a másolatot
|
||||||
|
pendingOrderDtos2[0].PaidDateUtc = dtNow;
|
||||||
|
pendingOrderDtos2[0].GenericAttributes[0].CreatedOrUpdatedDateUTC = dtNow;
|
||||||
|
|
||||||
|
pendingOrderDtos2.CopyTo(pendingOrderDtos);
|
||||||
|
// Szerializálás a Merge Resolverrel
|
||||||
|
//var json = pendingOrderDtos2.ToJson(settings);
|
||||||
|
|
||||||
|
//Assert.IsTrue(json.Contains("$id"));
|
||||||
|
//Assert.IsTrue(json.Contains("$ref"));
|
||||||
|
|
||||||
|
//pendingOrderDtos.DeepPopulateWithMerge(json, settings);
|
||||||
|
|
||||||
|
Assert.IsTrue(pendingOrderDtos.Count == pendingOrderDtos2.Count, $"A listák méretének egyeznie kell: {pendingOrderDtos.Count} != {pendingOrderDtos2.Count}");
|
||||||
|
|
||||||
|
Assert.IsTrue(orderDto1.PaidDateUtc == dtNow, "A PaidDateUtc mezőnek frissülnie kellett a Merge során.");
|
||||||
|
Assert.IsTrue(orderDto1.GenericAttributes[0].CreatedOrUpdatedDateUTC == dtNow, "A beágyazott GenericAttribute dátumának frissülnie kellett.");
|
||||||
|
|
||||||
Assert.IsTrue(pendingOrderDtos.All(o => o.OrderStatus == OrderStatus.Pending));
|
Assert.IsTrue(pendingOrderDtos.All(o => o.OrderStatus == OrderStatus.Pending));
|
||||||
Assert.IsTrue(pendingOrderDtos.Count != 0);
|
Assert.IsTrue(pendingOrderDtos.Count != 0);
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +157,7 @@ public sealed class OrderClientTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(new[] {1,2,4,7})]
|
[DataRow(new[] { 1, 2, 4, 7 })]
|
||||||
public async Task GetOrderDtoByIds(int[] orderIds)
|
public async Task GetOrderDtoByIds(int[] orderIds)
|
||||||
{
|
{
|
||||||
var orderDtoList = await _signalRClient.GetAllOrderDtoByIds(orderIds);
|
var orderDtoList = await _signalRClient.GetAllOrderDtoByIds(orderIds);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,320 @@
|
||||||
|
using AyCode.Core.Enums;
|
||||||
|
using AyCode.Core.Loggers;
|
||||||
|
using AyCode.Utils.Extensions;
|
||||||
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Dtos;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBank.Common.Interfaces;
|
||||||
|
using FruitBank.Common.Loggers;
|
||||||
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using FruitBank.Common.SignalRs;
|
||||||
|
using AyCode.Services.SignalRs;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Teszt a TestSignalREndpoint-hoz.
|
||||||
|
/// FONTOS: A SANDBOX-ot manuálisan kell elindítani a tesztek futtatása elõtt!
|
||||||
|
/// Indítás: dotnet run --project Mango.Sandbox.EndPoints --urls http://localhost:59579
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class SandboxEndpointSimpleTests
|
||||||
|
{
|
||||||
|
private static readonly string SandboxUrl = FruitBankConstClient.BaseUrl; //"http://localhost:59579";
|
||||||
|
private static readonly string HubUrl = $"{SandboxUrl}/fbHub";
|
||||||
|
|
||||||
|
// Teszt SignalR Tags (TestSignalRTags-bõl)
|
||||||
|
private const int PingTag = SignalRTags.PingTag;
|
||||||
|
private const int EchoTag = SignalRTags.EchoTag;
|
||||||
|
private const int GetTestItemsTag = 9003;
|
||||||
|
|
||||||
|
private FruitBankSignalRClient _signalRClient = null!;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInit()
|
||||||
|
{
|
||||||
|
if (!SandboxUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
|
||||||
|
|
||||||
|
_signalRClient = new FruitBankSignalRClient(new List<IAcLogWriterClientBase>
|
||||||
|
{
|
||||||
|
//new ConsoleLogWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)),
|
||||||
|
new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(SandboxEndpointSimpleTests))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#region HTTP Endpoint Tests
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task HealthEndpoint_ReturnsSuccess()
|
||||||
|
{
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
var response = await httpClient.GetAsync($"{SandboxUrl}/health");
|
||||||
|
Assert.IsTrue(response.IsSuccessStatusCode, $"Health endpoint returned {response.StatusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task RootEndpoint_ReturnsSandboxIsRunning()
|
||||||
|
{
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
var response = await httpClient.GetStringAsync(SandboxUrl);
|
||||||
|
Assert.AreEqual("SANDBOX is running!", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SignalR Connection Tests
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task SignalR_Negotiate_ReturnsSuccess()
|
||||||
|
{
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
var response = await httpClient.PostAsync($"{HubUrl}/negotiate?negotiateVersion=1", null);
|
||||||
|
Assert.IsTrue(response.IsSuccessStatusCode, $"SignalR negotiate returned {response.StatusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task SignalR_Connect_Succeeds()
|
||||||
|
{
|
||||||
|
var testItems = await _signalRClient.GetAllAsync<List<TestItem>>(GetTestItemsTag);
|
||||||
|
Assert.IsNotNull(testItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public decimal Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public async Task SignalR_Connect_Succeeds()
|
||||||
|
//{
|
||||||
|
// var connection = new HubConnectionBuilder()
|
||||||
|
// .WithUrl(HubUrl)
|
||||||
|
// .Build();
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// await connection.StartAsync();
|
||||||
|
// Assert.AreEqual(HubConnectionState.Connected, connection.State);
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// await connection.StopAsync();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region TestSignalREndpoint Tests
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public async Task SignalR_Ping_ReturnsResponse()
|
||||||
|
//{
|
||||||
|
// var testMessage = "Hello SignalR!";
|
||||||
|
// await TestSignalREndpoint(PingTag, testMessage, "Ping", response =>
|
||||||
|
// {
|
||||||
|
// Assert.IsNotNull(response, "Response should not be null");
|
||||||
|
|
||||||
|
// // Parse JSON response
|
||||||
|
// using var jsonDoc = JsonDocument.Parse(response);
|
||||||
|
// var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
|
// // Ellenõrizzük, hogy van Message property
|
||||||
|
// Assert.IsTrue(root.TryGetProperty("Message", out var messageElement) ||
|
||||||
|
// root.TryGetProperty("message", out messageElement),
|
||||||
|
// "Response should contain 'Message' property");
|
||||||
|
|
||||||
|
// Console.WriteLine($"[Ping] Received message: {messageElement.GetString()}");
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public async Task SignalR_Echo_ReturnsEchoedData()
|
||||||
|
//{
|
||||||
|
// var request = new { Id = 42, Name = "TestName" };
|
||||||
|
// await TestSignalREndpoint(EchoTag, request, "Echo", response =>
|
||||||
|
// {
|
||||||
|
// Assert.IsNotNull(response, "Response should not be null");
|
||||||
|
|
||||||
|
// using var jsonDoc = JsonDocument.Parse(response);
|
||||||
|
// var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
|
// // Ellenõrizzük az Id-t
|
||||||
|
// Assert.IsTrue(root.TryGetProperty("Id", out var idElement) ||
|
||||||
|
// root.TryGetProperty("id", out idElement),
|
||||||
|
// "Response should contain 'Id' property");
|
||||||
|
// Assert.AreEqual(42, idElement.GetInt32(), "Id should be 42");
|
||||||
|
|
||||||
|
// // Ellenõrizzük a Name-et
|
||||||
|
// Assert.IsTrue(root.TryGetProperty("Name", out var nameElement) ||
|
||||||
|
// root.TryGetProperty("name", out nameElement),
|
||||||
|
// "Response should contain 'Name' property");
|
||||||
|
// Assert.AreEqual("TestName", nameElement.GetString(), "Name should be 'TestName'");
|
||||||
|
|
||||||
|
// Console.WriteLine($"[Echo] Received: Id={idElement.GetInt32()}, Name={nameElement.GetString()}");
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[TestMethod]
|
||||||
|
//public async Task SignalR_GetTestItems_ReturnsItemList()
|
||||||
|
//{
|
||||||
|
// await TestSignalREndpoint(GetTestItemsTag, null, "GetTestItems", response =>
|
||||||
|
// {
|
||||||
|
// Assert.IsNotNull(response, "Response should not be null");
|
||||||
|
|
||||||
|
// using var jsonDoc = JsonDocument.Parse(response);
|
||||||
|
// var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
|
// // Ellenõrizzük, hogy tömb-e
|
||||||
|
// Assert.AreEqual(JsonValueKind.Array, root.ValueKind, "Response should be an array");
|
||||||
|
// Assert.IsTrue(root.GetArrayLength() > 0, "Array should have items");
|
||||||
|
|
||||||
|
// Console.WriteLine($"[GetTestItems] Received {root.GetArrayLength()} items");
|
||||||
|
|
||||||
|
// // Ellenõrizzük az elsõ elemet
|
||||||
|
// var firstItem = root[0];
|
||||||
|
// Assert.IsTrue(firstItem.TryGetProperty("Id", out _) || firstItem.TryGetProperty("id", out _),
|
||||||
|
// "Item should have 'Id' property");
|
||||||
|
// Assert.IsTrue(firstItem.TryGetProperty("Name", out _) || firstItem.TryGetProperty("name", out _),
|
||||||
|
// "Item should have 'Name' property");
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region EREDETI BUSINESS ENDPOINT TESZTEK - KIKOMMENTEZVE
|
||||||
|
|
||||||
|
//// ===========================================
|
||||||
|
//// === Az alábbi tesztek az eredeti 3 endpoint-ot tesztelik ===
|
||||||
|
//// === Visszaállításhoz: töröld a kommenteket és regisztráld az endpoint-okat a Program.cs-ben ===
|
||||||
|
//// ===========================================
|
||||||
|
|
||||||
|
//// [TestMethod]
|
||||||
|
//// public async Task SignalR_GetMeasuringUsers_ReturnsJson()
|
||||||
|
//// {
|
||||||
|
//// await TestSignalREndpoint(GetMeasuringUsersTag, null, "GetMeasuringUsers");
|
||||||
|
//// }
|
||||||
|
|
||||||
|
//// [TestMethod]
|
||||||
|
//// public async Task SignalR_GetStockQuantityHistoryDtos_ReturnsJson()
|
||||||
|
//// {
|
||||||
|
//// await TestSignalREndpoint(GetStockQuantityHistoryDtosTag, null, "GetStockQuantityHistoryDtos");
|
||||||
|
//// }
|
||||||
|
|
||||||
|
//// [TestMethod]
|
||||||
|
//// public async Task SignalR_GetStockQuantityHistoryDtosByProductId_ReturnsJson()
|
||||||
|
//// {
|
||||||
|
//// // ProductId = 10
|
||||||
|
//// await TestSignalREndpoint(GetStockQuantityHistoryDtosByProductIdTag, 10, "GetStockQuantityHistoryDtosByProductId");
|
||||||
|
//// }
|
||||||
|
|
||||||
|
//// [TestMethod]
|
||||||
|
//// public async Task SignalR_GetShippingDocumentsByShippingId_ReturnsJson()
|
||||||
|
//// {
|
||||||
|
//// // ShippingId = 5
|
||||||
|
//// await TestSignalREndpoint(GetShippingDocumentsByShippingIdTag, 5, "GetShippingDocumentsByShippingId");
|
||||||
|
//// }
|
||||||
|
|
||||||
|
//// [TestMethod]
|
||||||
|
//// public async Task SignalR_GetOrderDtoById_ReturnsJson()
|
||||||
|
//// {
|
||||||
|
//// // OrderId = 15
|
||||||
|
//// await TestSignalREndpoint(GetOrderDtoByIdTag, 15, "GetOrderDtoById");
|
||||||
|
//// }
|
||||||
|
|
||||||
|
//// [TestMethod]
|
||||||
|
//// public async Task SignalR_GetStockTakingItemsById_ReturnsJson()
|
||||||
|
//// {
|
||||||
|
//// // StockTakingItemId = 200
|
||||||
|
//// await TestSignalREndpoint(GetStockTakingItemsByIdTag, 200, "GetStockTakingItemsById");
|
||||||
|
//// }
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Helper Methods
|
||||||
|
|
||||||
|
//private async Task TestSignalREndpoint(int tag, object? parameter, string endpointName, Action<string?>? validateResponse = null)
|
||||||
|
//{
|
||||||
|
// var connection = new HubConnectionBuilder()
|
||||||
|
// .WithUrl(HubUrl)
|
||||||
|
// .Build();
|
||||||
|
|
||||||
|
// string? receivedJson = null;
|
||||||
|
// int receivedTag = -1;
|
||||||
|
// var responseReceived = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
// connection.On<int, byte[]>("ReceiveMessage", (responseTag, data) =>
|
||||||
|
// {
|
||||||
|
// receivedTag = responseTag;
|
||||||
|
// if (data != null && data.Length > 0)
|
||||||
|
// {
|
||||||
|
// receivedJson = Encoding.UTF8.GetString(data);
|
||||||
|
// }
|
||||||
|
// responseReceived.TrySetResult(true);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// await connection.StartAsync();
|
||||||
|
// Assert.AreEqual(HubConnectionState.Connected, connection.State, $"Failed to connect to SignalR hub for {endpointName}");
|
||||||
|
|
||||||
|
// // Készítsük el a request data-t
|
||||||
|
// // Ha nincs paraméter, null-t küldünk (nem üres byte tömböt!)
|
||||||
|
// byte[]? requestData = parameter != null
|
||||||
|
// ? Encoding.UTF8.GetBytes(JsonSerializer.Serialize(parameter))
|
||||||
|
// : null;
|
||||||
|
|
||||||
|
// // A Hub metódus neve: OnReceiveMessage (3 paraméter: messageTag, messageBytes, requestId)
|
||||||
|
// await connection.InvokeAsync("OnReceiveMessage", tag, requestData, (int?)null);
|
||||||
|
|
||||||
|
// var completed = await Task.WhenAny(responseReceived.Task, Task.Delay(15000));
|
||||||
|
|
||||||
|
// if (completed == responseReceived.Task)
|
||||||
|
// {
|
||||||
|
// Console.WriteLine($"[{endpointName}] Response tag: {receivedTag}");
|
||||||
|
// Console.WriteLine($"[{endpointName}] Response JSON: {receivedJson?.Substring(0, Math.Min(500, receivedJson?.Length ?? 0))}...");
|
||||||
|
|
||||||
|
// // Ellenõrizzük, hogy valid JSON-e (ha van adat)
|
||||||
|
// if (!string.IsNullOrEmpty(receivedJson))
|
||||||
|
// {
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// using var jsonDoc = JsonDocument.Parse(receivedJson);
|
||||||
|
// Assert.IsTrue(
|
||||||
|
// jsonDoc.RootElement.ValueKind == JsonValueKind.Array ||
|
||||||
|
// jsonDoc.RootElement.ValueKind == JsonValueKind.Object ||
|
||||||
|
// jsonDoc.RootElement.ValueKind == JsonValueKind.Null,
|
||||||
|
// $"[{endpointName}] Response is not a valid JSON");
|
||||||
|
|
||||||
|
// // Custom validation
|
||||||
|
// validateResponse?.Invoke(receivedJson);
|
||||||
|
// }
|
||||||
|
// catch (JsonException ex)
|
||||||
|
// {
|
||||||
|
// Assert.Fail($"[{endpointName}] Invalid JSON response: {ex.Message}");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// Assert.AreEqual(HubConnectionState.Connected, connection.State,
|
||||||
|
// $"[{endpointName}] Connection was closed - check SANDBOX logs for DI errors");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// Assert.Fail($"[{endpointName}] SignalR error: {ex.Message}. Check SANDBOX logs for missing DI registrations.");
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// if (connection.State == HubConnectionState.Connected)
|
||||||
|
// {
|
||||||
|
// await connection.StopAsync();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Test debugger script for JsonExtensionTests
|
||||||
|
$projectPath = "H:\Applications\Mango\Source\FruitBankHybridApp"
|
||||||
|
Set-Location $projectPath
|
||||||
|
|
||||||
|
Write-Host "Building test project..."
|
||||||
|
dotnet build FruitBankHybrid.Shared.Tests/FruitBankHybrid.Shared.Tests.csproj -c Debug
|
||||||
|
|
||||||
|
Write-Host "`nRunning JsonExtensionTests..."
|
||||||
|
# Use --no-build to avoid the MSBuild conflict
|
||||||
|
dotnet test FruitBankHybrid.Shared.Tests/FruitBankHybrid.Shared.Tests.csproj `
|
||||||
|
--no-build `
|
||||||
|
-c Debug `
|
||||||
|
--filter "ClassName=FruitBankHybrid.Shared.Tests.JsonExtensionTests" `
|
||||||
|
2>&1 | Tee-Object -FilePath "test_results.txt"
|
||||||
|
|
||||||
|
Write-Host "`n=== Test Results ==="
|
||||||
|
Get-Content "test_results.txt" | Select-String -Pattern "FAILED|PASSED|Error|Assert" | tail -50
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
ShowFilterRow="IsMasterGrid" ShowGroupPanel="IsMasterGrid"
|
ShowFilterRow="IsMasterGrid" ShowGroupPanel="IsMasterGrid"
|
||||||
AutoExpandAllGroupRows="false"
|
AutoExpandAllGroupRows="false"
|
||||||
CssClass="@GridCss"
|
CssClass="@GridCss"
|
||||||
ColumnResizeMode="GridColumnResizeMode.NextColumn" VirtualScrollingEnabled="IsMasterGrid"
|
ColumnResizeMode="GridColumnResizeMode.NextColumn"
|
||||||
FilterMenuButtonDisplayMode="@(IsMasterGrid ? GridFilterMenuButtonDisplayMode.Never : GridFilterMenuButtonDisplayMode.Always)">
|
FilterMenuButtonDisplayMode="@(IsMasterGrid ? GridFilterMenuButtonDisplayMode.Never : GridFilterMenuButtonDisplayMode.Always)">
|
||||||
<Columns>
|
<Columns>
|
||||||
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" />
|
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" />
|
||||||
|
|
@ -44,19 +44,19 @@
|
||||||
<DxTabPage Text="Rendelés tételek">
|
<DxTabPage Text="Rendelés tételek">
|
||||||
<GridDetailOrderItemDto OrderItemDtos="orderDto.OrderItemDtos" IsMasterGrid="false" />
|
<GridDetailOrderItemDto OrderItemDtos="orderDto.OrderItemDtos" IsMasterGrid="false" />
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Mérések">
|
<DxTabPage Text="Mérések" Visible="@LoggedInModel.IsDeveloper">
|
||||||
@{
|
@{
|
||||||
var orderItemPalletDtos = orderDto?.OrderItemDtos.SelectMany(oi => oi.OrderItemPallets).ToList() ?? [];
|
var orderItemPalletDtos = orderDto?.OrderItemDtos.SelectMany(oi => oi.OrderItemPallets).ToList() ?? [];
|
||||||
<GridDetailOrderItemPallets OrderItemPallets="orderItemPalletDtos" IsMasterGrid="false"/>
|
<GridDetailOrderItemPallets OrderItemPallets="orderItemPalletDtos" IsMasterGrid="false"/>
|
||||||
}
|
}
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Rendelés jegyzetek">
|
<DxTabPage Text="Rendelés jegyzetek" Visible="@LoggedInModel.IsDeveloper">
|
||||||
@{
|
@{
|
||||||
// var orderItemPalletDtos = orderDto?.OrderItemDtos.SelectMany(oi => oi.OrderItemPallets).ToList() ?? [];
|
// var orderItemPalletDtos = orderDto?.OrderItemDtos.SelectMany(oi => oi.OrderItemPallets).ToList() ?? [];
|
||||||
// <GridDetailOrderItemPallets OrderItemPallets="orderItemPalletDtos" IsMasterGrid="false" />
|
// <GridDetailOrderItemPallets OrderItemPallets="orderItemPalletDtos" IsMasterGrid="false" />
|
||||||
}
|
}
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Speciális jellemzők">
|
<DxTabPage Text="Speciális jellemzők" Visible="@LoggedInModel.IsDeveloper">
|
||||||
@{
|
@{
|
||||||
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(orderDto.GenericAttributes);
|
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(orderDto.GenericAttributes);
|
||||||
<GridGenericAttribute ParentDataItem="@orderDto" GenericAttributes="@genericAttributeDtos" />
|
<GridGenericAttribute ParentDataItem="@orderDto" GenericAttributes="@genericAttributeDtos" />
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
<GridDetailOrderItemPallets OrderItemPallets="orderItemDto.OrderItemPallets" />
|
<GridDetailOrderItemPallets OrderItemPallets="orderItemDto.OrderItemPallets" />
|
||||||
}
|
}
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Speciális jellemzők">
|
<DxTabPage Text="Speciális jellemzők" Visible="@LoggedInModel.IsDeveloper">
|
||||||
@{
|
@{
|
||||||
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(orderItemDto.GenericAttributes);
|
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(orderItemDto.GenericAttributes);
|
||||||
<GridGenericAttribute ParentDataItem="@orderItemDto" GenericAttributes="@genericAttributeDtos" />
|
<GridGenericAttribute ParentDataItem="@orderItemDto" GenericAttributes="@genericAttributeDtos" />
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
{
|
{
|
||||||
LoadingPanelVisibility.Visible = true;
|
LoadingPanelVisibility.Visible = true;
|
||||||
|
|
||||||
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
//using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,16 +51,7 @@
|
||||||
<GridDetailOrderItemDto OrderItemDtos="_currentOrderItemDtos" IsMasterGrid="false" />
|
<GridDetailOrderItemDto OrderItemDtos="_currentOrderItemDtos" IsMasterGrid="false" />
|
||||||
}
|
}
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Készlet mennyiség változások">
|
<DxTabPage Text="Speciális jellemzők" Visible="@LoggedInModel.IsDeveloper">
|
||||||
@{
|
|
||||||
//GetOrderItemDtosFromDbAsync(productId).Forget();
|
|
||||||
//var orderItemDtos = _orderItemDtos?.Where(oi => oi.ProductId == productId).ToList() ?? [];
|
|
||||||
|
|
||||||
var contextIds = new[] { (object)productDto.Id };
|
|
||||||
<GridStockQuantityHistoryDtoTemplate ContextIds="@(contextIds)" ParentDataItem="@productDto" />
|
|
||||||
}
|
|
||||||
</DxTabPage>
|
|
||||||
<DxTabPage Text="Speciális jellemzők">
|
|
||||||
@{
|
@{
|
||||||
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(productDto.GenericAttributes);
|
var genericAttributeDtos = new AcObservableCollection<GenericAttributeDto>(productDto.GenericAttributes);
|
||||||
<GridGenericAttribute ParentDataItem="@productDto" GenericAttributes="@genericAttributeDtos" />
|
<GridGenericAttribute ParentDataItem="@productDto" GenericAttributes="@genericAttributeDtos" />
|
||||||
|
|
@ -72,7 +63,7 @@
|
||||||
<ToolbarTemplate>
|
<ToolbarTemplate>
|
||||||
@if (IsMasterGrid)
|
@if (IsMasterGrid)
|
||||||
{
|
{
|
||||||
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)"/>
|
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
|
||||||
}
|
}
|
||||||
</ToolbarTemplate>
|
</ToolbarTemplate>
|
||||||
</GridProductDto>
|
</GridProductDto>
|
||||||
|
|
@ -124,7 +115,7 @@
|
||||||
if (!IsMasterGrid) return;
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
LoadingPanelVisibility.Visible = true;
|
LoadingPanelVisibility.Visible = true;
|
||||||
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
//using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<GridShippingItemBase @ref="Grid" ParentDataItem="ParentDataItem" DataSource="ShippingItems" AutoSaveLayoutName="GridShippingItem"
|
<GridShippingItemBase @ref="Grid" ParentDataItem="ParentDataItem" DataSource="ShippingItems" AutoSaveLayoutName="GridShippingItem"
|
||||||
SignalRClient="FruitBankSignalRClient" Logger="_logger"
|
SignalRClient="FruitBankSignalRClient" Logger="_logger"
|
||||||
CssClass="@GridCss" ValidationEnabled="false"
|
CssClass="@GridCss" ValidationEnabled="false" CustomizeElement="Grid_CustomizeElement"
|
||||||
FocusedRowChanged="Grid_FocusedRowChanged">
|
FocusedRowChanged="Grid_FocusedRowChanged">
|
||||||
<Columns>
|
<Columns>
|
||||||
<DxGridDataColumn FieldName="Id" Caption="oiId" Width="125" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Id" Caption="oiId" Width="125" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
||||||
|
|
@ -75,9 +75,9 @@
|
||||||
<DxGridDataColumn FieldName="GrossWeightOnDocument" Caption="Br.súly(OnDoc)" />
|
<DxGridDataColumn FieldName="GrossWeightOnDocument" Caption="Br.súly(OnDoc)" />
|
||||||
<DxGridDataColumn FieldName="MeasuringCount" Caption="Mérések száma" />
|
<DxGridDataColumn FieldName="MeasuringCount" Caption="Mérések száma" />
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="MeasuredQuantity" Caption="Mért mennyiség" ReadOnly="true" />
|
<DxGridDataColumn FieldName="MeasuredQuantity" Name="MeasuredQuantity" Caption="Mért mennyiség" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="MeasuredNetWeight" Caption="Mért net.súly(kg)" ReadOnly="true" />
|
<DxGridDataColumn FieldName="MeasuredNetWeight" Name="MeasuredNetWeight" Caption="Mért net.súly(kg)" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="MeasuredGrossWeight" Caption="Mért br.súly(kg)" ReadOnly="true" />
|
<DxGridDataColumn FieldName="MeasuredGrossWeight" Name="MeasuredGrossWeight" Caption="Mért br.súly(kg)" ReadOnly="true" />
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="IsMeasurable" ReadOnly="true" />
|
<DxGridDataColumn FieldName="IsMeasurable" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="IsMeasured" ReadOnly="true" />
|
<DxGridDataColumn FieldName="IsMeasured" ReadOnly="true" />
|
||||||
|
|
@ -159,7 +159,7 @@
|
||||||
|
|
||||||
public async Task ReloadDataFromDb(bool forceReload = false)
|
public async Task ReloadDataFromDb(bool forceReload = false)
|
||||||
{
|
{
|
||||||
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
//using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
||||||
}
|
}
|
||||||
|
|
@ -190,6 +190,55 @@
|
||||||
if (forceReload) Grid.Reload();
|
if (forceReload) Grid.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Grid_CustomizeElement(GridCustomizeElementEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ElementType != GridElementType.DataCell) return;
|
||||||
|
|
||||||
|
if (e.Column.Name != nameof(ShippingItem.MeasuredNetWeight) &&
|
||||||
|
e.Column.Name != nameof(ShippingItem.MeasuredGrossWeight) &&
|
||||||
|
e.Column.Name != nameof(ShippingItem.MeasuredQuantity)) return;
|
||||||
|
|
||||||
|
var isMeasured = (bool)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.IsMeasured));
|
||||||
|
if (!isMeasured) return;
|
||||||
|
|
||||||
|
switch (e.Column.Name)
|
||||||
|
{
|
||||||
|
case nameof(ShippingItem.MeasuredNetWeight) or nameof(ShippingItem.MeasuredGrossWeight):
|
||||||
|
{
|
||||||
|
var isMeasurable = (bool)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.IsMeasurable));
|
||||||
|
if (!isMeasurable) return;
|
||||||
|
|
||||||
|
var valueOnDocument = 0d;
|
||||||
|
var measuredValue = 0d;
|
||||||
|
|
||||||
|
if (e.Column.Name == nameof(ShippingItem.MeasuredGrossWeight))
|
||||||
|
{
|
||||||
|
valueOnDocument = (double)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.GrossWeightOnDocument));
|
||||||
|
measuredValue = (double)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.MeasuredGrossWeight));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valueOnDocument = (double)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.NetWeightOnDocument));
|
||||||
|
measuredValue = (double)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.MeasuredNetWeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueOnDocument > 0 && valueOnDocument > measuredValue) e.CssClass = "text-danger";
|
||||||
|
//else if (valueOnDocument <= measuredValue) e.CssClass = "text-success";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case nameof(ShippingItem.MeasuredQuantity):
|
||||||
|
{
|
||||||
|
var quantityOnDocument = (int)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.QuantityOnDocument));
|
||||||
|
var measuredQuantity = (int)e.Grid.GetRowValue(e.VisibleIndex, nameof(ShippingItem.MeasuredQuantity));
|
||||||
|
|
||||||
|
if (quantityOnDocument > 0 && quantityOnDocument > measuredQuantity) e.CssClass = "text-danger";
|
||||||
|
//else if (quantityOnDocument <= measuredQuantity) e.CssClass = "text-success";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task Grid_FocusedRowChanged(GridFocusedRowChangedEventArgs args)
|
async Task Grid_FocusedRowChanged(GridFocusedRowChangedEventArgs args)
|
||||||
{
|
{
|
||||||
if ((args.Grid.IsEditing() || args.Grid.IsEditingNewRow()) && (args.DataItem as IId<int>).Id > 0)
|
if ((args.Grid.IsEditing() || args.Grid.IsEditingNewRow()) && (args.DataItem as IId<int>).Id > 0)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,18 @@
|
||||||
<Columns>
|
<Columns>
|
||||||
<DxGridDataColumn FieldName="Id" Width="125" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
<DxGridDataColumn FieldName="Id" Width="125" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
||||||
|
|
||||||
<DxGridDataColumn FieldName="ProductId" Width="125" ReadOnly="true" Visible="false" />
|
<DxGridDataColumn FieldName="ProductId" ReadOnly="true" Visible="@IsMasterGrid">
|
||||||
|
<EditSettings>
|
||||||
|
<DxComboBoxSettings Data="ProductDtos"
|
||||||
|
ValueFieldName="Id"
|
||||||
|
TextFieldName="Name"
|
||||||
|
DropDownBodyCssClass="dd-body-class"
|
||||||
|
ListRenderMode="ListRenderMode.Entire"
|
||||||
|
SearchMode="ListSearchMode.AutoSearch"
|
||||||
|
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
||||||
|
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto" />
|
||||||
|
</EditSettings>
|
||||||
|
</DxGridDataColumn>
|
||||||
<DxGridDataColumn FieldName="QuantityAdjustment" Width="135" Caption="Adj. Quantity" ReadOnly="true" />
|
<DxGridDataColumn FieldName="QuantityAdjustment" Width="135" Caption="Adj. Quantity" ReadOnly="true" />
|
||||||
<DxGridDataColumn FieldName="StockQuantity" Width="135" ReadOnly="true" />
|
<DxGridDataColumn FieldName="StockQuantity" Width="135" ReadOnly="true" />
|
||||||
|
|
||||||
|
|
@ -77,7 +88,7 @@
|
||||||
{
|
{
|
||||||
if (!IsMasterGrid) return;
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
//using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,11 @@ public class GridShippingItemBase : FruitBankGridBase<ShippingItem>, IGrid
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//protected override Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||||
|
//{
|
||||||
|
// return base.OnCustomizeEditModel(e);
|
||||||
|
//}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
@using System.Collections.ObjectModel
|
||||||
|
@using AyCode.Core.Helpers
|
||||||
|
@using AyCode.Core.Loggers
|
||||||
|
@using AyCode.Utils.Extensions
|
||||||
|
@using FruitBank.Common.Dtos
|
||||||
|
@using FruitBank.Common.Entities
|
||||||
|
@using FruitBankHybrid.Shared.Components.Grids.Shippings
|
||||||
|
@using FruitBankHybrid.Shared.Components.Toolbars
|
||||||
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
@using FruitBankHybrid.Shared.Services.Loggers
|
||||||
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
|
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
||||||
|
@inject FruitBankSignalRClient FruitBankSignalRClient
|
||||||
|
|
||||||
|
<GridStockTakingItemBase @ref="Grid" AutoSaveLayoutName="GridStockTakingItem" SignalRClient="FruitBankSignalRClient" Logger="_logger"
|
||||||
|
CssClass="@GridCss" ValidationEnabled="false" FocusedRowChanged="Grid_FocusedRowChanged">
|
||||||
|
<Columns>
|
||||||
|
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.StockTakingId)" TextAlignment="GridTextAlignment.Left" Caption="Leltár időpontja">
|
||||||
|
<CellDisplayTemplate>
|
||||||
|
<span>@(((StockTakingItem)context.DataItem)?.StockTaking?.StartDateTime.ToString("g") ?? "")</span>
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
<DxGridDataColumn FieldName="Product.Name" />
|
||||||
|
<DxGridDataColumn FieldName="OriginalStockQuantity" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.InProcessOrdersQuantity)" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.TotalOriginalQuantity)" />
|
||||||
|
<DxGridDataColumn FieldName="MeasuredStockQuantity" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="OriginalNetWeight" />
|
||||||
|
<DxGridDataColumn FieldName="MeasuredNetWeight" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.QuantityDiff)" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.NetWeightDiff)" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.IsMeasurable)" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.IsMeasured)" />
|
||||||
|
<DxGridDataColumn FieldName="@nameof(StockTakingItem.IsInvalid)" />
|
||||||
|
|
||||||
|
<DxGridDataColumn FieldName="Created" ReadOnly="true" Visible="false" DisplayFormat="g" />
|
||||||
|
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="g" />
|
||||||
|
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
|
||||||
|
</Columns>
|
||||||
|
<ToolbarTemplate>
|
||||||
|
@if (IsMasterGrid)
|
||||||
|
{
|
||||||
|
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
|
||||||
|
}
|
||||||
|
</ToolbarTemplate>
|
||||||
|
</GridStockTakingItemBase>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
//[Inject] public required ObjectLock ObjectLock { get; set; }
|
||||||
|
[Inject] public required DatabaseClient Database { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public bool IsMasterGrid { get; set; } = false;
|
||||||
|
[Parameter] public AcObservableCollection<Partner>? Partners { get; set; }
|
||||||
|
[Parameter] public AcObservableCollection<Shipping>? Shippings { get; set; }
|
||||||
|
|
||||||
|
const string ExportFileName = "ExportResult";
|
||||||
|
string GridSearchText = "";
|
||||||
|
bool EditItemsEnabled { get; set; }
|
||||||
|
int FocusedRowVisibleIndex { get; set; }
|
||||||
|
public GridStockTakingItemBase Grid { get; set; }
|
||||||
|
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
|
||||||
|
|
||||||
|
private int _activeTabIndex;
|
||||||
|
private LoggerClient<GridStockTakingItem> _logger;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_logger = new LoggerClient<GridStockTakingItem>(LogWriters.ToArray());
|
||||||
|
await ReloadDataFromDb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadDataFromDb(bool forceReload = false)
|
||||||
|
{
|
||||||
|
if (!IsMasterGrid) return;
|
||||||
|
|
||||||
|
if (Grid == null) return;
|
||||||
|
|
||||||
|
//using (await ObjectLock.GetSemaphore<StockTakingItem>().UseWaitAsync())
|
||||||
|
//if (forceReload) await Grid.ReloadDataSourceAsync();
|
||||||
|
|
||||||
|
if (forceReload) Grid.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Grid_FocusedRowChanged(GridFocusedRowChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (Grid == null) return;
|
||||||
|
|
||||||
|
if (Grid.IsEditing() && !Grid.IsEditingNewRow())
|
||||||
|
await Grid.SaveChangesAsync();
|
||||||
|
|
||||||
|
FocusedRowVisibleIndex = args.VisibleIndex;
|
||||||
|
EditItemsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void Grid_CustomGroup(GridCustomGroupEventArgs e)
|
||||||
|
// {
|
||||||
|
// if (e.FieldName != "StockTaking.StartDateTime") return;
|
||||||
|
|
||||||
|
// e.SameGroup = ((StockTakingItem)e.DataItem1).StockTakingId == ((StockTakingItem)e.DataItem2).StockTakingId;
|
||||||
|
// e.Handled = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void Grid_CustomizeGroupValueDisplayText(GridCustomizeGroupValueDisplayTextEventArgs e)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// if (e.FieldName != "StockTaking.StartDateTime") return;
|
||||||
|
|
||||||
|
// var startDate = (DateTime)e.Value;
|
||||||
|
// e.DisplayText = startDate.ToString("g");
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
using AyCode.Core.Interfaces;
|
||||||
|
using DevExpress.Blazor;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBank.Common.Interfaces;
|
||||||
|
using FruitBank.Common.SignalRs;
|
||||||
|
using FruitBankHybrid.Shared.Pages;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Components.Grids.StockTakingItems;
|
||||||
|
|
||||||
|
public class GridStockTakingItemBase: FruitBankGridBase<StockTakingItem>, IGrid
|
||||||
|
{
|
||||||
|
private bool _isFirstInitializeParameterCore;
|
||||||
|
private bool _isFirstInitializeParameters;
|
||||||
|
|
||||||
|
public GridStockTakingItemBase() : base()
|
||||||
|
{
|
||||||
|
GetAllMessageTag = SignalRTags.GetStockTakingItems;
|
||||||
|
//AddMessageTag = SignalRTags.AddPartner;
|
||||||
|
//UpdateMessageTag = SignalRTags.UpdatePartner;
|
||||||
|
|
||||||
|
//RemoveMessageTag = SignalRTags.;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
base.OnParametersSet();
|
||||||
|
|
||||||
|
if (!_isFirstInitializeParameters)
|
||||||
|
{
|
||||||
|
//if (!IsMasterGrid && (ContextIds == null || ContextIds.Length == 0))
|
||||||
|
//{
|
||||||
|
// ContextIds = [ParentDataItem!.Id];
|
||||||
|
// GetAllMessageTag = SignalRTags.GetShippingItemsByDocumentId;
|
||||||
|
//}
|
||||||
|
|
||||||
|
_isFirstInitializeParameters = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
||||||
|
{
|
||||||
|
await base.SetParametersAsyncCore(parameters);
|
||||||
|
|
||||||
|
if (!_isFirstInitializeParameterCore)
|
||||||
|
{
|
||||||
|
//if (!IsMasterGrid && (ContextIds == null || ContextIds.Length == 0))
|
||||||
|
//{
|
||||||
|
// ContextIds = [ParentDataItem!.Id];
|
||||||
|
// GetAllMessageTag = SignalRTags.GetShippingItemsByDocumentId;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//ShowFilterRow = true;
|
||||||
|
//ShowGroupPanel = true;
|
||||||
|
//AllowSort = false;
|
||||||
|
|
||||||
|
//etc...
|
||||||
|
|
||||||
|
_isFirstInitializeParameterCore = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
@using FruitBankHybrid.Shared.Extensions
|
@using FruitBankHybrid.Shared.Extensions
|
||||||
@using FruitBankHybrid.Shared.Services
|
@using FruitBankHybrid.Shared.Services
|
||||||
@using FruitBankHybrid.Shared.Services.SignalRs
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
@typeparam TPalletItem where TPalletItem : class, IMeasuringItemPalletBase
|
@typeparam TPalletItem where TPalletItem : class, IMeasuringItemPalletBase
|
||||||
|
|
||||||
<DxFormLayout Context="ctxFromLayoutPallet" Data="@PalletItem" CaptionPosition="CaptionPosition.Vertical" CssClass="w-100 measuring-form-layout"
|
<DxFormLayout Context="ctxFromLayoutPallet" Data="@PalletItem" CaptionPosition="CaptionPosition.Vertical" CssClass="w-100 measuring-form-layout"
|
||||||
|
|
@ -181,6 +182,8 @@
|
||||||
|
|
||||||
StateHasChanged(); //Az Audit button miatt kell a StateHasChanged(), most már van RevisorId és emiatt disabled lesz...
|
StateHasChanged(); //Az Audit button miatt kell a StateHasChanged(), most már van RevisorId és emiatt disabled lesz...
|
||||||
|
|
||||||
|
PalletItem.SetParentPropToNull();
|
||||||
|
|
||||||
var responseShippingItemPallet = await FruitBankSignalRClient.PostDataAsync(AddOrUpdateSignalRTag!.Value, PalletItem);
|
var responseShippingItemPallet = await FruitBankSignalRClient.PostDataAsync(AddOrUpdateSignalRTag!.Value, PalletItem);
|
||||||
if (responseShippingItemPallet == null)
|
if (responseShippingItemPallet == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
@using AyCode.Core.Extensions
|
||||||
|
@using AyCode.Utils.Extensions
|
||||||
|
@using DevExpress.Blazor
|
||||||
|
@using FruitBank.Common.Dtos
|
||||||
|
@using FruitBank.Common.Entities
|
||||||
|
@using FruitBank.Common.Helpers
|
||||||
|
@using FruitBank.Common.Models
|
||||||
|
@using FruitBank.Common.SignalRs
|
||||||
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
@using FruitBankHybrid.Shared.Extensions
|
||||||
|
@using FruitBankHybrid.Shared.Services
|
||||||
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
@using Mango.Nop.Core.Entities
|
||||||
|
|
||||||
|
<DxFormLayout CaptionPosition="CaptionPosition.Vertical" CssClass="w-100">
|
||||||
|
<DxFormLayoutItem Caption="Termék:" ColSpanMd="2">
|
||||||
|
@* CaptionCssClass="@(SelectedProductDto?.IsMeasured == true ? "text-success" : "")"> *@
|
||||||
|
<DxComboBox Data="@_stockTakings"
|
||||||
|
TextFieldName="@nameof(StockTaking.StartDateTime)"
|
||||||
|
CssClass="cw-480"
|
||||||
|
DropDownBodyCssClass="dd-body-class"
|
||||||
|
Context="ctxProduct"
|
||||||
|
InputId="cbProduct"
|
||||||
|
Value="@SelectedStockTaking"
|
||||||
|
ValueChanged="@(async (StockTaking stockTaking) => await StockTakingComboValueChanged(stockTaking))">
|
||||||
|
</DxComboBox>
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="4">
|
||||||
|
<DxComboBox Data="@_stockTakingItems"
|
||||||
|
@bind-Value="@SelectedStockTakingItem"
|
||||||
|
TextFieldName="@nameof(StockTakingItem.DisplayText)"
|
||||||
|
CssClass="cw-480"
|
||||||
|
DropDownBodyCssClass="dd-body-class"
|
||||||
|
Context="ctxProduct2"
|
||||||
|
InputId="cbProduct2">
|
||||||
|
</DxComboBox>
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
@* TextFieldName="StockTakingItem.Product.Name" *@
|
||||||
|
<DxFormLayoutItem ColSpanMd="1">
|
||||||
|
<DxButton Text="Új" Enabled="@(_stockTakings.All(x => x.IsClosed))" Click="() => NewStockTakingClick()"></DxButton>
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="1">
|
||||||
|
<DxButton Text="Módosít" Enabled="@(SelectedStockTaking?.IsClosed ?? false)" Click="() => UpdateStockTakingClick()"></DxButton>
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="1">
|
||||||
|
<DxButton Text="Lezárás" Enabled="@(SelectedStockTaking?.IsReadyForClose() ?? false)" Click="() => StockTakingCloseClick()"></DxButton>
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
|
||||||
|
</DxFormLayout>
|
||||||
|
|
||||||
|
<div style="margin-top: 50px;">
|
||||||
|
@if (SelectedStockTakingItem is { ProductId: > 0 })
|
||||||
|
{
|
||||||
|
<h3 style="margin-bottom: 30px;" class="@(SelectedStockTakingItem.IsMeasured && SelectedStockTakingItem.StockTakingItemPallets!.All(x => x.IsMeasuredAndValid(SelectedStockTakingItem.IsMeasurable)) ? "text-success" : "")">
|
||||||
|
#@(SelectedStockTakingItem.ProductId). @(SelectedStockTakingItem.Product!.Name)
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
@{
|
||||||
|
var a = $"Várható rekesz: {SelectedStockTakingItem.TotalOriginalQuantity} ({SelectedStockTakingItem.OriginalStockQuantity} + {SelectedStockTakingItem.InProcessOrdersQuantity}), Várható net.súly: {SelectedStockTakingItem.OriginalNetWeight} kg.";
|
||||||
|
<span>@a</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<DxFormLayout Data="@SelectedStockTakingItem" CaptionPosition="CaptionPosition.Vertical" CssClass="w-100">
|
||||||
|
<DxFormLayoutItem Context="ctxShippingItemFromLayoutItem" ColSpanMd="12">
|
||||||
|
@for (var index = 0; index < (SelectedStockTakingItem?.StockTakingItemPallets?.Count ?? 0); index++)
|
||||||
|
{
|
||||||
|
var localI = index + 1;
|
||||||
|
var currentShippingItemPallet = SelectedStockTakingItem!.StockTakingItemPallets![index];
|
||||||
|
|
||||||
|
<PalletItemComponent IsMeasurable="@SelectedStockTakingItem!.IsMeasurable"
|
||||||
|
MeasuringIndex="@localI"
|
||||||
|
PalletItem="@currentShippingItemPallet"
|
||||||
|
ProductId="@SelectedStockTakingItem.Product!.Id"
|
||||||
|
AddOrUpdateSignalRTag="SignalRTags.AddOrUpdateMeasuredStockTakingItemPallet"
|
||||||
|
OnPalletItemSaved="pallet => OnStockTakingItemPalletSaved(pallet)"
|
||||||
|
OnPalletItemValueChanged="pallet => OnStockTakingItemPalletValueChanged(pallet, SelectedStockTakingItem)">
|
||||||
|
</PalletItemComponent>
|
||||||
|
}
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
|
||||||
|
@* <DxFormLayoutItem Context="vfdfgfd" ColSpanMd="12" BeginRow="true">
|
||||||
|
<DxFormLayout CssClass="w-100">
|
||||||
|
<DxFormLayoutItem ColSpanMd="1" BeginRow="false"><strong>TOTAL:</strong></DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="2" BeginRow="false" Visible="@(SelectedStockTakingItem.IsMeasurable)" />
|
||||||
|
<DxFormLayoutItem ColSpanMd="2" BeginRow="false" Visible="@(SelectedStockTakingItem.IsMeasurable)" />
|
||||||
|
<DxFormLayoutItem ColSpanMd="2" BeginRow="false"><strong>Rekesz: @(SelectedStockTakingItem.MeasuredStockQuantity) db</strong></DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="2" BeginRow="false" Visible="@(SelectedStockTakingItem.IsMeasurable)"><strong>Br: @(SelectedStockTakingItem.MeasuredGrossWeight) kg</strong></DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="1" BeginRow="false" Visible="@(SelectedStockTakingItem.IsMeasurable)"><strong>Net: @(SelectedStockTakingItem.MeasuredNetWeight) kg</strong></DxFormLayoutItem>
|
||||||
|
<DxFormLayoutItem ColSpanMd="1" BeginRow="false" />
|
||||||
|
</DxFormLayout>
|
||||||
|
</DxFormLayoutItem>
|
||||||
|
*@
|
||||||
|
</DxFormLayout>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Inject] public required DatabaseClient Database { get; set; }
|
||||||
|
[Inject] public required LoggedInModel LoggedInModel { get; set; }
|
||||||
|
[Inject] public required IDialogService DialogService { get; set; } = null!;
|
||||||
|
[Inject] public required FruitBankSignalRClient FruitBankSignalRClient { get; set; }
|
||||||
|
|
||||||
|
List<StockTaking> _stockTakings { get; set; } = [];
|
||||||
|
List<StockTakingItem> _stockTakingItems { get; set; } = [];
|
||||||
|
List<StockTakingItemPallet> _stockTakingItemPallets { get; set; } = [];
|
||||||
|
StockTaking? SelectedStockTaking { get; set; }
|
||||||
|
StockTakingItem? SelectedStockTakingItem { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await ReloadDataFromDb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReloadDataFromDb(bool forceReload)
|
||||||
|
{
|
||||||
|
LoadingPanelVisibility.Visible = true;
|
||||||
|
|
||||||
|
_stockTakings = await FruitBankSignalRClient.GetStockTakings(false) ?? [];
|
||||||
|
await StockTakingComboValueChanged(_stockTakings.FirstOrDefault());
|
||||||
|
|
||||||
|
LoadingPanelVisibility.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NewStockTakingClick()
|
||||||
|
{
|
||||||
|
var stockTaking = new StockTaking();
|
||||||
|
stockTaking.StartDateTime = DateTime.Now;
|
||||||
|
stockTaking.Creator = LoggedInModel.CustomerDto!.Id;
|
||||||
|
|
||||||
|
var resultStockTakings = await FruitBankSignalRClient.AddStockTaking(stockTaking);
|
||||||
|
if (resultStockTakings == null) return;
|
||||||
|
|
||||||
|
_stockTakings.UpdateCollection(resultStockTakings, false);
|
||||||
|
await StockTakingComboValueChanged(_stockTakings.FirstOrDefault(x => x.Id == stockTaking.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateStockTakingClick()
|
||||||
|
{
|
||||||
|
// var resultStockTaking = await FruitBankSignalRClient.AddStockTaking(stockTaking);
|
||||||
|
// if (resultStockTaking == null) return;
|
||||||
|
|
||||||
|
// _stockTakings.Add(resultStockTaking);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StockTakingCloseClick()
|
||||||
|
{
|
||||||
|
// var resultStockTaking = await FruitBankSignalRClient.AddStockTaking(stockTaking);
|
||||||
|
// if (resultStockTaking == null) return;
|
||||||
|
|
||||||
|
// _stockTakings.Add(resultStockTaking);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StockTakingComboValueChanged(StockTaking? newValue)
|
||||||
|
{
|
||||||
|
SelectedStockTaking = newValue;
|
||||||
|
SelectedStockTaking?.StockTakingItems = await FruitBankSignalRClient.GetStockTakingItemsByStockTakingId(SelectedStockTaking.Id);
|
||||||
|
|
||||||
|
PrepareStockTakingItems(SelectedStockTaking);
|
||||||
|
|
||||||
|
SelectedStockTakingItem = _stockTakingItems.FirstOrDefault();
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareStockTakingItems(StockTaking? stockTaking)
|
||||||
|
{
|
||||||
|
_stockTakingItems = stockTaking?.StockTakingItems?
|
||||||
|
.OrderByDescending(x => x.IsInvalid)
|
||||||
|
.ThenByDescending(x => x.IsRequiredForMeasuring)
|
||||||
|
.ThenBy(x => x.Product?.Name)
|
||||||
|
.ToList() ?? [];
|
||||||
|
|
||||||
|
foreach (var stockTakingItem in _stockTakingItems)
|
||||||
|
{
|
||||||
|
stockTakingItem.StockTakingItemPallets ??= [];
|
||||||
|
stockTakingItem.StockTaking = stockTaking;
|
||||||
|
|
||||||
|
if (!stockTakingItem.IsInvalid && stockTakingItem.StockTakingItemPallets.Count == 0)
|
||||||
|
{
|
||||||
|
stockTakingItem.StockTakingItemPallets.Add(MeasurementService.CreateNewStockTakingItemPallet(stockTakingItem, LoggedInModel.CustomerDto));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var stockTakingItemPallet in stockTakingItem.StockTakingItemPallets)
|
||||||
|
stockTakingItemPallet.StockTakingItem = stockTakingItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task OnStockTakingItemPalletValueChanged(StockTakingItemPallet stockTakingItemPallet, StockTakingItem stockTakingItem)
|
||||||
|
{
|
||||||
|
// MeasuringValuesHelper.SetShippingItemTotalMeasuringValues(stockTakingItem);
|
||||||
|
// BtnSaveEnabled = stockTakingItem.IsValidMeasuringValues() && stockTakingItemPallet.IsValidMeasuringValues(stockTakingItem.IsMeasurable);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnStockTakingItemPalletSaved(StockTakingItemPallet? responseStockTakingItemPallet)
|
||||||
|
{
|
||||||
|
if (responseStockTakingItemPallet != null)
|
||||||
|
{
|
||||||
|
responseStockTakingItemPallet.StockTakingItem = SelectedStockTakingItem;
|
||||||
|
SelectedStockTakingItem!.MeasuredStockQuantity = responseStockTakingItemPallet.TrayQuantity;
|
||||||
|
|
||||||
|
if (SelectedStockTakingItem.IsMeasurable) SelectedStockTakingItem.MeasuredNetWeight = responseStockTakingItemPallet.NetWeight;
|
||||||
|
|
||||||
|
SelectedStockTakingItem.StockTakingItemPallets!.UpdateCollection(responseStockTakingItemPallet, false);
|
||||||
|
SelectedStockTakingItem.IsMeasured = SelectedStockTakingItem.StockTakingItemPallets!.All(sip => sip.IsMeasuredAndValid(SelectedStockTakingItem.IsMeasurable));
|
||||||
|
|
||||||
|
// MeasuringValuesHelper.SetShippingItemTotalMeasuringValues(SelectedShippingItem);
|
||||||
|
}
|
||||||
|
else await DialogService.ShowMessageBoxAsync("Hiba", "Adatok mentése sikertelen volt, ellenőrizze a mérés adatait!", MessageBoxRenderStyle.Danger);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@using AyCode.Core.Loggers;
|
@using AyCode.Blazor.Components.Components.Grids
|
||||||
|
@using AyCode.Core.Loggers;
|
||||||
@using AyCode.Core.Extensions
|
@using AyCode.Core.Extensions
|
||||||
@using AyCode.Core.Helpers
|
@using AyCode.Core.Helpers
|
||||||
@using AyCode.Utils.Extensions
|
@using AyCode.Utils.Extensions
|
||||||
|
|
@ -10,6 +11,8 @@
|
||||||
@using FruitBankHybrid.Shared.Services.Loggers;
|
@using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
@using FruitBankHybrid.Shared.Services.SignalRs
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
||||||
@inject FruitBankSignalRClient FruitBankSignalRClient
|
@inject FruitBankSignalRClient FruitBankSignalRClient
|
||||||
@inject LoggedInModel LoggedInModel;
|
@inject LoggedInModel LoggedInModel;
|
||||||
|
|
@ -30,23 +33,6 @@
|
||||||
<DxToolbarItem Text="Reload data" BeginGroup="true" Click="ReloadData_Click" Enabled="@BtnReloadDataEnabled" />
|
<DxToolbarItem Text="Reload data" BeginGroup="true" Click="ReloadData_Click" Enabled="@BtnReloadDataEnabled" />
|
||||||
<DxToolbarItem BeginGroup="true">
|
<DxToolbarItem BeginGroup="true">
|
||||||
</DxToolbarItem>
|
</DxToolbarItem>
|
||||||
@* <DxToolbarItem BeginGroup="true">
|
|
||||||
<Template Context="toolbar_item_context">
|
|
||||||
<DxSearchBox @bind-Text="GridSearchText"
|
|
||||||
BindValueMode="BindValueMode.OnInput"
|
|
||||||
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
|
|
||||||
aria-label="Search" />
|
|
||||||
</Template>
|
|
||||||
</DxToolbarItem>*@
|
|
||||||
|
|
||||||
@* @if (DxToolbarItems != null)
|
|
||||||
{
|
|
||||||
foreach (var toolBarItem in DxToolbarItems)
|
|
||||||
{
|
|
||||||
@toolBarItem
|
|
||||||
}
|
|
||||||
//@DxToolbarItem
|
|
||||||
} *@
|
|
||||||
|
|
||||||
@ToolbarItemsExtended
|
@ToolbarItemsExtended
|
||||||
|
|
||||||
|
|
@ -60,20 +46,67 @@
|
||||||
public ToolbarBase Toolbar { get; set; }
|
public ToolbarBase Toolbar { get; set; }
|
||||||
const string ExportFileName = "ExportResult";
|
const string ExportFileName = "ExportResult";
|
||||||
|
|
||||||
public bool BtnReloadDataEnabled = true;
|
private bool _isReloadInProgress;
|
||||||
|
private bool _isGridSyncing;
|
||||||
|
private IMgGridBase? _mgGrid;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reload button is enabled only when no sync operation is in progress
|
||||||
|
/// </summary>
|
||||||
|
public bool BtnReloadDataEnabled => !_isReloadInProgress && !_isGridSyncing;
|
||||||
|
|
||||||
public bool EditItemsEnabled { get; set; } = true;
|
public bool EditItemsEnabled { get; set; } = true;
|
||||||
private LoggerClient<GridShippingItemTemplate> _logger;
|
private LoggerClient<GridShippingItemTemplate> _logger;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_logger = new LoggerClient<GridShippingItemTemplate>(LogWriters.ToArray());
|
_logger = new LoggerClient<GridShippingItemTemplate>(LogWriters.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
// Subscribe to grid syncing state changes if Grid implements IMgGridBase
|
||||||
|
if (Grid is IMgGridBase mgGrid && !ReferenceEquals(_mgGrid, mgGrid))
|
||||||
|
{
|
||||||
|
// Unsubscribe from previous grid
|
||||||
|
if (_mgGrid != null)
|
||||||
|
{
|
||||||
|
_mgGrid.OnSyncingStateChanged -= OnGridSyncingStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mgGrid = mgGrid;
|
||||||
|
_mgGrid.OnSyncingStateChanged += OnGridSyncingStateChanged;
|
||||||
|
|
||||||
|
// Get initial syncing state
|
||||||
|
_isGridSyncing = _mgGrid.IsSyncing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridSyncingStateChanged(bool isSyncing)
|
||||||
|
{
|
||||||
|
_isGridSyncing = isSyncing;
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_mgGrid != null)
|
||||||
|
{
|
||||||
|
_mgGrid.OnSyncingStateChanged -= OnGridSyncingStateChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
|
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
|
||||||
{
|
{
|
||||||
BtnReloadDataEnabled = false;
|
_isReloadInProgress = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
await OnReloadDataClick.InvokeAsync();
|
await OnReloadDataClick.InvokeAsync();
|
||||||
BtnReloadDataEnabled = true;
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isReloadInProgress = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task NewItem_Click()
|
async Task NewItem_Click()
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,13 @@ public class ShippingItemTable : SignalRDataSourceList<ShippingItemTableItem>
|
||||||
|
|
||||||
public class ProductDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : AcObservableCollection<ProductDtoTableItem>
|
public class ProductDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : AcObservableCollection<ProductDtoTableItem>
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _semaphoreSlim = new(1);
|
//private readonly SemaphoreSlim _semaphoreSlim = new(1);
|
||||||
public async Task<ProductDtoTable> LoadDataAsync(bool onlyIfEmpty = true)
|
public async Task<ProductDtoTable> LoadDataAsync(bool onlyIfEmpty = true)
|
||||||
{
|
{
|
||||||
if (onlyIfEmpty && Count > 0) return this;
|
if (onlyIfEmpty && Count > 0) return this;
|
||||||
|
|
||||||
using (await _semaphoreSlim.UseWaitAsync())
|
//using (await _semaphoreSlim.UseWaitAsync())
|
||||||
|
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
//Előfordulhat, h egy másik szálban már megtörtént a refresh... - J.
|
//Előfordulhat, h egy másik szálban már megtörtént a refresh... - J.
|
||||||
if (onlyIfEmpty && Count > 0) return this;
|
if (onlyIfEmpty && Count > 0) return this;
|
||||||
|
|
@ -83,13 +84,14 @@ public class ProductDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : Ac
|
||||||
}
|
}
|
||||||
public class OrderDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : AcObservableCollection<OrderDtoTableItem>
|
public class OrderDtoTable(FruitBankSignalRClient fruitBankSignalRClient) : AcObservableCollection<OrderDtoTableItem>
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _semaphoreSlim = new(1);
|
//private readonly SemaphoreSlim _semaphoreSlim = new(1);
|
||||||
|
|
||||||
public async Task<OrderDtoTable> LoadDataAsync(bool onlyIfEmpty = true)
|
public async Task<OrderDtoTable> LoadDataAsync(bool onlyIfEmpty = true)
|
||||||
{
|
{
|
||||||
if (onlyIfEmpty && Count > 0) return this;
|
if (onlyIfEmpty && Count > 0) return this;
|
||||||
|
|
||||||
using (await _semaphoreSlim.UseWaitAsync())
|
//using (await _semaphoreSlim.UseWaitAsync())
|
||||||
|
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
if (Count > 0) return this;
|
if (Count > 0) return this;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|
||||||
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -14,9 +17,9 @@
|
||||||
<PackageReference Include="DevExpress.Blazor" Version="25.1.3" />
|
<PackageReference Include="DevExpress.Blazor" Version="25.1.3" />
|
||||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.11" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@using FruitBank.Common.Models
|
@using FruitBank.Common.Models
|
||||||
|
@using FruitBankHybrid.Shared.Pages
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
@ -29,7 +30,12 @@
|
||||||
ShowCloseButton="true">
|
ShowCloseButton="true">
|
||||||
</DxToastProvider>
|
</DxToastProvider>
|
||||||
<CascadingValue Value="RefreshMainLayoutEventCallback">
|
<CascadingValue Value="RefreshMainLayoutEventCallback">
|
||||||
|
@* @Body *@
|
||||||
|
@if (LoggedInModel.IsLoggedIn || IsOnLoginPage)
|
||||||
|
{
|
||||||
@Body
|
@Body
|
||||||
|
}
|
||||||
|
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ using FruitBankHybrid.Shared.Pages;
|
||||||
using FruitBankHybrid.Shared.Services.Loggers;
|
using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
using Mango.Nop.Core.Loggers;
|
using Mango.Nop.Core.Loggers;
|
||||||
using MessagePack.Resolvers;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Layout;
|
namespace FruitBankHybrid.Shared.Layout;
|
||||||
|
|
@ -29,6 +28,8 @@ public partial class MainLayout : LayoutComponentBase
|
||||||
private NavMenu _navMenu = null!;
|
private NavMenu _navMenu = null!;
|
||||||
private LoggerClient _logger = null!;
|
private LoggerClient _logger = null!;
|
||||||
|
|
||||||
|
private bool IsOnLoginPage => NavManager.Uri.Equals(NavManager.ToAbsoluteUri("/Login").ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Toast fields
|
// Toast fields
|
||||||
private DxToast orderNotificationToast;
|
private DxToast orderNotificationToast;
|
||||||
private string toastTitle = "Értesítő!";
|
private string toastTitle = "Értesítő!";
|
||||||
|
|
@ -39,14 +40,29 @@ public partial class MainLayout : LayoutComponentBase
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_logger = new LoggerClient<MainLayout>(LogWriters.ToArray());
|
_logger = new LoggerClient<MainLayout>(LogWriters.ToArray());
|
||||||
_logger.Info("OnInitializedAsync");
|
_logger.Info("OnInitialized");
|
||||||
var loginUri = NavManager.ToAbsoluteUri("/Login").ToString();
|
|
||||||
FruitBankSignalRClient.OnMessageReceived += SignalRClientOnMessageReceived;
|
|
||||||
|
|
||||||
if (!LoggedInModel.IsLoggedIn && NavManager.Uri != loginUri)
|
// Setup login delegates
|
||||||
|
LoggedInModel.LoginFunc = FruitBankSignalRClient.LoginMeasuringUser;
|
||||||
|
LoggedInModel.GetRolesFunc = FruitBankSignalRClient.GetCustomerRolesByCustomerId;
|
||||||
|
|
||||||
|
FruitBankSignalRClient.OnMessageReceived += SignalRClientOnMessageReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (!firstRender) return;
|
||||||
|
|
||||||
|
await LoggedInModel.TryAutoLoginAsync();
|
||||||
|
|
||||||
|
if (!LoggedInModel.IsLoggedIn && !IsOnLoginPage)
|
||||||
{
|
{
|
||||||
NavManager.NavigateTo("/Login");
|
NavManager.NavigateTo("/Login");
|
||||||
}
|
}
|
||||||
|
else if (LoggedInModel.IsLoggedIn)
|
||||||
|
{
|
||||||
|
StateHasChanged(); // Refresh UI after successful auto-login
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SignalRClientOnMessageReceived(int messageTag, string? jsonMessage)
|
private async Task SignalRClientOnMessageReceived(int messageTag, string? jsonMessage)
|
||||||
|
|
@ -85,9 +101,9 @@ public partial class MainLayout : LayoutComponentBase
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLogoutClick()
|
private async void OnLogoutClick()
|
||||||
{
|
{
|
||||||
LoggedInModel.LogOut();
|
await LoggedInModel.LogOutAsync();
|
||||||
RefreshMainLayout();
|
RefreshMainLayout();
|
||||||
NavManager.NavigateTo("/Login");
|
NavManager.NavigateTo("/Login");
|
||||||
}
|
}
|
||||||
|
|
@ -97,4 +113,9 @@ public partial class MainLayout : LayoutComponentBase
|
||||||
_navMenu.RefreshNavMenu();
|
_navMenu.RefreshNavMenu();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
FruitBankSignalRClient.OnMessageReceived -= SignalRClientOnMessageReceived;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +37,15 @@
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (LoggedInModel.IsDeveloper)
|
||||||
|
{
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="StockTaking">
|
||||||
|
<span class="icon counter-icon" aria-hidden="true"></span> Leltározás
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (LoggedInModel.IsAdministrator)
|
@if (LoggedInModel.IsAdministrator)
|
||||||
{
|
{
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
|
|
@ -44,9 +53,7 @@
|
||||||
<span class="icon counter-icon" aria-hidden="true"></span> Rendelések - Adminisztrátor
|
<span class="icon counter-icon" aria-hidden="true"></span> Rendelések - Adminisztrátor
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
@if (LoggedInModel.IsAdministrator)
|
|
||||||
{
|
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="ShippingsAdmin">
|
<NavLink class="nav-link" href="ShippingsAdmin">
|
||||||
<span class="icon counter-icon" aria-hidden="true"></span> Szállítmányok - Adminisztrátor
|
<span class="icon counter-icon" aria-hidden="true"></span> Szállítmányok - Adminisztrátor
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,4 @@ public partial class Home : ComponentBase
|
||||||
|
|
||||||
private string Factor => FormFactor.GetFormFactor();
|
private string Factor => FormFactor.GetFormFactor();
|
||||||
private string Platform => FormFactor.GetPlatform();
|
private string Platform => FormFactor.GetPlatform();
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
if (!LoggedInModel.IsLoggedIn) NavManager.NavigateTo("/Login");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -20,11 +20,10 @@ public partial class Login : ComponentBase
|
||||||
[Inject] public required NavigationManager NavManager{ get; set; }
|
[Inject] public required NavigationManager NavManager{ get; set; }
|
||||||
|
|
||||||
private ILogger _logger = null!;
|
private ILogger _logger = null!;
|
||||||
//private List<CustomerDto> Users { get; set; }
|
|
||||||
private CustomerDto? SelectedUser { get; set; }
|
private CustomerDto? SelectedUser { get; set; }
|
||||||
private string PasswordValue { get; set; } = string.Empty;
|
private string PasswordValue { get; set; } = string.Empty;
|
||||||
|
|
||||||
private MgLoginModelResponse? LoginModelResponse { get; set; }
|
private MgLoginModelResponse? LoginModelResponse { get; set; }
|
||||||
|
private string _rolesText = string.Empty;
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public EventCallback UpdateStyle { get; set; }
|
public EventCallback UpdateStyle { get; set; }
|
||||||
|
|
@ -35,6 +34,18 @@ public partial class Login : ComponentBase
|
||||||
_logger.Info("OnInitializedAsync");
|
_logger.Info("OnInitializedAsync");
|
||||||
|
|
||||||
if (!LoggedInModel.IsLoggedIn)
|
if (!LoggedInModel.IsLoggedIn)
|
||||||
|
{
|
||||||
|
await LoadMeasuringUsersAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rolesText = string.Join("; ", LoggedInModel.CustomerRoles.Select(x => x.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadMeasuringUsersAsync()
|
||||||
{
|
{
|
||||||
using (await ObjectLock.GetSemaphore<CustomerDto>().UseWaitAsync())
|
using (await ObjectLock.GetSemaphore<CustomerDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
|
|
@ -45,46 +56,42 @@ public partial class Login : ComponentBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else _rolesText = string.Join("; ", LoggedInModel.CustomerRoles.Select(x => x.Name));
|
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _rolesText = string.Empty;
|
|
||||||
private async Task OnLoginClick()
|
private async Task OnLoginClick()
|
||||||
{
|
{
|
||||||
if (LoggedInModel.IsLoggedIn) return;
|
if (LoggedInModel.IsLoggedIn) return;
|
||||||
|
|
||||||
_rolesText = string.Empty;
|
_rolesText = string.Empty;
|
||||||
|
|
||||||
|
if (!ValidateLoginInput()) return;
|
||||||
|
|
||||||
|
// Use the simplified LoginAsync from LoggedInModel
|
||||||
|
if(await LoggedInModel.LoginAsync(SelectedUser!.Email, PasswordValue))
|
||||||
|
{
|
||||||
|
_rolesText = string.Join("; ", LoggedInModel.CustomerRoles.Select(x => x.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateStyle.InvokeAsync();
|
||||||
|
|
||||||
|
if (LoggedInModel.IsLoggedIn)
|
||||||
|
{
|
||||||
|
NavManager.NavigateTo("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateLoginInput()
|
||||||
|
{
|
||||||
if (SelectedUser == null || PasswordValue.IsNullOrWhiteSpace())
|
if (SelectedUser == null || PasswordValue.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
LoginModelResponse = new MgLoginModelResponse
|
LoginModelResponse = new MgLoginModelResponse
|
||||||
{
|
{
|
||||||
ErrorMessage = "Válasszon felhsználót és adja meg a jelszavát!"
|
ErrorMessage = "Válasszon felhsználót és adja meg a jelszavát!"
|
||||||
};
|
};
|
||||||
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
LoginModelResponse = await FruitBankSignalRClient.LoginMeasuringUser(SelectedUser.Email, PasswordValue);
|
|
||||||
|
|
||||||
if (LoginModelResponse is { IsSuccesLogin: true })
|
|
||||||
{
|
|
||||||
LoggedInModel.InitLoggedInCustomer(LoginModelResponse.CustomerDto);
|
|
||||||
|
|
||||||
var customerRoles = await FruitBankSignalRClient.GetCustomerRolesByCustomerId(LoginModelResponse.CustomerDto!.Id);
|
|
||||||
if (customerRoles != null)
|
|
||||||
{
|
|
||||||
LoggedInModel.InitCustomerRoles(customerRoles);
|
|
||||||
|
|
||||||
_rolesText = string.Join("; ", LoggedInModel.CustomerRoles.Select(x => x.Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await UpdateStyle.InvokeAsync();
|
|
||||||
if (LoggedInModel.IsLoggedIn) NavManager.NavigateTo("/");
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task OnPasswordKeyDown(KeyboardEventArgs e)
|
protected async Task OnPasswordKeyDown(KeyboardEventArgs e)
|
||||||
|
|
@ -94,7 +101,6 @@ public partial class Login : ComponentBase
|
||||||
|
|
||||||
private string GetImageFileName(CustomerDto employee)
|
private string GetImageFileName(CustomerDto employee)
|
||||||
{
|
{
|
||||||
//return StaticAssetUtils.GetEmployeeImagePath(employee.Id);
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,8 +39,6 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if (!LoggedInModel.IsLoggedIn) NavManager.NavigateTo("/Login");
|
|
||||||
|
|
||||||
LoadingPanelVisible = true;
|
LoadingPanelVisible = true;
|
||||||
|
|
||||||
_logger = new LoggerClient<MeasuringIn>(LogWriters.ToArray());
|
_logger = new LoggerClient<MeasuringIn>(LogWriters.ToArray());
|
||||||
|
|
@ -156,6 +154,8 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
{
|
{
|
||||||
if (responseShippingItemPallet != null)
|
if (responseShippingItemPallet != null)
|
||||||
{
|
{
|
||||||
|
responseShippingItemPallet.ShippingItem = SelectedShippingItem;
|
||||||
|
|
||||||
SelectedShippingItem!.ShippingItemPallets!.UpdateCollection(responseShippingItemPallet, false);
|
SelectedShippingItem!.ShippingItemPallets!.UpdateCollection(responseShippingItemPallet, false);
|
||||||
SelectedShippingItem.IsMeasured = SelectedShippingItem!.ShippingItemPallets!.All(sip => sip.IsMeasuredAndValid(SelectedShippingItem.IsMeasurable));
|
SelectedShippingItem.IsMeasured = SelectedShippingItem!.ShippingItemPallets!.All(sip => sip.IsMeasuredAndValid(SelectedShippingItem.IsMeasurable));
|
||||||
|
|
||||||
|
|
@ -197,8 +197,14 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
|
|
||||||
shippingItem.ShippingItemPallets ??= new List<ShippingItemPallet>(shippingItem.MeasuringCount);
|
shippingItem.ShippingItemPallets ??= new List<ShippingItemPallet>(shippingItem.MeasuringCount);
|
||||||
|
|
||||||
for (var i = shippingItem.ShippingItemPallets.Count; i < shippingItem.MeasuringCount; i++)
|
for (var i = 0; i < shippingItem.MeasuringCount; i++)
|
||||||
{
|
{
|
||||||
|
if (i < shippingItem.ShippingItemPallets.Count)
|
||||||
|
{
|
||||||
|
shippingItem.ShippingItemPallets[i].ShippingItem = shippingItem;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
shippingItem.ShippingItemPallets.Add(MeasurementService.CreateNewShippingItemPallet(shippingItem, LoggedInModel.CustomerDto));
|
shippingItem.ShippingItemPallets.Add(MeasurementService.CreateNewShippingItemPallet(shippingItem, LoggedInModel.CustomerDto));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,6 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if (!LoggedInModel.IsLoggedIn) NavManager.NavigateTo("/Login");
|
|
||||||
|
|
||||||
LoadingPanelVisible = true;
|
LoadingPanelVisible = true;
|
||||||
_logger = new LoggerClient<MeasuringOut>(LogWriters.ToArray());
|
_logger = new LoggerClient<MeasuringOut>(LogWriters.ToArray());
|
||||||
_logger.Info("OnInitializedAsync");
|
_logger.Info("OnInitializedAsync");
|
||||||
|
|
@ -93,10 +91,14 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
|
|
||||||
if (SelectedDate != orderDto.DateOfReceipt.Value.Date) return;
|
if (SelectedDate != orderDto.DateOfReceipt.Value.Date) return;
|
||||||
|
|
||||||
var selectedOrderId = SelectedOrder?.Id;
|
//Elég lenne ez is, csak a CopyTo a Collection - ökben lévő elemeket hozzáfűzi és duplikálva lesznek... -J.
|
||||||
SelectedDayOrders.UpdateCollection(orderDto, false);
|
if (SelectedOrder?.Id == orderDto.Id) orderDto.CopyTo(SelectedOrder);
|
||||||
|
else SelectedDayOrders.UpdateCollection(orderDto, false);
|
||||||
|
|
||||||
if (selectedOrderId.GetValueOrDefault(-1) == orderDto.Id) SelectedOrder = orderDto;
|
//var selectedOrderId = SelectedOrder?.Id;
|
||||||
|
//SelectedDayOrders.UpdateCollection(orderDto, false);
|
||||||
|
|
||||||
|
//if (selectedOrderId.GetValueOrDefault(-1) == orderDto.Id) SelectedOrder = orderDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
@ -108,7 +110,11 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
var localOrderDto = SelectedDayOrders.FirstOrDefault(o => o.OrderItemDtos.Any(oi => oi.Id == orderItemDto.Id));
|
var localOrderDto = SelectedDayOrders.FirstOrDefault(o => o.OrderItemDtos.Any(oi => oi.Id == orderItemDto.Id));
|
||||||
var localOrderItemDto = localOrderDto?.OrderItemDtos.First(x => x.Id == orderItemDto.Id)!;
|
var localOrderItemDto = localOrderDto?.OrderItemDtos.FirstOrDefault(x => x.Id == orderItemDto.Id);
|
||||||
|
|
||||||
|
if (localOrderItemDto == null) return;
|
||||||
|
|
||||||
|
//orderItemDto.OrderDto = localOrderDto!;
|
||||||
|
|
||||||
localOrderItemDto.Quantity = orderItemDto.Quantity;
|
localOrderItemDto.Quantity = orderItemDto.Quantity;
|
||||||
localOrderItemDto.GenericAttributes.UpdateBaseEntityCollection(orderItemDto.GenericAttributes, false);
|
localOrderItemDto.GenericAttributes.UpdateBaseEntityCollection(orderItemDto.GenericAttributes, false);
|
||||||
|
|
@ -127,6 +133,7 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
orderItemDto = orderItemDtos?.FirstOrDefault(oi => oi.Id == orderItemPallet.OrderItemId);
|
orderItemDto = orderItemDtos?.FirstOrDefault(oi => oi.Id == orderItemPallet.OrderItemId);
|
||||||
if (orderItemDto == null) return;
|
if (orderItemDto == null) return;
|
||||||
|
|
||||||
|
orderItemPallet.OrderItemDto = orderItemDto;
|
||||||
var orderItemPalletsCount = orderItemDto.OrderItemPallets.Count;
|
var orderItemPalletsCount = orderItemDto.OrderItemPallets.Count;
|
||||||
|
|
||||||
if (orderItemDto.OrderItemPallets[orderItemPalletsCount - 1].Id == 0) orderItemDto.OrderItemPallets.Insert(orderItemPalletsCount - 1, orderItemPallet);
|
if (orderItemDto.OrderItemPallets[orderItemPalletsCount - 1].Id == 0) orderItemDto.OrderItemPallets.Insert(orderItemPalletsCount - 1, orderItemPallet);
|
||||||
|
|
@ -161,7 +168,7 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
|
|
||||||
SelectedDayOrders = orders.Where(order => MeasurementService.DaysEqual(order.DateOfReceiptOrCreated, dateTime)).OrderBy(x => x.DateOfReceipt).ToList();
|
SelectedDayOrders = orders.Where(order => MeasurementService.DaysEqual(order.DateOfReceiptOrCreated, dateTime)).OrderBy(x => x.DateOfReceipt).ToList();
|
||||||
|
|
||||||
foreach (var orderDto in SelectedDayOrders) PrepareOrderDto(orderDto);
|
foreach (var orderDto in SelectedDayOrders) PrepareOrderItemDtos(orderDto);
|
||||||
|
|
||||||
SelectedOrder = LoggedInModel.IsRevisor
|
SelectedOrder = LoggedInModel.IsRevisor
|
||||||
? SelectedDayOrders.FirstOrDefault(o => o is { IsComplete: false, IsMeasured: true })
|
? SelectedDayOrders.FirstOrDefault(o => o is { IsComplete: false, IsMeasured: true })
|
||||||
|
|
@ -233,16 +240,18 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OnPalletItemAuditedClick(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
|
private async Task OnPalletItemAuditedClick(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
|
||||||
{
|
{
|
||||||
StateHasChanged();
|
if (orderItemPallet == null) return;
|
||||||
return Task.CompletedTask;
|
await OnOrderItemPalletSaved(orderItemPallet, selectedOrderItemDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnOrderItemPalletSaved(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
|
private async Task OnOrderItemPalletSaved(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
|
||||||
{
|
{
|
||||||
if (orderItemPallet != null)
|
if (orderItemPallet != null)
|
||||||
{
|
{
|
||||||
|
orderItemPallet.OrderItemDto = selectedOrderItemDto;
|
||||||
|
|
||||||
selectedOrderItemDto.OrderItemPallets.UpdateCollection(orderItemPallet, false);
|
selectedOrderItemDto.OrderItemPallets.UpdateCollection(orderItemPallet, false);
|
||||||
//MeasuringValuesHelper.SetShippingItemTotalMeasuringValues(SelectedShippingItem);
|
//MeasuringValuesHelper.SetShippingItemTotalMeasuringValues(SelectedShippingItem);
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +300,7 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
var responseOrderDto = await FruitBankSignalRClient.StartMeasuring(SelectedOrder.Id, LoggedInModel.CustomerDto!.Id);
|
var responseOrderDto = await FruitBankSignalRClient.StartMeasuring(SelectedOrder.Id, LoggedInModel.CustomerDto!.Id);
|
||||||
if (responseOrderDto != null)
|
if (responseOrderDto != null)
|
||||||
{
|
{
|
||||||
PrepareOrderDto(responseOrderDto);
|
PrepareOrderItemDtos(responseOrderDto);
|
||||||
|
|
||||||
//SelectedOrder.GenericAttributes.UpdateBaseEntityCollection(responseOrderDto.GenericAttributes, false);
|
//SelectedOrder.GenericAttributes.UpdateBaseEntityCollection(responseOrderDto.GenericAttributes, false);
|
||||||
SelectedDayOrders.UpdateCollection(responseOrderDto, false);
|
SelectedDayOrders.UpdateCollection(responseOrderDto, false);
|
||||||
|
|
@ -311,7 +320,7 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
var responseOrderDto = await FruitBankSignalRClient.SetOrderStatusToComplete(SelectedOrder.Id, LoggedInModel.CustomerDto!.Id);
|
var responseOrderDto = await FruitBankSignalRClient.SetOrderStatusToComplete(SelectedOrder.Id, LoggedInModel.CustomerDto!.Id);
|
||||||
if (responseOrderDto != null)
|
if (responseOrderDto != null)
|
||||||
{
|
{
|
||||||
PrepareOrderDto(responseOrderDto);
|
PrepareOrderItemDtos(responseOrderDto);
|
||||||
|
|
||||||
SelectedDayOrders.UpdateCollection(responseOrderDto, false);
|
SelectedDayOrders.UpdateCollection(responseOrderDto, false);
|
||||||
SelectedOrder = responseOrderDto;
|
SelectedOrder = responseOrderDto;
|
||||||
|
|
@ -325,11 +334,18 @@ namespace FruitBankHybrid.Shared.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareOrderDto(OrderDto orderDto)
|
private void PrepareOrderItemDtos(OrderDto orderDto)
|
||||||
{
|
{
|
||||||
foreach (var orderItemDto in orderDto.OrderItemDtos.Where(orderItem => orderItem.OrderItemPallets.Count == 0))
|
foreach (var orderItemDto in orderDto.OrderItemDtos)
|
||||||
{
|
{
|
||||||
orderItemDto.OrderItemPallets.Add(MeasurementService.CreateNewOrderItemPallet(orderItemDto, LoggedInModel.CustomerDto));
|
orderItemDto.OrderDto = orderDto;
|
||||||
|
|
||||||
|
if (orderItemDto.OrderItemPallets.Count == 0) orderItemDto.OrderItemPallets.Add(MeasurementService.CreateNewOrderItemPallet(orderItemDto, LoggedInModel.CustomerDto));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var orderItemPallet in orderItemDto.OrderItemPallets)
|
||||||
|
orderItemPallet.OrderItemDto = orderItemDto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public partial class OrdersAdmin : ComponentBase
|
||||||
private async Task ReloadDataFromDb(bool forceReload = false)
|
private async Task ReloadDataFromDb(bool forceReload = false)
|
||||||
{
|
{
|
||||||
LoadingPanelVisibility.Visible = true;
|
LoadingPanelVisibility.Visible = true;
|
||||||
using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
//using (await ObjectLock.GetSemaphore<ProductDto>().UseWaitAsync())
|
||||||
{
|
{
|
||||||
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
if (ProductDtos == null || !ProductDtos.Any() || forceReload) ProductDtos = await Database.ProductDtoTable.LoadDataAsync(!forceReload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
@page "/StockTaking"
|
||||||
|
@using FruitBankHybrid.Shared.Components.Grids.Products
|
||||||
|
@using FruitBankHybrid.Shared.Components.Grids.StockTakingItems
|
||||||
|
@using FruitBankHybrid.Shared.Components.StockTakings
|
||||||
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
|
||||||
|
<h3>Leltározás</h3>
|
||||||
|
|
||||||
|
<DxDialogProvider />
|
||||||
|
|
||||||
|
<div style="margin-top: 30px;">
|
||||||
|
|
||||||
|
<DxLoadingPanel @bind-Visible="LoadingPanelVisibility.Visible"
|
||||||
|
IsContentBlocked="true"
|
||||||
|
ApplyBackgroundShading="true"
|
||||||
|
IndicatorAreaVisible="false"
|
||||||
|
Text="Adatok szinkronizálása folyamatban...">
|
||||||
|
|
||||||
|
<DxTabs RenderMode="TabsRenderMode.OnDemand">
|
||||||
|
<DxTabPage Text="Leltározás">
|
||||||
|
<StockTakingTemplate></StockTakingTemplate>
|
||||||
|
</DxTabPage>
|
||||||
|
<DxTabPage Text="Leltár előzmények">
|
||||||
|
@{
|
||||||
|
<GridStockTakingItem IsMasterGrid="true"></GridStockTakingItem>
|
||||||
|
}
|
||||||
|
</DxTabPage>
|
||||||
|
<DxTabPage Text="Készlet mennyiség változások">
|
||||||
|
@{
|
||||||
|
<GridStockQuantityHistoryDtoTemplate></GridStockQuantityHistoryDtoTemplate>
|
||||||
|
}
|
||||||
|
</DxTabPage>
|
||||||
|
</DxTabs>
|
||||||
|
|
||||||
|
</DxLoadingPanel>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Pages
|
||||||
|
{
|
||||||
|
public partial class StockTaking : ComponentBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file is kept for backward compatibility.
|
||||||
|
// The interface has been moved to FruitBank.Common.Services.ISecureCredentialService
|
||||||
|
global using FruitBank.Common.Services;
|
||||||
|
|
@ -65,7 +65,7 @@ public class MeasurementService(IEnumerable<IAcLogWriterClientBase> logWriters)
|
||||||
|
|
||||||
public static ShippingItemPallet CreateNewShippingItemPallet(ShippingItem shippingItem, CustomerDto? customerDto)
|
public static ShippingItemPallet CreateNewShippingItemPallet(ShippingItem shippingItem, CustomerDto? customerDto)
|
||||||
{
|
{
|
||||||
var shippingItemPallet = CreatePalletItem<ShippingItemPallet>(shippingItem.Id, shippingItem.ProductDto?.Tare, shippingItem.IsMeasurable, customerDto);
|
var shippingItemPallet = CreatePalletItemBase<ShippingItemPallet>(shippingItem.Id, shippingItem.ProductDto?.Tare, shippingItem.IsMeasurable, customerDto);
|
||||||
|
|
||||||
shippingItemPallet.ShippingItem = shippingItem;
|
shippingItemPallet.ShippingItem = shippingItem;
|
||||||
shippingItemPallet.PalletWeight = shippingItem.IsMeasurable ? shippingItem.Pallet?.Weight ?? 0 : 0;
|
shippingItemPallet.PalletWeight = shippingItem.IsMeasurable ? shippingItem.Pallet?.Weight ?? 0 : 0;
|
||||||
|
|
@ -75,13 +75,24 @@ public class MeasurementService(IEnumerable<IAcLogWriterClientBase> logWriters)
|
||||||
|
|
||||||
public static OrderItemPallet CreateNewOrderItemPallet(OrderItemDto orderItemDto, CustomerDto? customerDto)
|
public static OrderItemPallet CreateNewOrderItemPallet(OrderItemDto orderItemDto, CustomerDto? customerDto)
|
||||||
{
|
{
|
||||||
var orderItemPallet = CreatePalletItem<OrderItemPallet>(orderItemDto.Id, orderItemDto.ProductDto?.Tare, orderItemDto.IsMeasurable, customerDto);
|
var orderItemPallet = CreatePalletItemBase<OrderItemPallet>(orderItemDto.Id, orderItemDto.ProductDto?.Tare, orderItemDto.IsMeasurable, customerDto);
|
||||||
|
|
||||||
orderItemPallet.OrderItemDto = orderItemDto;
|
orderItemPallet.OrderItemDto = orderItemDto;
|
||||||
return orderItemPallet;
|
return orderItemPallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TPalletItem CreatePalletItem<TPalletItem>(int foreignKey, double? tare, bool isMeasurable, CustomerDto? customerDto) where TPalletItem : MeasuringItemPalletBase
|
public static StockTakingItemPallet CreateNewStockTakingItemPallet(StockTakingItem stockTakingItem, CustomerDto? customerDto)
|
||||||
|
{
|
||||||
|
var stockTakingItemPallet = CreatePalletItemBase<StockTakingItemPallet>(stockTakingItem.Id, stockTakingItem.Product?.Tare, stockTakingItem.IsMeasurable, customerDto);
|
||||||
|
|
||||||
|
stockTakingItemPallet.GrossWeight = stockTakingItem.IsMeasurable ? -1 : 0;
|
||||||
|
stockTakingItemPallet.TrayQuantity = -1;
|
||||||
|
|
||||||
|
stockTakingItemPallet.StockTakingItem = stockTakingItem;
|
||||||
|
return stockTakingItemPallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TPalletItem CreatePalletItemBase<TPalletItem>(int foreignKey, double? tare, bool isMeasurable, CustomerDto? customerDto) where TPalletItem : MeasuringItemPalletBase
|
||||||
{
|
{
|
||||||
var palletItem = Activator.CreateInstance<TPalletItem>();
|
var palletItem = Activator.CreateInstance<TPalletItem>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Nop.Core.Domain.Customers;
|
using Nop.Core.Domain.Customers;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ServiceModel.Channels;
|
using System.ServiceModel.Channels;
|
||||||
|
using Mango.Nop.Core.Entities;
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Services.SignalRs
|
namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
{
|
{
|
||||||
public class FruitBankSignalRClient : AcSignalRClientBase, IFruitBankDataControllerClient, ICustomOrderSignalREndpointClient
|
public class FruitBankSignalRClient : AcSignalRClientBase, IFruitBankDataControllerClient, ICustomOrderSignalREndpointClient, IStockSignalREndpointClient
|
||||||
{
|
{
|
||||||
public FruitBankSignalRClient( /*IServiceProvider serviceProvider, */ IEnumerable<IAcLogWriterClientBase> logWriters) : base($"{FruitBankConstClient.BaseUrl}/{FruitBankConstClient.DefaultHubName}", new LoggerClient(nameof(FruitBankSignalRClient), logWriters.ToArray()))
|
public FruitBankSignalRClient( /*IServiceProvider serviceProvider, */ IEnumerable<IAcLogWriterClientBase> logWriters) : base($"{FruitBankConstClient.BaseUrl}/{FruitBankConstClient.DefaultHubName}", new LoggerClient(nameof(FruitBankSignalRClient), logWriters.ToArray()))
|
||||||
{
|
{
|
||||||
|
|
@ -263,5 +264,73 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
=> GetAllAsync<List<StockQuantityHistoryDto>>(SignalRTags.GetStockQuantityHistoryDtosByProductId, [productId]);
|
=> GetAllAsync<List<StockQuantityHistoryDto>>(SignalRTags.GetStockQuantityHistoryDtosByProductId, [productId]);
|
||||||
|
|
||||||
#endregion Orders
|
#endregion Orders
|
||||||
|
|
||||||
|
public async Task<List<GenericAttributeDto>?> GetGenericAttributeDtosByEntityIdAndKeyGroup(int productId, string keyGroup, int storeId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GenericAttributeDto?> AddGenericAttributeDto(GenericAttributeDto genericAttributeDto)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GenericAttributeDto?> UpdateGenericAttributeDto(GenericAttributeDto genericAttributeDto)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task<List<StockTaking>?> GetStockTakings(bool loadRelations) => GetAllAsync<List<StockTaking>>(SignalRTags.GetStockTakings, [loadRelations]);
|
||||||
|
|
||||||
|
public async Task<List<StockTaking>?> GetStockTakingsByProductId(int productId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<StockTaking?> AddStockTaking(StockTaking stockTaking) => PostDataAsync(SignalRTags.AddStockTaking, stockTaking);
|
||||||
|
|
||||||
|
public Task<StockTaking?> UpdateStockTaking(StockTaking stockTaking) => PostDataAsync(SignalRTags.UpdateStockTaking, stockTaking);
|
||||||
|
|
||||||
|
public Task<List<StockTakingItem>?> GetStockTakingItems() => GetAllAsync<List<StockTakingItem>>(SignalRTags.GetStockTakingItems);
|
||||||
|
|
||||||
|
public Task<StockTakingItem?> GetStockTakingItemsById(int stockTakingItemId)
|
||||||
|
=> GetByIdAsync<StockTakingItem>(SignalRTags.GetStockTakingItemsById, [stockTakingItemId]);
|
||||||
|
|
||||||
|
public Task<List<StockTakingItem>?> GetStockTakingItemsByProductId(int productId)
|
||||||
|
=> GetAllAsync<List<StockTakingItem>>(SignalRTags.GetStockTakingItemsByProductId, [productId]);
|
||||||
|
|
||||||
|
public Task<List<StockTakingItem>?> GetStockTakingItemsByStockTakingId(int stockTakingId)
|
||||||
|
=> GetAllAsync<List<StockTakingItem>>(SignalRTags.GetStockTakingItemsByStockTakingId, [stockTakingId]);
|
||||||
|
|
||||||
|
public async Task<StockTakingItem?> AddStockTakingItem(StockTakingItem stockTakingItem)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockTakingItem?> UpdateStockTakingItem(StockTakingItem stockTakingItem)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockTakingItemPallet>?> GetStockTakingItemPallets()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockTakingItemPallet>?> GetStockTakingItemPalletsByProductId(int productId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockTakingItemPallet?> AddStockTakingItemPallet(StockTakingItemPallet stockTakingItemPallet)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockTakingItemPallet?> UpdateStockTakingItemPallet(StockTakingItemPallet stockTakingItemPallet)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,10 @@
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
|
|
||||||
|
@using FruitBankHybrid
|
||||||
|
@using FruitBankHybrid.Shared
|
||||||
|
@using FruitBankHybrid.Shared.Layout
|
||||||
|
@using FruitBankHybrid.Shared.Components
|
||||||
|
@using FruitBankHybrid.Shared.Components.Grids.Products
|
||||||
|
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
|
|
@ -1,19 +1,25 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
|
|
||||||
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
|
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DevExpress.Blazor" Version="25.1.*" />
|
<PackageReference Include="DevExpress.Blazor" Version="25.1.3" />
|
||||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
|
||||||
|
<StaticWebAssetFingerprintPattern Include="JS" Pattern="*.js" Expression="#[.{fingerprint}]!" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using FruitBank.Common.Loggers;
|
using FruitBank.Common.Loggers;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
|
using FruitBank.Common.Services;
|
||||||
using FruitBankHybrid.Shared.Databases;
|
using FruitBankHybrid.Shared.Databases;
|
||||||
using FruitBankHybrid.Shared.Services;
|
using FruitBankHybrid.Shared.Services;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
|
|
@ -15,12 +16,14 @@ builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpres
|
||||||
|
|
||||||
// Add device-specific services used by the FruitBankHybrid.Shared project
|
// Add device-specific services used by the FruitBankHybrid.Shared project
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
|
builder.Services.AddSingleton<ISecureCredentialService, WebSecureCredentialService>();
|
||||||
|
|
||||||
//#if DEBUG
|
//#if DEBUG
|
||||||
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
builder.Services.AddSingleton<LoggedInModel>();
|
builder.Services.AddSingleton<LoggedInModel>(sp =>
|
||||||
|
new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||||
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
||||||
builder.Services.AddSingleton<DatabaseClient>();
|
builder.Services.AddSingleton<DatabaseClient>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using FruitBank.Common.Services;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Web.Client.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WebAssembly implementation of ISecureCredentialService using obfuscated localStorage.
|
||||||
|
/// Note: WebAssembly has limited cryptography support, so we use Base64 + XOR obfuscation.
|
||||||
|
/// This prevents casual inspection but is not cryptographically secure.
|
||||||
|
/// For true security, consider server-side token storage or authentication cookies.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WebSecureCredentialService : ISecureCredentialService
|
||||||
|
{
|
||||||
|
private const string CredentialsKey = "FruitBank_UserCredentials";
|
||||||
|
private static readonly TimeSpan ExpirationDuration = TimeSpan.FromDays(2);
|
||||||
|
private static readonly byte[] ObfuscationKey = "FruitBank_Secure_v1_2025"u8.ToArray();
|
||||||
|
|
||||||
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
|
public WebSecureCredentialService(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveCredentialsAsync(string email, string password)
|
||||||
|
{
|
||||||
|
var data = new SecureCredentialData
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
Password = password,
|
||||||
|
ExpiresAtUtc = DateTime.UtcNow.Add(ExpirationDuration)
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(data);
|
||||||
|
var obfuscated = Obfuscate(json);
|
||||||
|
|
||||||
|
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", CredentialsKey, obfuscated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StoredCredentials?> GetCredentialsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var obfuscated = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", CredentialsKey);
|
||||||
|
if (string.IsNullOrEmpty(obfuscated))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var json = Deobfuscate(obfuscated);
|
||||||
|
if (string.IsNullOrEmpty(json))
|
||||||
|
{
|
||||||
|
await ClearCredentialsAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = JsonSerializer.Deserialize<SecureCredentialData>(json);
|
||||||
|
if (data == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Check expiration
|
||||||
|
if (DateTime.UtcNow > data.ExpiresAtUtc)
|
||||||
|
{
|
||||||
|
await ClearCredentialsAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StoredCredentials(data.Email, data.Password);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If any error occurs (corrupted data, etc.), clear and return null
|
||||||
|
await ClearCredentialsAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearCredentialsAsync()
|
||||||
|
{
|
||||||
|
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", CredentialsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Obfuscate(string plainText)
|
||||||
|
{
|
||||||
|
var plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||||
|
var obfuscatedBytes = new byte[plainBytes.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < plainBytes.Length; i++)
|
||||||
|
{
|
||||||
|
obfuscatedBytes[i] = (byte)(plainBytes[i] ^ ObfuscationKey[i % ObfuscationKey.Length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToBase64String(obfuscatedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? Deobfuscate(string obfuscated)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var obfuscatedBytes = Convert.FromBase64String(obfuscated);
|
||||||
|
var plainBytes = new byte[obfuscatedBytes.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < obfuscatedBytes.Length; i++)
|
||||||
|
{
|
||||||
|
plainBytes[i] = (byte)(obfuscatedBytes[i] ^ ObfuscationKey[i % ObfuscationKey.Length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(plainBytes);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SecureCredentialData
|
||||||
|
{
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public DateTime ExpiresAtUtc { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
|
|
||||||
@using FruitBankHybrid.Shared
|
@using FruitBankHybrid.Shared
|
||||||
@using FruitBankHybrid.Web.Client
|
@using FruitBankHybrid.Web.Client
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,23 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
|
@* <ResourcePreloader /> *@
|
||||||
@DxResourceManager.RegisterScripts()
|
@DxResourceManager.RegisterScripts()
|
||||||
@DxResourceManager.RegisterTheme(Themes.Fluent)
|
@DxResourceManager.RegisterTheme(Themes.Fluent)
|
||||||
|
|
||||||
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/bootstrap/bootstrap.min.css") rel="stylesheet" />
|
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/bootstrap/bootstrap.min.css") rel="stylesheet" />
|
||||||
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/app.css") rel="stylesheet" />
|
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/app.css") rel="stylesheet" />
|
||||||
<link href=@AppendVersion("FruitBankHybrid.Web.styles.css") rel="stylesheet" />
|
<link href=@AppendVersion("FruitBankHybrid.Web.styles.css") rel="stylesheet" />
|
||||||
|
|
||||||
|
<ImportMap />
|
||||||
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/favicon.png") rel="icon" type="image/png" />
|
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/favicon.png") rel="icon" type="image/png" />
|
||||||
<HeadOutlet @rendermode="InteractiveWebAssembly" />
|
<HeadOutlet @rendermode="InteractiveWebAssembly" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="dxbl-theme-fluent">
|
<body class="dxbl-theme-fluent">
|
||||||
<Routes @rendermode="InteractiveWebAssembly" />
|
<Routes @rendermode="InteractiveWebAssembly" />
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
|
|
||||||
@using FruitBankHybrid.Shared
|
@using FruitBankHybrid.Shared
|
||||||
|
@using FruitBankHybrid.Shared.Layout
|
||||||
@using FruitBankHybrid.Web
|
@using FruitBankHybrid.Web
|
||||||
@using FruitBankHybrid.Web.Client
|
@using FruitBankHybrid.Web.Client
|
||||||
@using FruitBankHybrid.Web.Components
|
@using FruitBankHybrid.Web.Components
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<!--<PublishTrimmed>true</PublishTrimmed>-->
|
<!--<PublishTrimmed>true</PublishTrimmed>-->
|
||||||
|
|
||||||
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
|
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -17,9 +21,9 @@
|
||||||
<PackageReference Include="DevExpress.Blazor" Version="25.1.3" />
|
<PackageReference Include="DevExpress.Blazor" Version="25.1.3" />
|
||||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
<PackageReference Include="MessagePack.Annotations" Version="3.1.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.11" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using FruitBank.Common;
|
using FruitBank.Common;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
|
using FruitBank.Common.Services;
|
||||||
using FruitBank.Common.Server.Services.Loggers;
|
using FruitBank.Common.Server.Services.Loggers;
|
||||||
using FruitBank.Common.Server.Services.SignalRs;
|
using FruitBank.Common.Server.Services.SignalRs;
|
||||||
using FruitBankHybrid.Shared.Databases;
|
using FruitBankHybrid.Shared.Databases;
|
||||||
|
|
@ -11,15 +12,17 @@ using FruitBankHybrid.Web.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddInteractiveWebAssemblyComponents();
|
builder.Services.AddRazorComponents().AddInteractiveWebAssemblyComponents();
|
||||||
builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpress.Blazor.SizeMode.Medium);
|
builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpress.Blazor.SizeMode.Medium);
|
||||||
builder.Services.AddMvc();
|
builder.Services.AddMvc();
|
||||||
|
|
||||||
builder.Services.AddSignalR(options => options.MaximumReceiveMessageSize = 256 * 1024);
|
builder.Services.AddSignalR(options => options.MaximumReceiveMessageSize = 256 * 1024);
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
|
builder.Services.AddSingleton<ISecureCredentialService, ServerSecureCredentialService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IAcLogWriterBase, ConsoleLogWriter>();
|
builder.Services.AddSingleton<IAcLogWriterBase, ConsoleLogWriter>();
|
||||||
builder.Services.AddSingleton<LoggedInModel>();
|
builder.Services.AddSingleton<LoggedInModel>(sp =>
|
||||||
|
new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||||
|
|
||||||
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
||||||
builder.Services.AddSingleton<DatabaseClient>();
|
builder.Services.AddSingleton<DatabaseClient>();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
using FruitBank.Common.Services;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Web.Services;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side implementation of ISecureCredentialService.
|
||||||
|
/// This is a no-op implementation used during prerendering - actual credential storage
|
||||||
|
/// is handled by the client-side WebSecureCredentialService after WebAssembly loads.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ServerSecureCredentialService : ISecureCredentialService
|
||||||
|
{
|
||||||
|
public Task SaveCredentialsAsync(string email, string password)
|
||||||
|
{
|
||||||
|
// No-op on server side - credentials are stored client-side
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<StoredCredentials?> GetCredentialsAsync()
|
||||||
|
{
|
||||||
|
// Always return null on server side - auto-login happens client-side after WASM loads
|
||||||
|
return Task.FromResult<StoredCredentials?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ClearCredentialsAsync()
|
||||||
|
{
|
||||||
|
// No-op on server side
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 17.14.36414.22
|
VisualStudioVersion = 18.0.11222.15 d18.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruitBankHybrid", "FruitBankHybrid\FruitBankHybrid.csproj", "{85ADEDE3-C271-47DF-B273-2EDB32792CEF}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruitBankHybrid", "FruitBankHybrid\FruitBankHybrid.csproj", "{85ADEDE3-C271-47DF-B273-2EDB32792CEF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net9.0-android;net9.0-ios</TargetFrameworks>
|
<TargetFrameworks>net10.0-android;net10.0-ios</TargetFrameworks>
|
||||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.26100.0</TargetFrameworks>
|
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.26100.0</TargetFrameworks>
|
||||||
|
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>FruitBankHybrid</RootNamespace>
|
<RootNamespace>FruitBankHybrid</RootNamespace>
|
||||||
|
|
@ -22,14 +22,18 @@
|
||||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>1</ApplicationVersion>
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
|
||||||
|
<RunAOTCompilation>true</RunAOTCompilation>
|
||||||
|
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
|
||||||
|
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||||
|
|
||||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||||
<!--<WindowsPackageType>None</WindowsPackageType>-->
|
<!--<WindowsPackageType>None</WindowsPackageType>-->
|
||||||
|
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">29.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">33.0</SupportedOSPlatformVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.19041.0</SupportedOSPlatformVersion>
|
||||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.19041.0</TargetPlatformMinVersion>
|
||||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||||
<AppxPackageSigningEnabled>False</AppxPackageSigningEnabled>
|
<AppxPackageSigningEnabled>False</AppxPackageSigningEnabled>
|
||||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||||
|
|
@ -53,14 +57,14 @@
|
||||||
<UseInterpreter>true</UseInterpreter>
|
<UseInterpreter>true</UseInterpreter>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-android|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net10.0-android|AnyCPU'">
|
||||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||||
<AndroidUseAapt2>True</AndroidUseAapt2>
|
<AndroidUseAapt2>True</AndroidUseAapt2>
|
||||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-android|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net10.0-android|AnyCPU'">
|
||||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||||
<AndroidUseAapt2>True</AndroidUseAapt2>
|
<AndroidUseAapt2>True</AndroidUseAapt2>
|
||||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||||
|
|
@ -89,9 +93,10 @@
|
||||||
<!--<PackageReference Include="DevExpress.Maui.Controls" Version="25.1.3" />
|
<!--<PackageReference Include="DevExpress.Maui.Controls" Version="25.1.3" />
|
||||||
<PackageReference Include="DevExpress.Maui.Editors" Version="25.1.3" />
|
<PackageReference Include="DevExpress.Maui.Editors" Version="25.1.3" />
|
||||||
<PackageReference Include="DevExpress.Maui.CollectionView" Version="25.1.3" />-->
|
<PackageReference Include="DevExpress.Maui.CollectionView" Version="25.1.3" />-->
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.120" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="10.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.120" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.11" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using FruitBank.Common.Loggers;
|
using FruitBank.Common.Loggers;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
|
using FruitBank.Common.Services;
|
||||||
using FruitBankHybrid.Services;
|
using FruitBankHybrid.Services;
|
||||||
using FruitBankHybrid.Services.Loggers;
|
using FruitBankHybrid.Services.Loggers;
|
||||||
using FruitBankHybrid.Shared.Databases;
|
using FruitBankHybrid.Shared.Databases;
|
||||||
|
|
@ -28,12 +29,14 @@ namespace FruitBankHybrid
|
||||||
|
|
||||||
// Add device-specific services used by the FruitBankHybrid.Shared project
|
// Add device-specific services used by the FruitBankHybrid.Shared project
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
|
builder.Services.AddSingleton<ISecureCredentialService, MauiSecureCredentialService>();
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
builder.Services.AddSingleton<LoggedInModel>();
|
builder.Services.AddSingleton<LoggedInModel>(sp =>
|
||||||
|
new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||||
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
builder.Services.AddSingleton<FruitBankSignalRClient>();
|
||||||
builder.Services.AddSingleton<DatabaseClient>();
|
builder.Services.AddSingleton<DatabaseClient>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using FruitBank.Common.Services;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MAUI implementation of ISecureCredentialService using SecureStorage.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MauiSecureCredentialService : ISecureCredentialService
|
||||||
|
{
|
||||||
|
private const string CredentialsKey = "FruitBank_UserCredentials";
|
||||||
|
private static readonly TimeSpan ExpirationDuration = TimeSpan.FromDays(2);
|
||||||
|
|
||||||
|
public async Task SaveCredentialsAsync(string email, string password)
|
||||||
|
{
|
||||||
|
var data = new SecureCredentialData
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
Password = password,
|
||||||
|
ExpiresAtUtc = DateTime.UtcNow.Add(ExpirationDuration)
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(data);
|
||||||
|
await SecureStorage.Default.SetAsync(CredentialsKey, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StoredCredentials?> GetCredentialsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = await SecureStorage.Default.GetAsync(CredentialsKey);
|
||||||
|
if (string.IsNullOrEmpty(json))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var data = JsonSerializer.Deserialize<SecureCredentialData>(json);
|
||||||
|
if (data == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Check expiration
|
||||||
|
if (DateTime.UtcNow > data.ExpiresAtUtc)
|
||||||
|
{
|
||||||
|
await ClearCredentialsAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StoredCredentials(data.Email, data.Password);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If any error occurs (corrupted data, etc.), clear and return null
|
||||||
|
await ClearCredentialsAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ClearCredentialsAsync()
|
||||||
|
{
|
||||||
|
SecureStorage.Default.Remove(CredentialsKey);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SecureCredentialData
|
||||||
|
{
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public DateTime ExpiresAtUtc { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue