Compare commits
354 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c775146b64 | |||
| 6e4bebb89b | |||
| 932d09c135 | |||
| 455ce699ad | |||
| 7d7e9f1318 | |||
| 87e3112a9a | |||
| 801276559d | |||
| b1bda1448e | |||
| 58247a1a0b | |||
| 3a503abe6a | |||
| 855eb44118 | |||
| 33dc4adce2 | |||
| 5b2b39b7bf | |||
| e13ec0f109 | |||
| 2c39b34c5f | |||
| 99832df134 | |||
| 2be9d9b396 | |||
| a12e170205 | |||
| fdfca7cf5e | |||
| e84ce29064 | |||
| 2be2928ad1 | |||
| 872fd42c4a | |||
| 39ad424d21 | |||
| d79c5e55d0 | |||
| 50d3113c28 | |||
| 34d33571de | |||
| 0f575370d4 | |||
| e4061fa94c | |||
| 5f8bc70174 | |||
| f2d5c9f50a | |||
| 9e39981c9a | |||
| bd8b2d8b44 | |||
| f26557a66e | |||
| 91959913a8 | |||
| 48f607d623 | |||
| 82be251878 | |||
| 209ee7da2d | |||
| 6b9f27573b | |||
| a0df2a1bbf | |||
| d1c3d4e22f | |||
| a80512994b | |||
| 2de9e5e4ad | |||
| 5707bd4c31 | |||
| 28c465f59b | |||
| 21b38b5276 | |||
| 992cd292fa | |||
| 7b6c9ac92a | |||
| eb0b042f0c | |||
| a980e636f5 | |||
| b992d07689 | |||
| a4d58f3377 | |||
| 94db2a311c | |||
| 6b608ad2ef | |||
| 87c807c16c | |||
| 3c75f9786e | |||
| 2d6723a040 | |||
| 2fe2ba007c | |||
| f3f2b71bd4 | |||
| bc72426495 | |||
| da29492798 | |||
| ce21aa6588 | |||
| f44cbf711b | |||
| aaac42b481 | |||
| 7a21cde217 | |||
| 531b3a7fef | |||
| a81e32bbe2 | |||
| 4ef63420b7 | |||
| 9fdf5aa8e3 | |||
| d9c339949e | |||
| 0052725b88 | |||
| 5d18aba1f9 | |||
| 5828879ddc | |||
| d75a43d45f | |||
| 728f2a8b17 | |||
| 1b66d5d767 | |||
| c5a0992b5d | |||
| 09d86835a2 | |||
| d1d2d55352 | |||
| c626f56cdb | |||
| 263e2ea9c0 | |||
| 5c37f02f19 | |||
| 6b3f28d3ae | |||
| 558afc6f9d | |||
| 5b2ac8cc14 | |||
| 5cb32bee95 | |||
| 9adfff48a7 | |||
| e5f6aa15b9 | |||
| 4e12b03e4b | |||
| f5967497be | |||
| 74f33d8979 | |||
| dd8f50f54c | |||
| 25b21d6fa8 | |||
| 106ed57897 | |||
| 0451a0f418 | |||
| b12f3c8d08 | |||
| 702efba62e | |||
| 55aa35308a | |||
| 40e890d4ff | |||
| 9908a552d1 | |||
| 4110e2fcfc | |||
| 23fb7361a4 | |||
| ff6f3d3218 | |||
| 92a311e1cc | |||
| 3538bb5b0e | |||
| b75cd01e90 | |||
| c52ecfdebc | |||
| edb5fddc3a | |||
| 05d44ab9f0 | |||
| c358764f4e | |||
| 92f1aadb81 | |||
| a8725bc473 | |||
| d578cbad20 | |||
| e1db9b1b69 | |||
| 9707ef596c | |||
| bfd3e5cded | |||
| d28d996295 | |||
| 4e493f51f8 | |||
| c52eaabec4 | |||
| 63c1c4ad1a | |||
| e3af6100ec | |||
| df0eb3cd5a | |||
| d8ba956817 | |||
| 89c0c17be7 | |||
| cc72e22664 | |||
| ef4e0e0873 | |||
| bd90a3d68e | |||
| 7dac429a1a | |||
| 3dbfe6db23 | |||
| a6de58e2e9 | |||
| 681cf821a6 | |||
| 37f4a88014 | |||
| a4f5d12234 | |||
| ee50f0932d | |||
| 7dd012f926 | |||
| aebfa6e3cc | |||
| 2d365cd906 | |||
| d6fd685b3c | |||
| a892aec527 | |||
| c68c6e1ab9 | |||
| aaf6e5f12d | |||
| 75b0a802c3 | |||
| e8692b2474 | |||
| be38777ebb | |||
| 41f992b824 | |||
| 1ec5426d15 | |||
| 3b0a5f0d3f | |||
| 96e12972b9 | |||
| ddc3ecd3e6 | |||
| 556dedb8a3 | |||
| 80db899091 | |||
| c290a1e778 | |||
| d7b6f29200 | |||
| 6f72f7a0e9 | |||
| ee672da9f7 | |||
| 4b95e03e05 | |||
| ae302d0c9c | |||
| 53dcdf929a | |||
| d2277f626c | |||
| 35fa3b19e3 | |||
| b7db6a83e6 | |||
| 8bb3b3d192 | |||
| e5dadef863 | |||
| 44e335d542 | |||
| d20e4a532f | |||
| 52845b5d87 | |||
| 3149902574 | |||
| 3c17047550 | |||
| 34ffc7f97a | |||
| 37fb72376a | |||
| b3840b47ce | |||
| a9a201cce1 | |||
| 52a9a3fde7 | |||
| 3c2dee5b4a | |||
| 1b310c86b3 | |||
| 1579936baf | |||
| e398ee9d6b | |||
| a8b6503a8a | |||
| a3c0211654 | |||
| dc32ae7ea6 | |||
| e4e12aa05a | |||
| 6a31ad0190 | |||
| 3103fb66ef | |||
| 502bf25cc0 | |||
| 484aa3fece | |||
| 1c9ca179df | |||
| c8c04c357d | |||
| b63f264551 | |||
| 7ced279ad5 | |||
| 4d7b8d8a56 | |||
| 1f6d63a0b3 | |||
| 63e7ddba82 | |||
| e132e29c82 | |||
| e2cec66d6b | |||
| 03d01d4c4e | |||
| 93f2f1bd3b | |||
| 93355d34f1 | |||
| 6c40421b2d | |||
| 9151f47d15 | |||
| 661b522b7d | |||
| 541f59b15d | |||
| b68e5043c7 | |||
| e0bb17d0ff | |||
| 436f7c10a0 | |||
| 0f29413375 | |||
| 8739e8e1a7 | |||
| 308be2f5ce | |||
| eccc80eaad | |||
| ef17f7da0a | |||
| ca2507a904 | |||
| fb62448e2c | |||
| 307055f8e1 | |||
| 2000ba67f4 | |||
| b8836a1e87 | |||
| 6c6d30f4f9 | |||
| 9bb4c52efb | |||
| 617c0713a1 | |||
| eb0303fa3f | |||
| f9e4bb3a97 | |||
| 7dbeaa722a | |||
| 58d67495d2 | |||
| e53db290c8 | |||
| 2a5562a355 | |||
| 5a6eca1f07 | |||
| 6ba8d2fd19 | |||
| e267f94d46 | |||
| 81d92e16bb | |||
| ddbe56c5df | |||
| 5f303321d0 | |||
| 19ff0b5578 | |||
| bbf9e5b60e | |||
| 508149dab8 | |||
| eea87c0ee2 | |||
| 3bcb42ea9b | |||
| 2053b61965 | |||
| 7e362be304 | |||
| f5872cedaf | |||
| cff4f4bf30 | |||
| aee3e840a0 | |||
| ef0486829c | |||
| 7e9110b707 | |||
| 68aea8105d | |||
| 04004437bb | |||
| 712a163777 | |||
| dfd51b8273 | |||
| 51aaed8fd1 | |||
| 62bfe27a84 | |||
| 0523dabc5a | |||
| 7d4d07d692 | |||
| 1bdda3a8ab | |||
| 1645eae694 | |||
| d0bf5581c5 | |||
| 8864f8ad6d | |||
| c962ed9425 | |||
| ad74074771 | |||
| 899d04e4eb | |||
| 5e48873422 | |||
| ab2226e394 | |||
| d7546d24d1 | |||
| be66e65e59 | |||
| 63115f18b4 | |||
| 1357f7e3f8 | |||
| 831f33df34 | |||
| 8f5ad872e5 | |||
| 38b893b9f6 | |||
| 5f82090272 | |||
| 5890c03ffe | |||
| 6a8204b584 | |||
| 32f9b079b3 | |||
| b71cfae6c9 | |||
| 35fe512bd1 | |||
| de3dcb8c52 | |||
| 692fc4f290 | |||
| 85a16396aa | |||
| d9031f8e73 | |||
| 12ff9e8fef | |||
| 7cf272e311 | |||
| 82a32de966 | |||
| 75578ea7f6 | |||
| 68b34b3fc3 | |||
| 4f02fa303d | |||
| 83167b5eb3 | |||
| e67675a12a | |||
| 2ecda623ac | |||
| da9caa855a | |||
| 5c4e1b590a | |||
| 75b5035354 | |||
| c0432a448f | |||
| bfbfe352ee | |||
| 320da0e1c9 | |||
| f0d7f939ef | |||
| 8b84c8aeb3 | |||
| 9febae6d63 | |||
| df27d58089 | |||
| 4e12e9f8f1 | |||
| 39eab2e3a2 | |||
| 889ca0795d | |||
| a7801a2bc2 | |||
| f4db83654c | |||
| d414ec8e38 | |||
| dc7e7a70fc | |||
| 20b82a2734 | |||
| 83d3272784 | |||
| d6cd9ccd08 | |||
| 1bee5f91c6 | |||
| 08ad17f15b | |||
| 97a1db1343 | |||
| 3268a50c3e | |||
| f1c036d1d8 | |||
| 92fef1dfa8 | |||
| bb33928661 | |||
| b279a121ad | |||
| 916a2b8f0a | |||
| 3caacf44d0 | |||
| fe9ccb4de7 | |||
| f244a04dc0 | |||
| aa543d0d0d | |||
| 87146e4662 | |||
| cefefcbc49 | |||
| fcca03d0b2 | |||
| 6d8b6b3e59 | |||
| 3837facb02 | |||
| a833586e83 | |||
| 3bc33ce08f | |||
| 870b412a06 | |||
| 095d35e1b8 | |||
| d7b03941fb | |||
| 81a49e1a98 | |||
| 2995ce7012 | |||
| db0850f1f0 | |||
| 702dbeb39f | |||
| c8d23afe59 | |||
| ade41b4a60 | |||
| ac1b2fbc4b | |||
| f381e7a634 | |||
| 3680fc9035 | |||
| 6a0322570b | |||
| dc4b1934e1 | |||
| aabace1bba | |||
| f7101800cd | |||
| f6211a8edd | |||
| c9f5006727 | |||
| 942a39b0eb | |||
| b2681d1236 | |||
| 63bc6e9cd8 | |||
| 83bf715716 | |||
| 7a2494c156 | |||
| e3e331a2bb | |||
| 4e73dd7fb1 | |||
| e88668beca | |||
| c1961ebea2 | |||
| cf9d2c2661 | |||
| 429a80b1ed | |||
| f95fda3806 | |||
| d2390e7b12 |
+1
Submodule .claude/worktrees/agent-a203fc082069b1faf added at ade41b4a60
@@ -0,0 +1,89 @@
|
||||
name: CI/CD - ERP Distribution
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- production
|
||||
types:
|
||||
- closed
|
||||
jobs:
|
||||
build-and-deploy-backend:
|
||||
name: Build & Deploy Backend
|
||||
runs-on: linux
|
||||
if: github.event.pull_request.merged == true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone \
|
||||
--depth 1 \
|
||||
--branch production \
|
||||
https://oauth2:${{ secrets.DISTRIBUTION }}@gitea.hexdive.com/New_REP_SYSTEM/ERP_Distribution.git \
|
||||
.
|
||||
- name: Restore
|
||||
run: dotnet restore Distribution/Distribution.csproj
|
||||
- name: Publish
|
||||
run: |
|
||||
rm -rf /tmp/erpdistribution-deploy
|
||||
mkdir -p /tmp/erpdistribution-deploy
|
||||
dotnet publish Distribution/Distribution.csproj \
|
||||
-c Release \
|
||||
-r linux-x64 \
|
||||
--self-contained true \
|
||||
-o /tmp/erpdistribution-deploy
|
||||
- name: Stop service
|
||||
run: systemctl stop erpdistribution.service
|
||||
- name: Copy to deploy folder
|
||||
run: |
|
||||
mkdir -p /home/distribution/core/publish
|
||||
rm -rf /home/distribution/core/publish/*
|
||||
cp -r /tmp/erpdistribution-deploy/* /home/distribution/core/publish/
|
||||
- name: Fix ownership and permissions
|
||||
run: |
|
||||
chown -R distributionuser:distributionuser /home/distribution/core/publish
|
||||
find /home/distribution/core/publish -type d -exec chmod 755 {} +
|
||||
find /home/distribution/core/publish -type f -exec chmod 644 {} +
|
||||
chmod +x /home/distribution/core/publish/Distribution
|
||||
- name: Restart service
|
||||
run: systemctl restart erpdistribution.service
|
||||
- name: Verify
|
||||
run: |
|
||||
systemctl is-active erpdistribution.service && echo "✅ Service running" || echo "❌ Service failed"
|
||||
|
||||
build-and-deploy-frontend:
|
||||
name: Build & Deploy Frontend
|
||||
runs-on: linux
|
||||
if: github.event.pull_request.merged == true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone \
|
||||
--depth 1 \
|
||||
--branch production \
|
||||
https://oauth2:${{ secrets.DISTRIBUTION }}@gitea.hexdive.com/New_REP_SYSTEM/ERP_Distribution.git \
|
||||
.
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build --progress=plain --no-cache \
|
||||
--build-arg NEXT_PUBLIC_API_URL=https://distribution.hexdive.com/api \
|
||||
-t erpdistributionfe:latest \
|
||||
./distribution_fe
|
||||
- name: Stop & remove existing container
|
||||
run: |
|
||||
docker stop erpdistributionfe 2>/dev/null || true
|
||||
docker rm erpdistributionfe 2>/dev/null || true
|
||||
- name: Start container
|
||||
run: |
|
||||
docker run -d \
|
||||
--name erpdistributionfe \
|
||||
-p 8801:3000 \
|
||||
--restart unless-stopped \
|
||||
--memory="512m" \
|
||||
--cpus="1.0" \
|
||||
--cap-drop ALL \
|
||||
--security-opt no-new-privileges \
|
||||
erpdistributionfe:latest
|
||||
- name: Remove old images
|
||||
run: docker image prune -f
|
||||
- name: Verify
|
||||
run: |
|
||||
docker ps --filter name=erpdistributionfe --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" \
|
||||
| grep erpdistributionfe && echo "✅ Container running" || echo "❌ Container failed"
|
||||
@@ -0,0 +1,209 @@
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Infra.UoW;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class NotificationServiceTests
|
||||
{
|
||||
private readonly Mock<INotificationRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly Mock<IHubContext<NotificationHub>> _hub = new();
|
||||
private readonly Mock<IHubClients> _clients = new();
|
||||
private readonly Mock<IClientProxy> _proxy = new();
|
||||
private readonly NotificationService _sut;
|
||||
|
||||
public NotificationServiceTests()
|
||||
{
|
||||
_repo.Setup(r => r.FilterActiveUserIdsAsync(It.IsAny<IEnumerable<Guid>>()))
|
||||
.ReturnsAsync((IEnumerable<Guid> ids) => ids.ToList());
|
||||
_hub.SetupGet(h => h.Clients).Returns(_clients.Object);
|
||||
_clients.Setup(c => c.User(It.IsAny<string>())).Returns(_proxy.Object);
|
||||
|
||||
_sut = new NotificationService(
|
||||
_repo.Object,
|
||||
new NotificationRecipientResolver(_repo.Object),
|
||||
_hub.Object,
|
||||
_uow.Object,
|
||||
NullLogger<NotificationService>.Instance);
|
||||
}
|
||||
|
||||
private static NotificationArgs Args(Guid? actor = null) =>
|
||||
new(new { StoreName = "Fort Mini Mart" }, "DeliveryTask", Guid.NewGuid(), "/operations/delivery-tasks/1", actor);
|
||||
|
||||
// Registry
|
||||
|
||||
[Fact]
|
||||
public void EveryNotificationTypeConstant_HasADefinition()
|
||||
{
|
||||
var constants = typeof(NotificationTypes)
|
||||
.GetFields()
|
||||
.Where(f => f.IsLiteral && f.FieldType == typeof(string))
|
||||
.Select(f => (string)f.GetRawConstantValue()!)
|
||||
.ToList();
|
||||
|
||||
Assert.NotEmpty(constants);
|
||||
Assert.Equal(constants.Count, constants.Distinct().Count());
|
||||
Assert.All(constants, c => Assert.True(NotificationTypes.All.ContainsKey(c), $"{c} has no definition"));
|
||||
}
|
||||
|
||||
// NotifyAsync
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNothing_WhenTypeIsNotRegistered()
|
||||
{
|
||||
await _sut.NotifyAsync("NotARealType", [RecipientRule.Users(Guid.NewGuid())], Args());
|
||||
|
||||
_repo.Verify(r => r.AddAsync(It.IsAny<Models.Notification>()), Times.Never);
|
||||
_uow.Verify(u => u.CommitAsync(It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNothing_WhenThereAreNoRecipients()
|
||||
{
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users()], Args());
|
||||
|
||||
_repo.Verify(r => r.AddAsync(It.IsAny<Models.Notification>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_ExcludesTheActingUser()
|
||||
{
|
||||
var actor = Guid.NewGuid();
|
||||
var other = Guid.NewGuid();
|
||||
Models.Notification? saved = null;
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Models.Notification>()))
|
||||
.Callback<Models.Notification>(n => saved = n)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed,
|
||||
[RecipientRule.Users(actor, other)], Args(actor));
|
||||
|
||||
Assert.NotNull(saved);
|
||||
var recipient = Assert.Single(saved!.Recipients);
|
||||
Assert.Equal(other, recipient.FK_UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_RendersTemplate_CommitsAndPushesToEachRecipient()
|
||||
{
|
||||
var a = Guid.NewGuid();
|
||||
var b = Guid.NewGuid();
|
||||
Models.Notification? saved = null;
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Models.Notification>()))
|
||||
.Callback<Models.Notification>(n => saved = n)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(a, b)], Args());
|
||||
|
||||
Assert.Equal("Delivery to Fort Mini Mart failed.", saved!.Message);
|
||||
Assert.Equal(Models.NotificationSeverity.Warning, saved.Severity);
|
||||
_uow.Verify(u => u.CommitAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_clients.Verify(c => c.User(a.ToString()), Times.Once);
|
||||
_clients.Verify(c => c.User(b.ToString()), Times.Once);
|
||||
_proxy.Verify(p => p.SendCoreAsync(NotificationHub.ClientEvent,
|
||||
It.IsAny<object?[]>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNotThrow_WhenSavingFails_AndDiscardsTheEntity()
|
||||
{
|
||||
_uow.Setup(u => u.CommitAsync(It.IsAny<CancellationToken>())).ThrowsAsync(new InvalidOperationException("db down"));
|
||||
|
||||
var ex = await Record.ExceptionAsync(() =>
|
||||
_sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(Guid.NewGuid())], Args()));
|
||||
|
||||
Assert.Null(ex);
|
||||
_repo.Verify(r => r.Discard(It.IsAny<Models.Notification>()), Times.Once);
|
||||
_proxy.Verify(p => p.SendCoreAsync(It.IsAny<string>(), It.IsAny<object?[]>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNotThrow_WhenPushFails_AndKeepsTheSavedNotification()
|
||||
{
|
||||
_proxy.Setup(p => p.SendCoreAsync(It.IsAny<string>(), It.IsAny<object?[]>(), It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new InvalidOperationException("socket gone"));
|
||||
|
||||
var ex = await Record.ExceptionAsync(() =>
|
||||
_sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(Guid.NewGuid())], Args()));
|
||||
|
||||
Assert.Null(ex);
|
||||
_uow.Verify(u => u.CommitAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_repo.Verify(r => r.Discard(It.IsAny<Models.Notification>()), Times.Never);
|
||||
}
|
||||
|
||||
// Recipient rules
|
||||
|
||||
[Fact]
|
||||
public async Task PermissionInScope_ResolvesToTheIntersectionOfPermissionAndScope()
|
||||
{
|
||||
var both = Guid.NewGuid();
|
||||
var permissionOnly = Guid.NewGuid();
|
||||
var scopeOnly = Guid.NewGuid();
|
||||
var storeId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetUserIdsByPermissionAsync("store.manage")).ReturnsAsync([both, permissionOnly]);
|
||||
_repo.Setup(r => r.GetUserIdsByScopeAsync(ScopeKind.Store, storeId)).ReturnsAsync([both, scopeOnly]);
|
||||
|
||||
var ids = await new NotificationRecipientResolver(_repo.Object)
|
||||
.ResolveAsync([RecipientRule.PermissionInScope("store.manage", ScopeKind.Store, storeId)], actingUserId: null);
|
||||
|
||||
Assert.Equal([both], ids);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsTo_ResolvesToTheSupervisor_AndIsEmptyWhenThereIsNone()
|
||||
{
|
||||
var user = Guid.NewGuid();
|
||||
var supervisor = Guid.NewGuid();
|
||||
var resolver = new NotificationRecipientResolver(_repo.Object);
|
||||
|
||||
_repo.Setup(r => r.GetReportsToUserIdAsync(user)).ReturnsAsync(supervisor);
|
||||
Assert.Equal([supervisor], await resolver.ResolveAsync([RecipientRule.ReportsTo(user)], null));
|
||||
|
||||
_repo.Setup(r => r.GetReportsToUserIdAsync(user)).ReturnsAsync((Guid?)null);
|
||||
Assert.Empty(await resolver.ResolveAsync([RecipientRule.ReportsTo(user)], null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteOwner_ResolvesToTheOwner_AndIsEmptyWhenTheRouteHasNone()
|
||||
{
|
||||
var storeId = Guid.NewGuid();
|
||||
var owner = Guid.NewGuid();
|
||||
var resolver = new NotificationRecipientResolver(_repo.Object);
|
||||
|
||||
_repo.Setup(r => r.GetRouteOwnerUserIdAsync(ScopeKind.Store, storeId)).ReturnsAsync(owner);
|
||||
Assert.Equal([owner], await resolver.ResolveAsync([RecipientRule.RouteOwner(ScopeKind.Store, storeId)], null));
|
||||
|
||||
_repo.Setup(r => r.GetRouteOwnerUserIdAsync(ScopeKind.Store, storeId)).ReturnsAsync((Guid?)null);
|
||||
Assert.Empty(await resolver.ResolveAsync([RecipientRule.RouteOwner(ScopeKind.Store, storeId)], null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OptionalTokenAtTheEndOfATemplate_LeavesNoTrailingSpace()
|
||||
{
|
||||
Models.Notification? saved = null;
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Models.Notification>()))
|
||||
.Callback<Models.Notification>(n => saved = n)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(Guid.NewGuid())],
|
||||
new NotificationArgs(new { StoreName = "Fort Mini Mart", Note = (string?)null }, "DeliveryTask", Guid.NewGuid(), "/x"));
|
||||
|
||||
Assert.Equal("Delivery to Fort Mini Mart failed.", saved!.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Resolve_DeduplicatesUsersAcrossRules()
|
||||
{
|
||||
var user = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetUserIdsByPermissionAsync("stock.manage")).ReturnsAsync([user]);
|
||||
|
||||
var ids = await new NotificationRecipientResolver(_repo.Object)
|
||||
.ResolveAsync([RecipientRule.Users(user), RecipientRule.Permission("stock.manage")], null);
|
||||
|
||||
Assert.Equal([user], ids);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Features.Region;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Moq;
|
||||
@@ -10,11 +11,15 @@ public class RegionServiceTests
|
||||
{
|
||||
private readonly Mock<IRegionRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly Mock<ICurrentUserService> _currentUser = new();
|
||||
private readonly Mock<IScopeAccessService> _scopeAccess = new();
|
||||
private readonly RegionService _sut;
|
||||
|
||||
public RegionServiceTests()
|
||||
{
|
||||
_sut = new RegionService(_repo.Object, _uow.Object);
|
||||
_currentUser.Setup(u => u.RoleLevel).Returns(60);
|
||||
_repo.Setup(r => r.GetAllCodesAsync()).ReturnsAsync([]);
|
||||
_sut = new RegionService(_repo.Object, _uow.Object, _currentUser.Object, _scopeAccess.Object);
|
||||
}
|
||||
|
||||
private static Region ActiveRegion(string name = "North") =>
|
||||
@@ -144,6 +149,123 @@ public class RegionServiceTests
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
// Region code
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_GeneratesFirstCode_WhenNoRegionsExist()
|
||||
{
|
||||
_repo.Setup(r => r.GetAllCodesAsync()).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.CreateAsync(new CreateRegionRequest("North"));
|
||||
|
||||
Assert.Equal("REG-0001", result.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_GeneratesNextCode_FromHighestExisting_IgnoringNonMatching()
|
||||
{
|
||||
_repo.Setup(r => r.GetAllCodesAsync()).ReturnsAsync(["REG-0002", "REG-0009", "CUSTOM", "REG-abc", ""]);
|
||||
|
||||
var result = await _sut.CreateAsync(new CreateRegionRequest("North"));
|
||||
|
||||
Assert.Equal("REG-0010", result.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_StoresProvidedCode_AsEntered()
|
||||
{
|
||||
var result = await _sut.CreateAsync(new CreateRegionRequest("North", " nth-1 "));
|
||||
|
||||
Assert.Equal("nth-1", result.Code);
|
||||
_repo.Verify(r => r.GetAllCodesAsync(), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Throws_WhenProvidedCodeInUse()
|
||||
{
|
||||
_repo.Setup(r => r.CodeExistsAsync("NTH-1", null)).ReturnsAsync(true);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_sut.CreateAsync(new CreateRegionRequest("North", "NTH-1")));
|
||||
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetNextCodeAsync_ReturnsSuggestion()
|
||||
{
|
||||
_repo.Setup(r => r.GetAllCodesAsync()).ReturnsAsync(["REG-0003"]);
|
||||
|
||||
var result = await _sut.GetNextCodeAsync();
|
||||
|
||||
Assert.Equal("REG-0004", result.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ChangesCode_StoredAsEntered()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
region.Code = "REG-0001";
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.CodeExistsAsync("north-1", region.Id)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.UpdateAsync(region.Id, new UpdateRegionRequest("North", " north-1 "));
|
||||
|
||||
Assert.Equal("north-1", result!.Code);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_KeepsCode_WhenCodeBlank()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
region.Code = "REG-0001";
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
|
||||
var result = await _sut.UpdateAsync(region.Id, new UpdateRegionRequest("North", " "));
|
||||
|
||||
Assert.Equal("REG-0001", result!.Code);
|
||||
_repo.Verify(r => r.CodeExistsAsync(It.IsAny<string>(), It.IsAny<Guid?>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_Throws_WhenCodeUsedByAnotherRegion()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
region.Code = "REG-0001";
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.CodeExistsAsync("REG-0002", region.Id)).ReturnsAsync(true);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_sut.UpdateAsync(region.Id, new UpdateRegionRequest("North", "REG-0002")));
|
||||
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_AllowsResubmittingOwnCode()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
region.Code = "REG-0001";
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
|
||||
var result = await _sut.UpdateAsync(region.Id, new UpdateRegionRequest("North", "REG-0001"));
|
||||
|
||||
Assert.Equal("REG-0001", result!.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsCode()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
region.Code = "REG-0007";
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
|
||||
var result = await _sut.GetByIdAsync(region.Id);
|
||||
|
||||
Assert.Equal("REG-0007", result!.Code);
|
||||
}
|
||||
|
||||
// DeleteAsync
|
||||
|
||||
[Fact]
|
||||
@@ -191,4 +313,93 @@ public class RegionServiceTests
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
// Region manager
|
||||
|
||||
private static User ManagerUser(string role = "RegionalManager", bool active = true) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(), Username = "mgr", FullName = "Mgr One", IsActive = active,
|
||||
Role = new Role { Name = role, Level = 50 }
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task SetManagerAsync_ReturnsNull_WhenRegionMissing()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Region?)null);
|
||||
|
||||
Assert.Null(await _sut.SetManagerAsync(Guid.NewGuid(), new SetRegionManagerRequest(Guid.NewGuid())));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetManagerAsync_Throws_WhenUserNotRegionalManager()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
var user = ManagerUser("SalesRep");
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.GetUserWithRoleAsync(user.Id)).ReturnsAsync(user);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_sut.SetManagerAsync(region.Id, new SetRegionManagerRequest(user.Id)));
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetManagerAsync_ReplacesExistingManager_AndAddsScope()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
var user = ManagerUser();
|
||||
var old = new UserRegionScope { FK_UserId = Guid.NewGuid(), FK_RegionId = region.Id };
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.GetUserWithRoleAsync(user.Id)).ReturnsAsync(user);
|
||||
_repo.Setup(r => r.GetManagerScopesAsync(region.Id)).ReturnsAsync([old]);
|
||||
_repo.Setup(r => r.GetRegionScopeAsync(user.Id, region.Id)).ReturnsAsync((UserRegionScope?)null);
|
||||
|
||||
var result = await _sut.SetManagerAsync(region.Id, new SetRegionManagerRequest(user.Id));
|
||||
|
||||
Assert.Equal(user.Id, result!.UserId);
|
||||
_repo.Verify(r => r.RemoveRegionScopes(It.Is<IEnumerable<UserRegionScope>>(s => s.Single() == old)), Times.Once);
|
||||
_repo.Verify(r => r.AddRegionScopeAsync(It.Is<UserRegionScope>(s => s.FK_UserId == user.Id && s.FK_RegionId == region.Id)), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetManagerAsync_DoesNotDuplicateScope_WhenAlreadyManager()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
var user = ManagerUser();
|
||||
var existing = new UserRegionScope { FK_UserId = user.Id, FK_RegionId = region.Id };
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.GetUserWithRoleAsync(user.Id)).ReturnsAsync(user);
|
||||
_repo.Setup(r => r.GetManagerScopesAsync(region.Id)).ReturnsAsync([existing]);
|
||||
_repo.Setup(r => r.GetRegionScopeAsync(user.Id, region.Id)).ReturnsAsync(existing);
|
||||
|
||||
await _sut.SetManagerAsync(region.Id, new SetRegionManagerRequest(user.Id));
|
||||
|
||||
_repo.Verify(r => r.AddRegionScopeAsync(It.IsAny<UserRegionScope>()), Times.Never);
|
||||
_repo.Verify(r => r.RemoveRegionScopes(It.Is<IEnumerable<UserRegionScope>>(s => !s.Any())), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RemoveManagerAsync_ReturnsFalse_WhenNoManager()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.GetManagerScopesAsync(region.Id)).ReturnsAsync([]);
|
||||
|
||||
Assert.False(await _sut.RemoveManagerAsync(region.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetManagerAsync_ReturnsManager()
|
||||
{
|
||||
var region = ActiveRegion();
|
||||
var user = ManagerUser();
|
||||
_repo.Setup(r => r.GetByIdAsync(region.Id)).ReturnsAsync(region);
|
||||
_repo.Setup(r => r.GetManagerScopesAsync(region.Id))
|
||||
.ReturnsAsync([new UserRegionScope { FK_UserId = user.Id, FK_RegionId = region.Id, User = user }]);
|
||||
|
||||
var result = await _sut.GetManagerAsync(region.Id);
|
||||
|
||||
Assert.Equal("mgr", result!.Username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
using Distribution.Features.Region;
|
||||
using Distribution.Features.Route;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class RouteServiceTests
|
||||
{
|
||||
private readonly Mock<IRouteRepository> _repo = new();
|
||||
private readonly Mock<IRegionRepository> _regionRepo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly Mock<ICurrentUserService> _currentUser = new();
|
||||
private readonly Mock<IScopeAccessService> _scopeAccess = new();
|
||||
private readonly RouteService _sut;
|
||||
|
||||
private static readonly Guid RepUserId = Guid.NewGuid();
|
||||
|
||||
public RouteServiceTests()
|
||||
{
|
||||
_currentUser.Setup(u => u.RoleLevel).Returns(20);
|
||||
_currentUser.Setup(u => u.UserId).Returns(RepUserId);
|
||||
_sut = new RouteService(_repo.Object, _regionRepo.Object, _uow.Object, _currentUser.Object, _scopeAccess.Object);
|
||||
}
|
||||
|
||||
private static Models.Route ActiveRoute(Guid? ownerId = null) =>
|
||||
new() { Id = Guid.NewGuid(), RouteName = "R1", StartPoint = "A", EndPoint = "B", Length = 10, IsActive = true, FK_OwnerUserId = ownerId };
|
||||
|
||||
private static Models.Route InactiveRoute() =>
|
||||
new() { Id = Guid.NewGuid(), RouteName = "Old", StartPoint = "A", EndPoint = "B", Length = 10, IsActive = false };
|
||||
|
||||
private static Store ActiveStore(Guid routeId, decimal lat = 10m, decimal lon = 20m) =>
|
||||
new() { Id = Guid.NewGuid(), FK_RouteId = routeId, IsActive = true, Latitude = lat, Longitude = lon };
|
||||
|
||||
// AssignStoresToRouteAsync
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_ReturnsNull_WhenRouteNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Models.Route?)null);
|
||||
|
||||
var result = await _sut.AssignStoresToRouteAsync(Guid.NewGuid(), new AssignStoresToRouteRequest([Guid.NewGuid()]));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_ReturnsNull_WhenRouteInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveRoute());
|
||||
|
||||
var result = await _sut.AssignStoresToRouteAsync(Guid.NewGuid(), new AssignStoresToRouteRequest([Guid.NewGuid()]));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_ReturnsNull_WhenCallerLacksRouteScope()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.AssignStoresToRouteAsync(route.Id, new AssignStoresToRouteRequest([Guid.NewGuid()]));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_ReturnsNull_WhenCallerBelowRoleFloor()
|
||||
{
|
||||
_currentUser.Setup(u => u.RoleLevel).Returns(10);
|
||||
var route = ActiveRoute();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
|
||||
var result = await _sut.AssignStoresToRouteAsync(route.Id, new AssignStoresToRouteRequest([Guid.NewGuid()]));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_ReturnsNull_WhenAnyStoreIdMissing()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
var storeIds = new List<Guid> { Guid.NewGuid(), Guid.NewGuid() };
|
||||
_repo.Setup(r => r.GetStoresByIdsAsync(storeIds)).ReturnsAsync([ActiveStore(route.Id)]); // only one of two found
|
||||
|
||||
var result = await _sut.AssignStoresToRouteAsync(route.Id, new AssignStoresToRouteRequest(storeIds));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_UpdatesEachStore_AndCommits()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var otherRouteId = Guid.NewGuid();
|
||||
var stores = new List<Store> { ActiveStore(otherRouteId), ActiveStore(otherRouteId) };
|
||||
var storeIds = stores.Select(s => s.Id).ToList();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetStoresByIdsAsync(storeIds)).ReturnsAsync(stores);
|
||||
|
||||
var result = await _sut.AssignStoresToRouteAsync(route.Id, new AssignStoresToRouteRequest(storeIds));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.All(stores, s => Assert.Equal(route.Id, s.FK_RouteId));
|
||||
_repo.Verify(r => r.UpdateStore(It.IsAny<Store>()), Times.Exactly(2));
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignStoresToRouteAsync_Rollbacks_WhenUpdateThrows()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var stores = new List<Store> { ActiveStore(route.Id) };
|
||||
var storeIds = stores.Select(s => s.Id).ToList();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetStoresByIdsAsync(storeIds)).ReturnsAsync(stores);
|
||||
_repo.Setup(r => r.UpdateStore(It.IsAny<Store>())).Throws(new Exception("db error"));
|
||||
|
||||
await Assert.ThrowsAsync<Exception>(() => _sut.AssignStoresToRouteAsync(route.Id, new AssignStoresToRouteRequest(storeIds)));
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
// AssignOwnerAsync
|
||||
|
||||
[Fact]
|
||||
public async Task AssignOwnerAsync_ReturnsNull_WhenRouteNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Models.Route?)null);
|
||||
|
||||
var result = await _sut.AssignOwnerAsync(Guid.NewGuid(), new AssignRouteOwnerRequest(Guid.NewGuid()));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignOwnerAsync_ReturnsNull_WhenTargetUserNotActive()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetActiveUserAsync(It.IsAny<Guid>())).ReturnsAsync((User?)null);
|
||||
|
||||
var result = await _sut.AssignOwnerAsync(route.Id, new AssignRouteOwnerRequest(Guid.NewGuid()));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignOwnerAsync_SetsOwner_AndCommits()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var salesRep = new User { Id = Guid.NewGuid(), IsActive = true };
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetActiveUserAsync(salesRep.Id)).ReturnsAsync(salesRep);
|
||||
|
||||
var result = await _sut.AssignOwnerAsync(route.Id, new AssignRouteOwnerRequest(salesRep.Id));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(salesRep.Id, result.OwnerUserId);
|
||||
Assert.Equal(salesRep.Id, route.FK_OwnerUserId);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignOwnerAsync_AllowsReassignment_ToDifferentSalesRep()
|
||||
{
|
||||
var firstOwner = Guid.NewGuid();
|
||||
var route = ActiveRoute(firstOwner);
|
||||
var newRep = new User { Id = Guid.NewGuid(), IsActive = true };
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetActiveUserAsync(newRep.Id)).ReturnsAsync(newRep);
|
||||
|
||||
var result = await _sut.AssignOwnerAsync(route.Id, new AssignRouteOwnerRequest(newRep.Id));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(newRep.Id, result.OwnerUserId);
|
||||
Assert.NotEqual(firstOwner, result.OwnerUserId);
|
||||
}
|
||||
|
||||
// RecordCheckInAsync
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_ReturnsNull_WhenCallerNotAuthenticated()
|
||||
{
|
||||
_currentUser.Setup(u => u.UserId).Returns((Guid?)null);
|
||||
|
||||
var result = await _sut.RecordCheckInAsync(Guid.NewGuid(), new RecordCheckInRequest(Guid.NewGuid(), 1, 2, null));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_ReturnsNull_WhenRouteNotFoundOrInactive()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync(InactiveRoute());
|
||||
|
||||
var result = await _sut.RecordCheckInAsync(Guid.NewGuid(), new RecordCheckInRequest(Guid.NewGuid(), 1, 2, null));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_ReturnsNull_WhenCallerLacksRouteScope()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(false);
|
||||
|
||||
var result = await _sut.RecordCheckInAsync(route.Id, new RecordCheckInRequest(Guid.NewGuid(), 1, 2, null));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_ReturnsNull_WhenStoreNotOnRoute()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var store = ActiveStore(Guid.NewGuid()); // belongs to a different route
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetStoreByIdAsync(store.Id)).ReturnsAsync(store);
|
||||
|
||||
var result = await _sut.RecordCheckInAsync(route.Id, new RecordCheckInRequest(store.Id, 1, 2, null));
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_NotFlagged_WhenGpsMatchesStoreLocation()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var store = ActiveStore(route.Id, lat: 12.9716m, lon: 77.5946m);
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetStoreByIdAsync(store.Id)).ReturnsAsync(store);
|
||||
|
||||
var result = await _sut.RecordCheckInAsync(route.Id, new RecordCheckInRequest(store.Id, 12.9716m, 77.5946m, null));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.IsDeviationFlagged);
|
||||
_repo.Verify(r => r.AddVisitLogAsync(It.IsAny<RouteVisitLog>()), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_Flagged_WhenGpsFarFromStoreLocation()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var store = ActiveStore(route.Id, lat: 12.9716m, lon: 77.5946m); // Bengaluru
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetStoreByIdAsync(store.Id)).ReturnsAsync(store);
|
||||
|
||||
// Mumbai coordinates - hundreds of km away
|
||||
var result = await _sut.RecordCheckInAsync(route.Id, new RecordCheckInRequest(store.Id, 19.0760m, 72.8777m, null));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsDeviationFlagged);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckInAsync_UsesCurrentUserId_AsVisitingRep()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var store = ActiveStore(route.Id);
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetStoreByIdAsync(store.Id)).ReturnsAsync(store);
|
||||
|
||||
var result = await _sut.RecordCheckInAsync(route.Id, new RecordCheckInRequest(store.Id, store.Latitude, store.Longitude, null));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(RepUserId, result.UserId);
|
||||
}
|
||||
|
||||
// RecordCheckOutAsync
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckOutAsync_ReturnsNull_WhenNoOpenVisit()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetOpenVisitAsync(route.Id, It.IsAny<Guid>())).ReturnsAsync((RouteVisitLog?)null);
|
||||
|
||||
var result = await _sut.RecordCheckOutAsync(route.Id, new RecordCheckOutRequest(Guid.NewGuid(), null));
|
||||
|
||||
Assert.Null(result);
|
||||
_uow.Verify(u => u.BeginAsync(default), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckOutAsync_SetsCheckOutAt_AndCommits()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var storeId = Guid.NewGuid();
|
||||
var log = new RouteVisitLog { Id = Guid.NewGuid(), FK_RouteId = route.Id, FK_StoreId = storeId, FK_UserId = RepUserId, CheckInAt = DateTime.Now.AddHours(-1) };
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetOpenVisitAsync(route.Id, storeId)).ReturnsAsync(log);
|
||||
|
||||
var result = await _sut.RecordCheckOutAsync(route.Id, new RecordCheckOutRequest(storeId, null));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.CheckOutAt);
|
||||
Assert.NotNull(log.CheckOutAt);
|
||||
_repo.Verify(r => r.UpdateVisitLog(log), Times.Once);
|
||||
_uow.Verify(u => u.CommitAsync(default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecordCheckOutAsync_Rollbacks_WhenUpdateThrows()
|
||||
{
|
||||
var route = ActiveRoute();
|
||||
var storeId = Guid.NewGuid();
|
||||
var log = new RouteVisitLog { Id = Guid.NewGuid(), FK_RouteId = route.Id, FK_StoreId = storeId, FK_UserId = RepUserId, CheckInAt = DateTime.Now };
|
||||
_repo.Setup(r => r.GetByIdAsync(route.Id)).ReturnsAsync(route);
|
||||
_scopeAccess.Setup(s => s.HasRouteScopeAsync(RepUserId, route.Id)).ReturnsAsync(true);
|
||||
_repo.Setup(r => r.GetOpenVisitAsync(route.Id, storeId)).ReturnsAsync(log);
|
||||
_repo.Setup(r => r.UpdateVisitLog(It.IsAny<RouteVisitLog>())).Throws(new Exception("db error"));
|
||||
|
||||
await Assert.ThrowsAsync<Exception>(() => _sut.RecordCheckOutAsync(route.Id, new RecordCheckOutRequest(storeId, null)));
|
||||
|
||||
_uow.Verify(u => u.RollbackAsync(default), Times.Once);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.Order;
|
||||
using Distribution.Features.Route;
|
||||
using Distribution.Features.Scheme;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class StoreServiceTests
|
||||
{
|
||||
private readonly Mock<IStoreRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly Mock<ICurrentUserService> _currentUser = new();
|
||||
private readonly Mock<IScopeAccessService> _scopeAccess = new();
|
||||
private readonly Mock<IRouteRepository> _routeRepo = new();
|
||||
private readonly StoreService _sut;
|
||||
|
||||
public StoreServiceTests()
|
||||
{
|
||||
_currentUser.Setup(u => u.RoleLevel).Returns(50); // global scope, skips per-store scope lookups
|
||||
var schemeService = new SchemeService(
|
||||
Mock.Of<ISchemeRepository>(),
|
||||
Mock.Of<IOrderRepository>(),
|
||||
Mock.Of<IItemRepository>(),
|
||||
_repo.Object,
|
||||
Mock.Of<IStockRepository>(),
|
||||
_uow.Object);
|
||||
_sut = new StoreService(_repo.Object, _uow.Object, _currentUser.Object, _scopeAccess.Object, schemeService, _routeRepo.Object);
|
||||
}
|
||||
|
||||
private static Store ActiveStore() =>
|
||||
new() { Id = Guid.NewGuid(), IsActive = true };
|
||||
|
||||
// GetVisitHistoryAsync
|
||||
|
||||
[Fact]
|
||||
public async Task GetVisitHistoryAsync_ReturnsNull_WhenStoreNotFound()
|
||||
{
|
||||
_repo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>())).ReturnsAsync((Store?)null);
|
||||
|
||||
var result = await _sut.GetVisitHistoryAsync(Guid.NewGuid());
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetVisitHistoryAsync_ReturnsEmpty_WhenNoVisitsLogged()
|
||||
{
|
||||
var store = ActiveStore();
|
||||
_repo.Setup(r => r.GetByIdAsync(store.Id)).ReturnsAsync(store);
|
||||
_routeRepo.Setup(r => r.GetVisitsByStoreIdAsync(store.Id)).ReturnsAsync([]);
|
||||
|
||||
var result = await _sut.GetVisitHistoryAsync(store.Id);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetVisitHistoryAsync_MapsVisits_MostRecentFirst()
|
||||
{
|
||||
var store = ActiveStore();
|
||||
var repId = Guid.NewGuid();
|
||||
var older = new RouteVisitLog { Id = Guid.NewGuid(), FK_StoreId = store.Id, FK_UserId = repId, CheckInAt = DateTime.Now.AddDays(-2), CheckOutAt = DateTime.Now.AddDays(-2).AddHours(1) };
|
||||
var newer = new RouteVisitLog { Id = Guid.NewGuid(), FK_StoreId = store.Id, FK_UserId = repId, CheckInAt = DateTime.Now.AddDays(-1), CheckOutAt = null };
|
||||
_repo.Setup(r => r.GetByIdAsync(store.Id)).ReturnsAsync(store);
|
||||
_routeRepo.Setup(r => r.GetVisitsByStoreIdAsync(store.Id)).ReturnsAsync([older, newer]);
|
||||
|
||||
var result = await _sut.GetVisitHistoryAsync(store.Id);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal(newer.Id, result[0].VisitId);
|
||||
Assert.Equal("In Progress", result[0].Outcome);
|
||||
Assert.Equal(older.Id, result[1].VisitId);
|
||||
Assert.Equal("Completed", result[1].Outcome);
|
||||
Assert.Equal(repId, result[0].RepUserId);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution_db;User=kalana;Password=kalana@hexdive"
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution;User=kalana;Password=kalana@hexdive"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+397af8fd597bb7dd2d4372bf717d06c8e49552b7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+85a16396aad31f1e82cf28f330a97dee2eafa51d")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
569664876b2d87d1ec65add142012e5eae24d860ed90b6cc736c1cb4bae13b9a
|
||||
100cae0f5643c7476420ef14e72b5b17dcb877fe4318c0b6cb2cff0baa955aa5
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property.EntryPointFilePath =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = Distribution.Tests
|
||||
build_property.ProjectDir = C:\Users\lenovo\Desktop\NEWGITEA\ERP_Distribution\Distribution.Tests\
|
||||
build_property.ProjectDir = D:\HexDrive Projects\Distribution\ERP_Distribution\Distribution.Tests\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 9.0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {}
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectUniqueName": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectName": "Distribution.Tests",
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"packagesPath": "C:\\Users\\lenovo\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\obj\\",
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"packagesPath": "C:\\Users\\dell\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\lenovo\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Users\\dell\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
@@ -26,6 +26,7 @@
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@@ -33,8 +34,8 @@
|
||||
"framework": "net9.0",
|
||||
"targetAlias": "net9.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +50,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.300"
|
||||
"SdkAnalysisLevel": "10.0.400"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
@@ -97,24 +98,24 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.302/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj",
|
||||
"projectUniqueName": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj",
|
||||
"projectName": "Distribution",
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj",
|
||||
"packagesPath": "C:\\Users\\lenovo\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\obj\\",
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj",
|
||||
"packagesPath": "C:\\Users\\dell\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\lenovo\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Users\\dell\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
@@ -123,6 +124,7 @@
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@@ -142,7 +144,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.300"
|
||||
"SdkAnalysisLevel": "10.0.400"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
@@ -191,7 +193,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.302/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\lenovo\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\dell\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\lenovo\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Users\dell\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
@@ -22,7 +22,7 @@
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk\17.12.0\build\netcoreapp3.1\Microsoft.NET.Test.Sdk.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk\17.12.0\build\netcoreapp3.1\Microsoft.NET.Test.Sdk.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Pkgxunit_analyzers Condition=" '$(Pkgxunit_analyzers)' == '' ">C:\Users\lenovo\.nuget\packages\xunit.analyzers\1.18.0</Pkgxunit_analyzers>
|
||||
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">C:\Users\lenovo\.nuget\packages\microsoft.extensions.apidescription.server\6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
|
||||
<Pkgxunit_analyzers Condition=" '$(Pkgxunit_analyzers)' == '' ">C:\Users\dell\.nuget\packages\xunit.analyzers\1.18.0</Pkgxunit_analyzers>
|
||||
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">C:\Users\dell\.nuget\packages\microsoft.extensions.apidescription.server\6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -2542,23 +2542,23 @@
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\": {},
|
||||
"C:\\Users\\dell\\.nuget\\packages\\": {},
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectUniqueName": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectName": "Distribution.Tests",
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"packagesPath": "C:\\Users\\lenovo\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\obj\\",
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"packagesPath": "C:\\Users\\dell\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\lenovo\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Users\\dell\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
@@ -2567,6 +2567,7 @@
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@@ -2574,8 +2575,8 @@
|
||||
"framework": "net9.0",
|
||||
"targetAlias": "net9.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2590,7 +2591,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.300"
|
||||
"SdkAnalysisLevel": "10.0.400"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
@@ -2638,7 +2639,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.302/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "XdH8cqlThLk=",
|
||||
"dgSpecHash": "JPCR3IFZHKY=",
|
||||
"success": true,
|
||||
"projectFilePath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectFilePath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\castle.core\\5.1.1\\castle.core.5.1.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\coverlet.collector\\6.0.2\\coverlet.collector.6.0.2.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.aspnetcore.authentication.jwtbearer\\9.0.0\\microsoft.aspnetcore.authentication.jwtbearer.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.aspnetcore.openapi\\9.0.0\\microsoft.aspnetcore.openapi.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.codecoverage\\17.12.0\\microsoft.codecoverage.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.entityframeworkcore\\9.0.0\\microsoft.entityframeworkcore.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.entityframeworkcore.abstractions\\9.0.0\\microsoft.entityframeworkcore.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.entityframeworkcore.analyzers\\9.0.0\\microsoft.entityframeworkcore.analyzers.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.entityframeworkcore.relational\\9.0.0\\microsoft.entityframeworkcore.relational.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.apidescription.server\\6.0.5\\microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\9.0.0\\microsoft.extensions.caching.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.caching.memory\\9.0.0\\microsoft.extensions.caching.memory.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\9.0.0\\microsoft.extensions.configuration.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.dependencyinjection\\9.0.0\\microsoft.extensions.dependencyinjection.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\9.0.0\\microsoft.extensions.dependencyinjection.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.logging\\9.0.0\\microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\9.0.0\\microsoft.extensions.logging.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.options\\9.0.0\\microsoft.extensions.options.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.extensions.primitives\\9.0.0\\microsoft.extensions.primitives.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.identitymodel.abstractions\\8.0.1\\microsoft.identitymodel.abstractions.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\8.0.1\\microsoft.identitymodel.jsonwebtokens.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.identitymodel.logging\\8.0.1\\microsoft.identitymodel.logging.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.identitymodel.protocols\\8.0.1\\microsoft.identitymodel.protocols.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.identitymodel.protocols.openidconnect\\8.0.1\\microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.identitymodel.tokens\\8.0.1\\microsoft.identitymodel.tokens.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.net.test.sdk\\17.12.0\\microsoft.net.test.sdk.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.openapi\\1.6.22\\microsoft.openapi.1.6.22.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.testplatform.objectmodel\\17.12.0\\microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\microsoft.testplatform.testhost\\17.12.0\\microsoft.testplatform.testhost.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\moq\\4.20.72\\moq.4.20.72.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\mysqlconnector\\2.4.0\\mysqlconnector.2.4.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\pomelo.entityframeworkcore.mysql\\9.0.0\\pomelo.entityframeworkcore.mysql.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\swashbuckle.aspnetcore\\7.3.1\\swashbuckle.aspnetcore.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\swashbuckle.aspnetcore.swagger\\7.3.1\\swashbuckle.aspnetcore.swagger.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\swashbuckle.aspnetcore.swaggergen\\7.3.1\\swashbuckle.aspnetcore.swaggergen.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\swashbuckle.aspnetcore.swaggerui\\7.3.1\\swashbuckle.aspnetcore.swaggerui.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\system.diagnostics.eventlog\\6.0.0\\system.diagnostics.eventlog.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\system.identitymodel.tokens.jwt\\8.0.1\\system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\system.reflection.metadata\\1.6.0\\system.reflection.metadata.1.6.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit\\2.9.3\\xunit.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.abstractions\\2.0.3\\xunit.abstractions.2.0.3.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.analyzers\\1.18.0\\xunit.analyzers.1.18.0.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.assert\\2.9.3\\xunit.assert.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.core\\2.9.3\\xunit.core.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.extensibility.core\\2.9.3\\xunit.extensibility.core.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.extensibility.execution\\2.9.3\\xunit.extensibility.execution.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\xunit.runner.visualstudio\\2.8.2\\xunit.runner.visualstudio.2.8.2.nupkg.sha512"
|
||||
"C:\\Users\\dell\\.nuget\\packages\\castle.core\\5.1.1\\castle.core.5.1.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\coverlet.collector\\6.0.2\\coverlet.collector.6.0.2.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.aspnetcore.authentication.jwtbearer\\9.0.0\\microsoft.aspnetcore.authentication.jwtbearer.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.aspnetcore.openapi\\9.0.0\\microsoft.aspnetcore.openapi.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.codecoverage\\17.12.0\\microsoft.codecoverage.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.entityframeworkcore\\9.0.0\\microsoft.entityframeworkcore.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.entityframeworkcore.abstractions\\9.0.0\\microsoft.entityframeworkcore.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.entityframeworkcore.analyzers\\9.0.0\\microsoft.entityframeworkcore.analyzers.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.entityframeworkcore.relational\\9.0.0\\microsoft.entityframeworkcore.relational.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.apidescription.server\\6.0.5\\microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\9.0.0\\microsoft.extensions.caching.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.caching.memory\\9.0.0\\microsoft.extensions.caching.memory.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\9.0.0\\microsoft.extensions.configuration.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.dependencyinjection\\9.0.0\\microsoft.extensions.dependencyinjection.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\9.0.0\\microsoft.extensions.dependencyinjection.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.logging\\9.0.0\\microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\9.0.0\\microsoft.extensions.logging.abstractions.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.options\\9.0.0\\microsoft.extensions.options.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.extensions.primitives\\9.0.0\\microsoft.extensions.primitives.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.identitymodel.abstractions\\8.0.1\\microsoft.identitymodel.abstractions.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\8.0.1\\microsoft.identitymodel.jsonwebtokens.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.identitymodel.logging\\8.0.1\\microsoft.identitymodel.logging.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.identitymodel.protocols\\8.0.1\\microsoft.identitymodel.protocols.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.identitymodel.protocols.openidconnect\\8.0.1\\microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.identitymodel.tokens\\8.0.1\\microsoft.identitymodel.tokens.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.net.test.sdk\\17.12.0\\microsoft.net.test.sdk.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.openapi\\1.6.22\\microsoft.openapi.1.6.22.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.testplatform.objectmodel\\17.12.0\\microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\microsoft.testplatform.testhost\\17.12.0\\microsoft.testplatform.testhost.17.12.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\moq\\4.20.72\\moq.4.20.72.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\mysqlconnector\\2.4.0\\mysqlconnector.2.4.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\pomelo.entityframeworkcore.mysql\\9.0.0\\pomelo.entityframeworkcore.mysql.9.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\swashbuckle.aspnetcore\\7.3.1\\swashbuckle.aspnetcore.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\swashbuckle.aspnetcore.swagger\\7.3.1\\swashbuckle.aspnetcore.swagger.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\swashbuckle.aspnetcore.swaggergen\\7.3.1\\swashbuckle.aspnetcore.swaggergen.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\swashbuckle.aspnetcore.swaggerui\\7.3.1\\swashbuckle.aspnetcore.swaggerui.7.3.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\system.diagnostics.eventlog\\6.0.0\\system.diagnostics.eventlog.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\system.identitymodel.tokens.jwt\\8.0.1\\system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\system.reflection.metadata\\1.6.0\\system.reflection.metadata.1.6.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit\\2.9.3\\xunit.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.abstractions\\2.0.3\\xunit.abstractions.2.0.3.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.analyzers\\1.18.0\\xunit.analyzers.1.18.0.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.assert\\2.9.3\\xunit.assert.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.core\\2.9.3\\xunit.core.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.extensibility.core\\2.9.3\\xunit.extensibility.core.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.extensibility.execution\\2.9.3\\xunit.extensibility.execution.2.9.3.nupkg.sha512",
|
||||
"C:\\Users\\dell\\.nuget\\packages\\xunit.runner.visualstudio\\2.8.2\\xunit.runner.visualstudio.2.8.2.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
False
|
||||
@@ -0,0 +1 @@
|
||||
a3dd8e4b-8c69-4536-b080-24506bc95dea
|
||||
@@ -0,0 +1 @@
|
||||
False
|
||||
@@ -0,0 +1 @@
|
||||
bf7d0202-9a86-43b3-8323-402073a8c61a
|
||||
@@ -0,0 +1 @@
|
||||
614d490d-41ba-47f9-ae3a-b56948b3d6e4
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTS2/jIBD+Kxbn4GKMMfatbVJtVHUbtdHuam8wjBtUF0cY96Eq/32Fk6rbve0FoREz8714J14/IWnJ+X6/ekYfR7Ig0c0lzrikTFHOtky1jLeszouS84pVv8mCuGt8Iy2pBUABDdISJVJhWUVN2QG1FpuCFZbxSqSZ+mEk7TvRLrf47ADzYfyBYXSDJy356bwdXtJu7XLoh8nmYehx7ceoPSQwy9X99fZ2Q69q+X11cXF8OeKYBuTOkpY0tWClQKQSTE1FozhVNXDKhIamKysrJRzbnI8YvO7z0T5+YrBD9Bih5Tkvc0Z5Qw4LYnXUCbbRI27f9gnJLNMy1Rdzeb627+QZA2n54kPQ47gz6N3Z02gm19uz4xn1+JiY7sOwxxAdzrrMVPteRzf47LTpsnfoI1mQ2/ts0+vYDeHpi1j3R/7r/6C/TdtXrwhTRHs5TD6SliiyIDcads5jtl6SlshCWNEwS0VhNBV111CNpaamko1QprQSk6mXg4/OT8M0Zmsf8SHM8ElLrnQ/4hH4eYCdiwhxConTL5kaN2GwE8TsU/2C5SwvGU9NU9xPMbtD6wJCxMRuG6bTvH9auOSMJShTCOhjttFxl33T4y49sLWtgCPaojQ1Mx1WyqpKQW2lVLIDUQEriwqEgLIzttHGNCVwwTsjsJZkQTCZnc35KpRSHVea1lpzKjgIqroCqdbAFGssGmO/6Jjd9qlPl9YqFIYqkA0VVSmpYYpRLiomDTSVxRSlD0lO3ovkyTUGj/1flG8chGEcupidQpB9EeFu8unvZnMgXpynr7PaywEeMWTJLe18iunJoMPhcPgDAAD//wMANAV6ewIEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoyUW2/cNhCF/4rAZ1GmSIq6vMVeGzXcZJ3sJi36xstoTVgmFyTl1DD2vxfUruts0oe+CdTozMczZ/SKnHwCNKAP+/31M7gUUYmSXY4ooQKTDlOyJd1A6EDaqhesJkz8hUpk7+AFDajlWte6B8xAAOaGNFixUWNjoK9JbQhteNaUu4iGVyRtZeDZaqh8/AYhWu/QgP6wzvjvube0lZ78bKrgJ7h1MUmnM8zqenO3Xd/jm1Z8ur68PFZGiFmgsgYNSNW17EwjcF03BvNaU9y3jGEqemWgFbJm5viZdQmCk1MVzeM7g/HJQdIDrSirCKY9OpTIyCQztpIRti/7TLLYtMrn5XK8PA6v6BkCGmj5ZuhR7kJP9uIpqtlO5mLyu511uyvvRrubg0y5cYn2we8hJAuLQetN8c5Uk4pUVFBCUIk+Sv1gHRS3q2I95RtLZkwHXOFOix7zhgmsSEcw5Q0RSveNgQx5B8HB9IPqR6uDj35Mxcn34qzPlXfJutnPsbh1CXYn0AHdyClCLphDAJeKe5keit9kfMikpjWNpgCmZqolaoSmM13T6dYI0YlR80YTVjeac81GZXqpVM805XRUHFqBSrSF8GSdnH73u1328t9+5y9WMMp5Shs/hyUZ0TyiEl1aJ8PLseJrBHOq+nQcxpvSEqhpWi5UnOZ5NVlw6cxfNCBRc8N7koOkJObt2GMJTGLViJ53ihkBOdb3wZtZp59HxghFJfoyu7xKxW0e1nfr8N+C/4T6AxvkXBVLlDWlou17jrUWNeYjUbiDusGCj20vteAUGCrRyutHCEWel7TuTGy9KT4E/WAT6DSHfM0/l97rTXE/yTT68HS2dus57edUfAFjA+gEmWIb5ix1Yyf4Bfbt3icPeXfMTfRvtd8gKB9tyr+Iz7OFbPDmuK6LHf9zW88035n+MxF5I+bk0eFwOPwDAAD//wMA4rnczdgEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUyW7jOBD9FYFnUaFEagXmkHYSjJFJ24jdM4O5cSnGRGTSoKikgyD/3qAst51GH+ZGFGt59epVvSPL94A6dH043L6ADQNKUTCTqSBFhUmDC7IlTUeKjrRZTeuqKcl/KEXmHt5Qh2omZS5bwBQqwEyREguqJVYK2pzkihQlizn504C6d8RNpuDFSMjc8Df4wTiLOvSPscq9xtrcZLJ3o8q862Fph8CtjGBubjf329Ua39XV19svX46eAwwxQWYU6pDIc96ossJ5XirMclngtqYUF1UrFNQVz6k6hhkbwFveZ4N6PmNQLlgIsiuygmYEFy36SJHigUfYgg+wfTtEJBNNN9GeTubp2b2jF/CoK9ITocd0V7I3V/tBjKZXV4H7Jwja8z28Ov8ML7xHKTp4dwAfDEz8rDbJGVJOMpIVVUEIStEDlztjIVneJKs+NsypUg0wgRtZtZiVtMKCNAQXrCSVkG2pIGK8B2+hv8j6YKR3g9MhmWlPPtVZOBuMHd04JEsb4MnzcIy74/0AKEXrUfRm2D0CV29b9zjGPzv2sZfF6D3YkKx52CV/8mEXm1C1KmUBoHIqaiI0lI1qykbWqqqaSktWSkLzUjImqRaq5UK0VBas0IJBXaEUrcZwGMOJ/+8XILbe7Pegzgi2E8frngft/H6pwAajTRzNyWMSVd9PXSVzzkVvwIZPJKMOVTlTrCVRTIJjVusWc6Aci7JqWSOoqiBKe+GsNk/jT56I1opRXTZNJVqREyh03TBSa0GU1FS2qhRQFzWpG2haSYgoWd6IVhIFecmiSNfeqVGGX6VASYFS9DjauKHJMrb9aiz+XrEzI2vvgpOujwMzPWzehgD7301t4fYHN5gQ+9fzaL8N8OBs/J1vwMzZHHztwoUzxEVIpt2ThNCa1hoXguWYcU1wW0mGCa1bqrnQTOQoRTdOPoNPosK4sdNQTqJabZJrL3cmgAyjj6X/nbo6zvPutDNnQrKvt9uF83B9OKSz9Y+XNosKXm2SkwA+3ZZvQ/TeuSF2Efx4oaMN9PoE60JNR+Elj6CMBxmmr+0xcObodwq79sFoLsMQ1+AvJydhzEqbXX6tdzGAn9HH4hdDmEUxZ2LNlGe6gJMU/tcB/Pj4+PgBAAD//wMAI6jx6vUFAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTS2/VMBCF/0rkdZz6HSc7aIuoKmhFK0Ds/Jj0Wk3tK8cpoOr+d+Tciwrs2EWjmfF3zpm8oGieAI3ozX5/+QyxLKhFJWwlRpjCRGNG7okeCRup6jSTgvb0G2pRuIafaES9cI66ATAHBVh4IrHlk8Pew0AJ9YRJUXeahwWNL8iEzsNzcNCl5TPkJaSIRvQlRJ++17dN6NycVt/lNMNVXIqJrsJcXN5d39/c4ne9+nj59u2xc4GlLuiCRyOylBrtpcKUSo8FdQwPPeeYqcF66JWh3B/HQiyQo5m7xT++MvhUIhQ3so7xjmA2oEOLvCmmYluzwP3PfSXZbLqo9XYrb5/jC3qGjEbW/jb0uO7MzeHMpacnE/3ZFGJYdqhF+5z2kEuAzZJN5TybElJsTo+czwFiQS26uWtuZ1OmlJ/+8unuKP3qP5R/MG4XIjRXF2hEigovBlLbrcGinwZsgBtspRqEttwrqLGdp1hCXNO6NFexwEPeKNGI3pl5gSPfm+x2oYAra67oX1UdhB+hnCdfCxS16DYnv7rSvNpNSUc6TljdsZb9WppP4EMGV6Bqus/raf0/I0wxQirZmjPE0tyasmvem2VXG3zvpWMAnnLbEzuB1F5L7XqvlFaTE9IRTqUTwvHJ+sFYO3DHBJusgF5V7ppusx0UcZJPWmssnOJYSGrxIBjD2knviVVeTZX+1dbmZq5zhnuvQVisnRqwkFxhSzTBTEiirBukh3o7vy05JS40atE15AjzH5I/BJfTkqbSnKJv/jLh0xrrz9psZ/A9RPxjM/8iuUfITQ3PhFjv8pTX4XA4/AIAAP//AwAiBvh48wMAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTyW7bMBD9FWHOokJSFLXckthBjSB1kBht0RuXcUxEoQyKyoLA/15QdpCmt14IYsB585bhO3j1hNDB+X6/fEYfR8ghurnEKZeENoTTDW06yjsmi4aXrRD8N+TgrvENOqiFMcy0SEqUSISlFdHl1hBrsWWUWcorkTDVwwjdOyhXWHx2Both/IFhdIOHDn46b4eXNFu5wvTDZIsw9LjyY1TeJDKL5f31Zn1Lrmr5fXlxcXw54pgACmehA82YamwlCWOVJYIZTtq6LAmXrbZYS8VKe2xzPmLwqi9G+/jJwQ7RYzQdL3hZUMJbOORgVVSJtlYjbt72icls0yLV87k8X7t3eMYAHc8/DD3CnZnenT2NenK9PTueUY2PSek+DHsM0eHsyyy171V0g89Oky57hz5CDuv77LZXcTuEpy9m3R/1r/5D/iZNX76imSLay2HyEToQHHK4UWbnPGarBXQgmbCipQlIKyLqbUsUloroSrai0aWVmFK9HHx0fhqmMVv5iA9h5g8dXKl+xCPz82B2LqKJU0iifsnUeBsGO5mYfdrPaEGLkiYm6ynup5jdoXUBTcQkbxOmE94/LVxyShOVKQT0MbtVcZd9U+MuPbC1rQxHtKzUNdVbrBrbVI2prZSN3BpRGVqyyghhyq22rdK6LQ0XfKsF1hJywJR2Ni+YsZLVumakaSpORKORtLxGoqjirNZMtiLtxKeP2bpPfaq0tkGhSWNkS0RVSqJpQwkXFZXatJXF1PdhySl80UAO1xg89n9JvnEmDOOwjdlpC7IvJtxNPn3ebN6IF+fJ6+z2YjCPGLKUlnI+7ekpoMPhcPgDAAD//wMA36GF6gMEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUy2rcMBT9FaP1yNHLsuxdmgcNIU1ohrZ0p8d1IuJIgySnDWH+vcgzpc2qdCPE9b1H555z8BsK+hnQiE53u4sXCCWjDSp+LTHCJCYKM7IlaiRspLJVlBCi1He0Qf4aXtGIemEttQNgDhKwcKTDhk8WOwcDJdQR1omKqR8yGt+Q9q2DF2+hjfkLpOxjQCP66oOLP+rb2rd2jotrU5zhKuSig61kzi/ur7e3d/iyl58uPnw4dGbIFaD1Do3IUKqV6ySmtHNYUMvw0HOOmRyMg15qyt1hzIcCKei5ze7pDwcXS4BiR9Yy3hLMBrTfIKeLrrSNzrB93VUmq0zntb5Zy+t1fEMvkNDINr8FPcCd2NmfPGez+NmdHM6i81Nez0nbEtMr2qBdijtIxcOq0WnO8Gzm163OT5eHnnrNFz/BLgXcWVxCQSPiEm3QqtE86+JjaI4Uz2YPoawfS/Ihe/svqIp0e9/czbpMMT2/s+T+oPLVf4h8o+2jD9BcnVdsKpwYSG03Got+GrAGrrHp5CCU4U5CTchZDMWHJS65uQoFHtK6EhrRpZ4zHPidJvvoC9iypLrnN1kH71J0iy3NHyspaUnLCatDS9ktpfkMziewBeoS27Qc8b68H2GSEVKpLClBKM2dLo/NR50fa4PrXWcZgKPc9MRM0CmnOmV7J6WSkxWdJZx2VgjLJ+MGbczALRNsMgL6KjDU5DRrWEnHeqGowsYMBAtKCB5Iz3FvFBAhmenNex2b27nOae6cAmGwsnLAouMSG6IIZqIj0tihc1Bz+VuSYx6EQht0DSnA/NfKN96mmONUmqPXzTsRPi+h/gia1fcfPuCfq9rn0T5Baqpb2oea+aNB+/1+/wsAAP//AwA/mN8hTwQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUzW7dNhCFX0XgWpT5L1K7JHZQ10htxEZTdEdyRrYQXeqCopwGxn33gvIN3HSXHTHgGZ75eMgXkvwByUDeHY9Xz5jKSlpSpr0kmDCUWSrYA7MDE4Owneq16nn/N2nJdIPfyUB6FSOPDqlEg1QB0zTIMVIAdJxxYEKr2tM/rmR4IX7qAJ+niN2y/ol5nZZEBvJlSrB8q2f7qYvzskGXlxmv01p8itXM5dX9zcPtHf3Ymz+u3r9/3bniWht0E5CBGBCSGeQUmBFUKbTUgkFqDSgWOdcC3KtsSgVz8nO3wtc3D7CUhCUOohOyY1Q4cmoJ+OKr7eBXfPh+rE52TJe13u7lfTm8kGfMZBDtD6Cv7S7iPF2U5TjjM85Hn1fMF3E5HHwC0pJjXo6Yy4Q7mn3aefZlWlJzPuzDPGEqpCW3983d7Mu45MNPvO5fEVz/AoFnzIEMRHoFQSFy1C4i98zz4EZtAhjQmhmwUTCFYuSgGQ+9l14xwdFIo9FCIC15nJfg59/XnZ8TLFhgvTACFTqhjHGRobQ46h4ZeGOCsdGBtL1QFqz0znvuAvbRmVhD8snHpylhc31Zp+EKlGNAFQ+eqn501KP0NGjjlA0SDFbNhyWVKW3LtjbXqeBj3vmRgXz084qv5N7l+DQVjGXLFepfpgrv8gJbLM1bAjjrWCeZqKKtHLfSfEaYMsaCFe9D3s79/icRRjBWrWw5YyrNnS9PzW9+faoboAcdBSJwGXoWRtQWrLaxB2OsGaPSkUmuo1JRjgGcD8HJKJQYg8LekJZgDVyzZ1yMVpjYR9rzqKnSY6ROiJ5G5ZkPzslRVSNvHJvbueq8BLCoArXROKq0NDQwy6hQmpkQnQascf6B5Bw+ZUlLbjAnnP8z8qcp5mVdxtKcU9j8BOHzlur/0eyJ/DYl+s9O+3KJXzE39bb8lOpTOV/QqSUH9OuW8bB/P8MLuavvpHnYfyHOVWeYasl98blsx3PZik5o6U6n0+lfAAAA//8DAFrkfDnDBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUXU/dOBCG/0rk6zh1HNtxcteeA1qECghQt9o7xzMuVoN95Dh0EeK/r5xzaJfe9S4Zzcc7z7zJCwnmEclIPh4OZ08Y8kJqkv0W4owryjTl7J7pkfGRD83Ateql+IfUxF/iMxlJL6xt7YC0Q4VUAJN06pylADi0rAXGpSg9zbeFjC/E+AbwyVts4vIF0+JjICP52weIP8ps4xs7xxWaFGe8CEs2wRYx+7O7y/vrG3req6uzT5+OmQsupUHjgYxEAe+YwpYCU5wKgZpqUEi1AsFs20oOw7HMh4wpmLlZ4PsvDRBzwGxH3vCuYZQP5LUmYLIpsiez4P3zoSjZMO1LvN7C2+P4Qp4wkZHXb0CP7T7Y2X+w8fHRBEi4xHnNPoZ3kScEUpNDigdM2eNGaVt8nk1Jrk5zd7PHkElNru+qm9lkF9PjO3SnplfH+RLA9b3g6CahcEIG2DPX6W5iptWub1sjmXOqFziBYL2VAx80ain7fuhFQXV3xHvxB3RPGm6PiyUyks/eprhEl5t9zFeYm93sm90x7dzYHNPz2+vtT0DNjckPu9961eSzsQ8+YHWxL4paAWJgQEU7GSp6N1CDnaGTVIPQUwcKi/F2MWQf1rgu1UXI+C1tUMlIzs284BHnx2QffEab11TQfVWl8CZFWG2ufjmkZQ1rOsZL0ZoPa65uEXxCm7Eguk/rqd9vJVxxxoqUNSUMuSrLVX+Z5aEkQA/SckRou6lnk0OpQUtte1BKK2eFtKxrpRXCdm6CwUzT0FkuuJsE9orUBIshq+0bMI5jy6WjDpyjwihOtW0N5S1zApwUvHXvOFbX81bXAWgUE9VWDVTITtGJaUa5kExNdpCAxe5vSE6OFJrU5BJTwPl/K/88eHWyZvUOwu0ayv+l2lz1wwf670Z7H+13TFW5lvFhc87xQK+vr6//AQAA//8DAME6mqamBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUUW/cKBSF/4rFs3EwYAx+a5tUm426GTWj7WrfLnCdoHrwCOPpVtH89xWeqbLdt76hK869h48DryTCAclA3h2PdyeMeSE1yWErccYVZZpytmd6YHwQrFFScsP136Qm4QG/k4H00rnWGaQCFVLpWUetGB31Hk3LWs94J0tPeF7I8EogNB5PwWEzL39iWsIcyUC+hOjnb2U2hMZN8+qbNE94H5cM0RUzt3dPD/vHHf3Yqz/u3r+/7FxwKQ2a4MlARj0q3VpO21GOVI5aU809o71BPhrhWc/1RRZixhRhahb/9c2Dn3PE7AbecNEwyg0518RDhmLbwoL778fiZMN0W+r1Vt6Wwys5YSIDr38AvbS7cVO4yfNxwhNOR0gLphs3Hw4QPanJMc1HTDnghmY77TRBDnOsrsM+TAFjJjV5fKp2E+RxToefeD1dENz/AoETJksG0ukORNsjSK/R+FF1yE0ndMtQShA99wzGUSrhvGJGjQjcO+lc33LujVCkJs/TbGH6fdn4Gc6sLjMUR4mGS6WMYyg0jl2PzINSVmlnvNA9l9prAQagNRZ7Z5QrIfkE7iVErO5vyUBUK700zFPZWqCyHw0FFEBtp4zUVniFRfNhjjnEdV6X6j5mfE4bPzKQjzAteCH3LrmXkNHlNRWof6ki3KXZry5XbwloWcMawXgRrfm45uoz+pDQZSx492m99vufhCvOWLGypoQxVzvIL9VvsLyUDb73neOIvhW2Z3bETnvdadd7pbQanewcE23npHRitN6AtUY4LvloJfaFMpbAVVvGpWHGcqepEYZRyTRQDb2hXScQmDFC+uL+jWP1OBUdCO81Sku1U4bKTihqmWaUy44p60znscT5B5Jr+GRJywOmiNN/jvwpuDQv85irawqrnyB8XmP5P6otkd9CpP9stG9n9xVTVW4LQixP5XpB55ocEJY14WH7foZXsivvpNpvv5BRjVBC1+QpQ8rr8VpuedvwlrXn8/n8LwAAAP//AwAW3FooxAQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoyTT2/bOBDFv4rAs6hQFEVRujVxgg3S1kHt3S72xj8jh4hCGiSVNgj83QvKTlu3PezNoEdvfvPmzSty8gnQgN7t99fP4FJEJUp2eaKEckwEpmRLxEDo0JBKNIxxIf5DJbJ38IIG1DGta90DboADZoa0WDWjxsZAX5PaENqyrCl3EQ2vSNrKwLPVUPn4D4RovUMD+myd8V9yb2krPfnZVMFPcOtikk5nmNX15m67vsc3Hf94fXl5rIwQs0BlDRrQKEYuakVxPbIRs1EILKghuOuBjn1jSEfF8TPrEgQnpyqaxx8MxicHSQ+0ok1FMO3RoURGJpmxlYywfdlnksWmVX4vl+fl5/CKniGggZZvhh7lLvRkL56imu1kLia/21m3u/JutLs5yJQbl2gf/B5CsrAYtN4UP5hqUpGKckoIKtEHqR+sg+J2VaynPLFsjBHAFBaa95i1DceKCIIpawlXum8NZMg7CA6mn1Q/WB189GMqTr4XZ32uvEvWzX6Oxa1LsDuBDuhGThFywRwCuFTcy/RQ/CXjQyY1nWk1BTB1ozqiRmiFEa3QneFc8FGzVpOmbjVjuhmV6aVSfaMpo6Ni0HFUoi2EJ+vk9N7vdtnL7/3O/1jBKOcpbfwclmRE84hKdGmdDC/Hir8jmFPVx+My3pSWQE3TMlBx2ufVZMGlM3/RgHjNDOuJwaxWErNu7LGERmLV8p4J1RgOOdb3wZtZp19X1hCKSvRpdvmUitu8rC/W4a+c/YL6ExvkXBVLlNu6q7VmFEtGCWaUSNwzSjEXtRTQEMlUjvLK60cIRd6XtO5MbL0p3gX9YBPoNIc85r9L7/WmuJ9kGn14Oju79Zz2cyo+gbEBdIJMsQ1zlrqxE/wG+zb3yUOWcTbHa1ym/Z/HeOVd9H+Q/+PGc+Ln5NHhcDh8AwAA//8DAKH1mMy4BAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUS2/dNhCF/4rAtSjz/dAuiR3UMFIbsZEW3VHkMCaiS11QlNPAuP+9oO4NUnfXnTDgGZ355pCvKLsDoBG9Ox5vXiDXFfWopr3ECFOYGMzIEzEjYSMngyaMc0n+Qj1Kd/ADjUgL76m3gDkowCIQiScePQ4BLCU0ECZF6+m+rmh8RS4NAV6Sh2FZv0BZ05LRiP5IOSzf279dGvy8bGEoywy3ea0u+2bm+ubx7un+AX/U6veb9+/PJ1dYW4MhBTSiaKIydGKYRhGxiMZgwwLB2gKLlgeimTnLUq5QspuHNXz75SEsNUP1IxsYHwhmFp16FFx1zfbkVnj6cWxOdkzXrd7v5f1zfEUvUNDI+p9Az+2u/Jyu1m2a4QXmoysrlCu/HA4uB9SjY1mOUGqCHc0LlGlZU21UvdfOGEqDcVpIHQyXjOvACQRDtAw2WKeEisxLGcNkrBZRBEZFUCDEpFGPdnjz7Gpacnfx/mFOkCvq0f1j9zC7GpdyeIP/8Uz09n8AbbbRiKSRjlMNTgQDNkQlgVnJDSUghOOaBeJiFIr7oIhVERwLXnivKWPBcoV69Mn555Shu71GI1JUBGFJwIJODgsdLXbAHZ6kssJMvA2KevRhyTXlbdnW7jZX+Fr2edGIPrp5hfOk74p/ThV83UqD8KdqwoeyhM3X7lcAKBnIwAlroq0et9p9hpAK+AoNx1PZLv3+I2GKEdKsbKVArt2Dq8/db259bgeCDtIzgED5pMkUQZpgpPE6KGVU9EJ6wqn0Qngep2DdNFnumWBxEqAbFWh5684Rt0IyFSV2QAUWlig8Ea8x8UIpb8ATbt9w7O7npnM8BANiwsYri4XkTWcIZkISNXkrA7Q0/0RyCYto272DkmH+18ifki/LusTaXVLTvYHwecvt+ej2BH1PGf+9075e/DcoXduWS7ndlMuCTj06gFu3Aof99Rlf0UO7Jt3T/ghZNXDFTY8eqyt1O17KlNGBUUJPp9PpHwAAAP//AwACiQ5HwwQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTS0/jMBD+K5HPdfArjpMb0KKtEEsF1e5qb35MqEVwKsfhIdT/vnJaxLK3vVjRxDPzvfyOgn4C1KLz/X71DCGNaIGSn0uMMImJwoxsiWoJazkt65qwmtHfaIH8NbyhFtXCWmobwBwkYOFIhQ3vLHYOGkqoI6wSeaZ+GFH7jrQvHTx7C+Uw/oA4+iGgFv30wQ0vebf2pe2HyZVx6GEdxqSDzWCWq/vr7e0GX9Xy++ri4nhzhDEPKL1DLepUJxU1DNNOdFh0SmHFHMF1A6xruCM1U8c2HxLEoPtydI+fGNyQAiTbspLxkmDWoMMCOZ10hm30CNu3fUYyy7TM9cVcnj/bd/QMEbVs8SHocdyZ7f3Z02gm37uz45n0+JiZ7uOwh5g8zLrMVPteJz+E4rTpsvcQElqg2/ti0+vUDfHpi1j3R/7r/6C/zdtXr2CnBO5ymEJCLco/brTd+QDFeolaJKlwoiEOC2o0FnXXYA1cY1PJRijDnYRs6uUQkg/TMI3FOiR4iDN81KIr3Y9wBH4e7c4nsGmKmdMvmRs3cXCTTcWn+pSUpOSE5aYp7adU3IHzEWyCzG4bp9O8f1qYZIRkKFOMEFKx0WlXfNPjLl9wtassA3CUm5qYDirlVKVs7aRUsrOisoTTygpheWdco41puGWCdUZALdECQTa7mPPlKmC04h2GmtdYOMqx0VWHK9m4ChSlBugXHYvbPvdp7pwCYbCyssGi4hIboghmoiLS2KZykKP0IcnJe5E9uYYYoP+L8o23cRiHLhWnEBRfRLibQn67xRyIFx/w66z2crCPEIvslvYhx/Rk0OFwOPwBAAD//wMAz5D+vAIEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRV227cNhD9FYGvlRTeJFEC8uCs16kR+ALvoin6xsvQy1omFyTl1DX23wtp7WadoA990WXImTlz5gz5grx8BDSgs/1+/QQ+J1Si7BYTxbStsKgo3mIxYDowUncC44azP1CJ3Bd4RgPquNZE91AxaKHiBjeVYlZXxkBPMDGYNnyOKe8TGl6QdLWBJ6ehDuk3iMkFjwb01XkTvs25pav1GCZTxzDCpU9Zej2DOV9vvmxvbquLrr1ef/p03JkgzQFqZ9CArLCtIIpWxHJbcStEJajBVdcDtT0zuKPi6OZ8hujlWCfz8B2DCdlD1gOtKatxRXt0KJGRWc6wlUywfd7PSBaazmd7uZiXz+EFPUFEAy3fCD2G+6BH9+ExqcmN5sPyRCXax7CHmB0sjCxFjqPMLvjiNcdqdOAzKtGn2WUzaQ0poQFdyDHBm3nt752Hiygf4VuID9fHvPX1eotKdBvDn6Dzrcw7NCBmtTBWaSJ5r6wC07e4kdBLyxUX1hhGtNWUWNx2naLcMmGUkm3bNUYpSlGJbjbF7SizDfHxXcM2xx5c/o8WbM5Way/VCObHkrYy3kNGA1KEWC26vueGNr1hILUATlplsBSCAQfDur63RDBGmSBWYCV62QisGuCGoRJdpk2W3sgxeFj/BXqaCUYD2sZpzncl9c55KC7P0YBawg3vsak4UbLine0rCUxWqml7LhQzLcwivpBunCKsZIb7EGf1X0+fYe7TKvjs/BSmVFz6DPdxaedJeTeb4izqncug8xTnRv3eziFvYzCTzsV3HRJc45rhhfMp76dc3IFxEXRe+HqFf7P50YW2FOMZyhQj+FzMrS9+lWnuPzGdaTQFMISpDisLjTCiEbozbStaq3mjMSON5lwzq0wvleqZppxaxaFrUYlgln2xTBontLWNtZViXVNxqk0lW6MrQbGGtmecA33HcHEzzn6SGSOAq0rotq94w9pKYYEryhvcKt03BuR7cZ+UKOq2JqRmDOP+hLfXgeGzrtYxhrgK03yKDad/l96G4qVYhce9GyEWH4uyuEpLns/gIcrx1LR+kuN0HMdT65uE3nlHud8thq1MD8vHxjzcQQrjE8S0GK4hb8xxbVFL8bGgZbG4r3agX1dkdk+wDWHUO+mOOVbBwJmX43Nyx0h38u9wBP91b5f3WdpfLxHL4ibvlsqKAyrRF4gexhOFXDkdQwo2F6+TW7zTzAnl5y7tR/n8E/O/sEY1PVOg3vYv6H8e5LvJzxdIsZwI35yv/lqEfh70A8RiHhTp/HxW/utxNY3ZbXcRpAFzFQz8GPRQokeQaYrwuFxRy6npIS4wzqfjsF36KzeOLoEO3iQ09O0Mm/NXtP+1jRAiaoKFOBwOh38AAAD//wMAez5l7Q8HAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTTW/cIBCG/4rFeXEwYIx9S/OhRlGaqFm1VW98DA2KAyvASaNo/3uFd6s2vfWC0GhmeOd5hzcU1BOgCZ3udhfPEEpGG1T8GqKECkwkpmRL5EToxLp2EF0vB/YdbZC/hlc0oYEb05kRMAMBmFvSY82cwdbC2JHOEtrz2lP9yGh6Q8q3Fp69gTbmL5CyjwFN6KsPNr7Ut5VvzRwX26Y4w1XIRQVTxZxf3F9vb+/w5SA+XXz4cMjMkGuD1ls0ISedkJ2muHPcYe6kxJJagocRqBuZJQOVhzIfCqSg5jbbxz8abCwBiploS1lLMB3RfoOsKqrK1irD9nVXlayYzmt8s4bX6/SGniGhiW5+Az20OzGzP3nKevGzPTmcReXHvJ5OmRLTK9qgXYo7SMXDyug0Z3jS8+tW5cfLQ0695oufYJYC9iwuoaAJ1XFWRPOsio+hOSo8mz2Egjbo9r65m1VxMT29g3x/4Hb1H9hulHnwAZqrczQh0XHLR2Ix77TCfHAjVsAU1r0YudTMCqien8VQfFjikpurUOBHWlWiCV2qOcNB32kyD76AKUuq0r+JWniXol1Maf6Y05GWtIzQWrSU3VKaz2B9AlOgDrFNy7HfPyVUUEKqlCUlCKW5U+Wh+ajyQ02wg+0NBbAd0wPRDnppZS/NYIWQwhneG8K63nBumNN2VFqPzFBOneYwCLRBUHehWddPO6p6xwbcEWswF85g1VGCpVOiBys1Z8M7js3tXOsUs1YC11gaMWLeM4E1kQRT3hOhzdhbqJv2G8nRYl49uYYUYP5r5BtvUszRlebodfMOwucl1K/drL6/+IB/rrTPo3mE1FS3lA91i48G7ff7/S8AAAD//wMAGYtnLCEEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTS2/cIBD+KxbnxQGMMfatTVI1itpESdRWvWFmyKI4sMI4D0X73yu8W7XprTc0mvnmewxvJJhHJAP5sNudP2HIM9mQ7NeSYEJRpqlgd0wPTAwNrzvNOeftT7Ih/hJfyUA6aS23PdIGFVIJrKVj4ywFwJ4zDky0smCa+5kMb8T4GvDJW6zj/A3T7GMgA/nuA8Tnstv42k5xgTrFCS/CnE2whczZ+e3l3dU1/dSpr+cfPx46Z5wLQO2BDMRppzQfBeVOOiqd1lQLYLTrUbi+AdYJfRjzIWMKZqpnePjDAWIOmO0gatHUjIqe7DcETDaF9mhmvHvdFSarTWelvlnL63N4I0+YyCA2vw09wJ3YyZ/Y+PhoApw4H/y8JRuyS3GHKXtcLVlVTpPJPobquOR08hgy2ZCr2+p6MtnF9PjOp9uD9Iv/UP7F2K0PWF2ckYEoLkH2DKjko6Gycz012Bg6tqqXemxAYYntNIbswxKXuboIGe/TypIM5JOZZjzw+5Ds1me0eUmF+g9VBvHF59MIpcDJhlynCIvN1R+7OatZ3TBRMJa8W3J1g+AT2oxF011ajvD/jAglGCvMlpQw5Ora5G312czb0gAdtFYgAm/Gjo0OWw261bYDpbRyVraWNby1UtrGjdCbcewbK6Rwo8ROFd4l3Wo9KN5oyXXHqbXgqIQeae9YSyU40K2UQoF7Z2t1NZU50wBolCPVVvVUto2iI9OMCtkyNdq+BSy389uSY+KyRHSJKeD0l+Qv3qY4R5erY/TVOxNullA+a7WewbMP9GU1/yzaB0xVCc/4UO7ymNd+v9//AgAA//8DAFu9oLHzAwAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyhT/KZuSeyghpHaiBdp0Rs/hjERLbWgKKeBsf+9oLyBm956I4ac4TsPX84LyvYIaELvTqebZ8h1RT2qaQ9RQiUmGlNyIHoidGJ0oJwpQfhfqEfpDn6gCSnu/egNYAYSMA9EYMeixyGAGckYCBW81bRfVzS9IJuGAM/Jw7CsX6CsacloQn+kHJbv7W6bBj8vWxjKMsNtXqvNvom5vnm8O9w/4I9K/n7z/v3ryRXWVmBIAU0oaiV15Ap7KTTmnglseTRYK0lFZFYANa9pKVco2c7DGr69aQhLzVD9RAfKBoKpQeceBVttk+3sCocfp6Zkx3Td4v0e3pfTC3qGgiba/wT6Wu7Kz+lq3dwMzzCfbFmhXPnleLQ5oB6dynKCUhPsaJ6huGVNtVH1XlmtxzFoq7hQQTNBmQqMQNBEiWCCsZLLSL0QMThtFI880JEHCZw7hXq0w5tnW9OSu4v2D3OCXFGP7h+7h9nWuJTjL/gfX4ne/g+gTTaakNDCslGB5UGDCVG2bcH0SIBzyxQNxMbIJfNBEiMjWBo8916NlAbDJOrRJ+ufUobu9hpNSI48cEMC5qOzmKtosAVmsRPScO1YaxT16MOSa8rbsq3dba7wtez9ogl9tPMKr52+K/4pVfB1Kw3Cn7IlPpQlbL52bwYYyUAGRmhL2uppq91nCKmAr9BwHMp2qfefFCopIU3KVgrk2j3Y+tT9ZtendiCoIDwFCCNzirgIQgcttFdBSi2j58ITNgrPuWfRBWOdM8xTTqPjoBoVaH7rdosHDcyNRGNposXceIeNNA7T6AUnI2hhm63eOHb3c8uzLAQN3GHtpcFcMIkd0QRTLoh03ogAzc0/kVzMwjXq0R2UDPO/Wv6UfFnWJdbu4pruFwift9zGR7c76HvK+O+d9vXiv0Hp2mvZlNtPuTzQuUdHsOtW4LhPn+kFPbRv0h32ISTUIAjTPXqsttTtdAlLMQhB2Pl8Pv8DAAD//wMAUcbLucIEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUS2/cNhSF/4rAtShTfFO7JHZQ10g9iAdN0R0fVzYRDTWgqEkDY/57Qc0EqbvLTrjiuTz8eHhfUbIHQAN6dzzenSCVBbWoxK1ECZWYaEzJnuiB0IHRrpdaCS3/Ri2KD/AdDUhx73tvADOQgHkgAjs2ehwCmJ70gVDBa0/7vKDhFdnYBThFD928/Al5iXNCA/oSU5i/1b1t7Pw0r6HL8wT3aSk2+Wrm9u7pYf+4wx+V/OPu/fvLygWW2qCLAQ1o1ErqkSvspdCYeyaw5aPBWkkqRmYFUHORxVQgJzt1S/j600OYS4LiB9pR1hFMDTq3KNhiq21nF9h/P1YnG6bbWm+38vY5vKITZDTQ9gfQS7sbP8WbMh8nOMF0tHmBfOPnw8GmgFp0zPMRcomwodlOO022xDk1180+TBFSQS16fGp2ky3jnA9veD1dENz/AoETZIcGJLSwrFdgedBgwijrb8F0T4BzyxQNxI4jl8wHSYwcwdLgufeqpzQYJlGLnqfZ2en3ZeNnKHE6EEUlBQ6GcimNJ8A0jEIBCVZKJ7U3gWlFuQ6aWWNtbxwob6SvIflk/UtM0NzfogHJngduSMC8dxZzNRpsgVnshDRcOxYkVM2HOZWY1nldmvtU4Dlv/NCAPtppgQu5d9m/xAK+rLlC/UtW4S7PYfWl+ZmAnnSkY4RW0VqOa2k+Q4gZfIGKd5/Xa7//SaikhFQra86QSrOz5aX5zS4vdUFQQXgKEHrmFHEjCB200F4FKbUcPReesF54zj0bXTDWOcM85XR0HFSlDDVwzZZxanrDKCHYjJxibpzERjuFGVHeh970AtQbjs3jVHWWhaCBO6y9NJgLJrEjmmDKBZHOGxGgxvkHkmv4uEYteoCcYPrPkT9Fn+dlHktzTWHzBsLnNdX50WyJ/BYT/mejfTv7r5Cbels2pvpUrhd0btEB7LJmOGzjZ3hFu/pOmv02hYTqBGG6RU/F5rIer2UpOiEIO5/P538BAAD//wMARF4PZcMEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy07cMBT9lcjrODi24zjZwQyoCFEQjNqqOz9uGItgjxyHh9D8e+XMIEp33VlXPsfncf2OvHoC1KPT3e78GXyaUImSW0aUUIGJxJRsiOwJ7Rmrmo4TIdrfqETuCt5Qj1puTG06wAwEYG5JgzUbDLYWuprUltCGZ071MKH+HSlXWXh2Bqow/YA4ueBRj346b8NLflu5yoxhtlUMI1z6KSlvspj1+f3V5uYWX7Ti+/nZ2eHmBFMmqJxFPRpkK+TAW2xEIzE3rMGKDx2WraDNwFQDtDvAnE8QvRqryT5+arAheUimpxVlFcG0Q/sSWZVUlq3VBJu3XVayxLTO83IZL8f+HT1DRD0tPwI90J2Y0Z2Y8PSkvD0ZnHfTFpVoF8MOYnKwRLK4HEeVXPDF8ZHV6MAnVKKb++J2VGkI8elLTvcH65f/4fxama3zUFyuUY9EzS3viMW81grzduiwAqawbkTHpWZWQK5tFXxyfg7zVFz6BA9xUYl6dKHGCQ76TqPZugQmzTFL/yUyEF5dWgWbBwSV6DYGO5tUfMZdk4pUjNDMMafdnIo7sC6CSZA9beJ8pP8HQgUlmXE1xwg+FbcqbYtvatrmC7a1jaEAtma6JXqARlrZSNNaIaQYDG8MYXVjODds0LZTWnfMUE4HzaEVWXdut1gWCqxqKa8VZtBpzBlrsWZCYU25YhIoJZB34DPW4mbMOMWslcA1lkZ0mDdMYE0kwZQ3RGjTNXbBfURybJxLVKIriB7GvyxfOxPDFIZUHKsvvoRwN/v8WYtlDV6cx69L+OtgHiEWuTzlfN7LY1/7/X7/BwAA//8DAEtP7wnzAwAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTW/cIBD9KxZn42DAGPvWZlM1itpEzaqtesPMOIvihRXGaato/3uFd6M0vfWGRrzH+xieiTd7JD15dzhcPaFPMylJcuuIM64o05SzLdM9470QVdsIxYT+QUribvA36Ukrra1th1SgQiqBNXQQo6UA2NWsBsYbmTnNw0z6Z2JcBfjkLFZh/opxdsGTnnxzHsLP/LZxlZ3CAlUME177ORlvs5jN1f3N9vaOfmjV56v37083Z5wzQeWA9EQBF0xhTYEpTqVETTUopFqBZLauGw7dCeZ8wujNVM3w+KoBQvKYbM8rLipGeUeOJQGTTJY9mBm3vw9ZyRrTJs/Ldbwe+2fyhJH0vHwJ9ER3YSd3YcN+bzxcjM67eUdKcojhgDE5XCNZXU6TSS744vzI5eTQJ1KS2/vibjJpDHH/Jqf7k/Xr/3D+ydid81hcbzKmliA7BlTWg6GyHTtqUBg6NKqTehCgMNd2GXxyfgnLXFz7hA9xVUl68sFMM570vYt25xLatMQs/bvKQPzl0mWAPKhJSe5igMWm4jXumlWsEoxnjiUdllR8QXARbcLsaRuXM/0/EK44Y1nZEiP6VNyZtCs+mnmXL0ALjeWIUIuhZcOIjQbdaNuCUlqNVjaWibqxUloxDtCZYeiE5ZKPg8RWZd253WJdKItqHBvZ0tpoSSWIjupOaQq1sFZzrqXJQl5jLW6njDMCQKMcqLaqo7IRig5MM8plw9RguwYw785LJOfGpSYlucHocfrL8idnY5jDmIpz9cWbEL4sPn/WYl2Dn87TX2v4m2AfMRa5PON83stzX8fj8fgHAAD//wMAJODDNvMDAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTyW7bMBD9FYFnUaFIaqFuSeygRpDaiI226I3LKCaikAZFZUHgfy8oO0jTWy8EMeCbecvwHTn5BKhDl4fD8hlcHFGOop1LlNAaE4HLZkeqjrcdF0VZcUoZ+41yZG/hDXWo4VqXWgBmUAPmhlRYsV5jY0CUpDSEVjz1lA8j6t6RtIWBZ6uh8OMPCKP1DnXop3XGv6TZ0hZ68JMpgh9g5cYonU5kFsvt7W69wTdN/X15dXV6OcKYGhTWoA61VUW10AJzISrMdcOx7BuKe6r6UpVNRQScYNZFCE4OxWgePzkYHx1E3dGCsoJgKtAxR0ZGmWgrOcLu7ZCYzDYtUj2fy/O1e0fPEFBH8w9DT+0u9GAvnkY12cFcnM4ox8ek9BD8AUK0MPsySx0GGa132XnS9WDBRZSj9TbbDDL2Pjx9MWt70r/6D/m7NH35CnqKYK795GLCohzdSb23DrLVAnWoLrnhghjMSyUxb3qBJTCJVVUL3ipmakihXnsXrZv8NGYrF+EhzPRRh27kMKZp6212GfTeRtBxCknTrzoBN8GbScfs0/2SFKRghCXQFA9TzO7B2AA6QlK3C9O53z8QWlNCEpUpBHAx28i4z77JcZ8emMZUmgKYkqmGqB6q1rRVqxtT123da15pwspKc65Zr4yQSgmmKae94tDUKEeQws7m/Wr6viW1ajEXjcJcMo6VIBzzsq4kV9JUMiE+fczWQ8KpvjGEEoqFbGvMmWK4ZZRhTihpmGx1XaZV+rDknD1PmdxCcDD8JfnO6uBH38fsvATZFxPuJ5f+bjYvxIt1+HV2e+H1I4QspSWtS2t6Duh4PB7/AAAA//8DAAY6R+UCBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTyU7dMBT9lcjrOHiKHWfHqCJEQYDaqjvHvuFZBPvJcRiE3r9XDg9RuuvOuvI5PsP1GwrmEVCPDrfb0ycIeUY1yn4dMcIkJhpTdUfaXnS90A2VHVdK/UY18hfwinqkhLXUasAcJGDhSIsHPlrsHGhKqCOsFYXT3M+of0PGNw6evIUmzj8gzT4G1KOfPrj4XN42vrFTXFyT4gTnYc4m2CLm5PT24u7qGp8p+f306Oj95gxzIWi8Qz3q2pZZbTUWWrdYWCWwGRXDIxtGOlDVEg3vMB8ypGCmZnYPnxpczAGy7VnDeEMw02hXI2eyKbIHM8Pd67YoWWM6KfN6Ha/H/g09QUI9qz8Cfac7sJM/sPHx0QR3MPrg5w2q0TbFLaTsYY1kdTlNJvsYqv0jx5OHkFGNrm6r68nkMabHLzndvls//w/nl8ZufIDq/AT1SFLhhCYOCzoYLNSosQFu8NBKLbqBOwmltuMYsg9LXObqPGS4T6tK1KMzM82F9Oq2Okx24zPYvKQi/ZcsQHjx+Ti6MiCoRtcpusXm6jNuShrScMILx5K3S65uwPkENkPxdJeWPf0/ECYZKYzHS0oQcnVt8qb6ZuZNueCUay0DcJQPigwjtJ3r2s4qJ2UnRytaSzhtrRCWj4PTZhg0t0ywcRCgZNFd2q3WhQJCmNCK4lGqFgupKDbcScwJjAoYpa4riM9Yq6up4IZROcIIw9p0Egs+cNxxxrEgjChuOitp2Z2PSPaNiw7V6AJSgOkvy5fepjjHMVf76qsvIdwsoXzWal2DZx/wyxr+SbQPkKpSnvGh7OW+r91ut/sDAAD//wMAfxRohPMDAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTS0/jMBD+K5HPdXBsJ3FyA1q0FWKpoNpd7c0eT6hFcCrH4SHU/75yWsSyt73E0cgz8738Trx+QtKS8/1+9Yw+jmRBoptLnPGKsoYW9ZaVrVStbHKlmkpU8jdZEHeNb6QltQQooEEqsEIqLSupER1Qa7EpWGEZL2WaqR9G0r4T7XKLzw4wH8YfGEY3eNKSn87b4SXt1i6HfphsHoYe136M2kMCs1zdX29vN/Sqrr6vLi6ON0cc04DcWdKSqkOjrC5p3TRAJTSGqrqpqdaisx2W6Ty2OR8xeN3no338xGCH6DFCy3MuckZ5Qw4LYnXUCbbRI27f9gnJLNMy1Rdzef5t38kzBtLyxYegx3Fn0Luzp9FMrrdnx2/U42Niug/DHkN0OOsyU+17Hd3gs9Omy96hj2RBbu+zTa9jN4SnL2LdH/mv/4P+Nm1fvSJMEe3lMPlIWqLIgtxo2DmP2XqZhhXSyoZZKgujqay7hmoUmpqyaqQywlaYTL0cfHR+GqYxW/uID2GGT1pypfsRj8DPA+xcRIhTSJx+ValxEwY7Qcw+1S9YznLBRGqa4n6K2R1aFxAiJnbbMJ3m/dPCK85YgjKFgD5mGx132Tc97tIFW9sSOKIthKmZ6bBUVpUKaltVqupAlsBEUYKUIDpjG21MI4BL3hmJdUUWBJPZ2ZwvbQ2zrKip6rim0uqCKgBGrapBAGCBUHzRMbvtU5/pass447TRqqJSGEGV4IJKxlkttIKqSFH6kOTkvUyeXGPw2P9F+cZBGMahi9kpBNkXEe4mn95uNgfixXn6Oqu9HOARQ5bc0s6nmJ4MOhwOhz8AAAD//wMACwdnnQIEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTS2/cIBSF/4rF2jgYsA3e5alGUZooGbVVdzyuExQHRoDzUDT/vcIzVZvuusHoinM5fOf6A3n1DGhEx9vt+Qv4nFCNsltLlNAeE4nbYUO6kYuRy0aInhJJfqIauSt4RyMauDGtkYAZ9IC5JR3WbDLYWpAtaS2hHS891UNC4wdSrrHw4gw0IX2DmFzwaETfnbfhtdytXGPmsNgmhhkufcrKm2Lm7Pz+anNziy+G/uv5ycn+ZIJUGjTOohH1E2hhVYcHKQ3mRmosBjlgpdhkJ+jKdy9zPkP0am6SffrjwYbsIZuRNpQ1BFOJdjWyKqtiW6sEm/dtcbJiOiv1ei2v2/EDvUBEI61/A923OzKzO3pOenGzPdqvWaWntK6TMjnEd1SjbQxbiNnByug4JXjW8/tGpaeL/ZmyTedvYJYM9jQsPqMRCVSjFdE8q+yCrw4OT2cHPqMa3dxXt7PKU4jPnyDf77ld/ge2a2UenYfq8qxoWm65JBbzVivMh0liBUxh3fWSC81sDyXz0+Cz80tYUnXpMzzE1SUa0YWaE+z9HUfz6DKYvMRi/UdfhLcx2MXk6k84LWlIwwgroiVvl1zdgXURTIbyiE1cDv3+kdCeElKsLDGCz9Wtyo/VF5UeywE72M5QANsyPRA9QSes6IQZbN+LfjK8M4S1neHcsElbqbSWzFBOJ81h6FGNoMxCtY6fsUQTbQQeqLGYc9liwcSAmSJaU2HEpOUnjtXNXHR6GiyhhGKpRI850wwLRhnmhJKBKWH6tkzabySHiHnJ/gqih/mvJ187E0MKU64OWVefINwtvvza1Zr7q/P4baV9FswTxKqkpZwvU3wIaLfb7X4BAAD//wMA4dUdUiEEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyhTJMUP3ZLYQV0j9SJeNEVvQ3JkC9FSC4raNDD2vxfUbuCmt5xIDPgOZx6+nFcS4YCkJ++Ox7sTxryQmuRxC3HGFWWWtnrPul6aXtpGWGN5K/8mNRkf8DvpiZbet94iFaiQysA66sTgaQhoW9YGxjtZcsLzQvpXAmMT8DR6bOblT0zLOEfSky9jDPO3cjeMjZ/mNTRpnvA+LhmiL8Xc3j097B939KNWf9y9f385ueBSEjRjID1RAzoToKPaWk+lt44abTUFEEMYsCvrRTbGjCnC1Czh61sNYc4Rs+95w0XDKLfkXJMAGUrZDhbcfz+WSjZMtyVeb+Ft27+SEybS8/oH0Eu6Gz+NN3k+TnjC6QhpwXTj58MBYiA1Oab5iCmPuKHZup0myOMcq+tlH6YRYyY1eXyqdhPkYU6Hn3g9XRDc/wKBEyZHetKZDkSrEWQwaMOgOuS2E6ZlKCUIzQODYZBK+KCYVQMCD156r1vOgxWK1OR5mh1Mvy8bP8uZM4FprjhKtFwqZT1DYXDoNLIASjllvA3CaC5NMAIsQGsdam+VLyb5BP5ljFjd35ZuWhmkZYHK1gGVerAUUAB1nbLSOBEUFs2HOeYxrvO6VPcx43Pa+JGefIRpwQu5d8m/jBl9XlOB+pcqwl2aw+pz9eaAljWsEUwU0ZqPa64+YxgT+owF7z6t13z/k3DFGSulrClhzNUO8kv1Gywv5UDQofMcMbTCaeYG7EwwnfE6KGXU4GXnmWg7L6UXgwsWnLPCc8kHJ1EXylgMV20ed8xLaNFT7V1HJVhDQYKhxjDDW9VxZouv3jhWj9OmG3RgnHFqwSgqhRPUCC6oZJxpAcarttj5B5Kr+aQhNXnAFHH6T8ufRp/mZR5ydXVh9ROEz2ss86PaHPltjPSfjfbt7L9iqsprwRjLV7k+0LkmB4RlTXjYxk//Snbln1T7bQpx1TBjTE2eMqS8Hq/hrmsMk+fz+fwvAAAA//8DALVuXjbCBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRU207kOBD9lcivGwffEtt5A7rRIsRF063d1b75UgEvaaflODAI9b+vkoYBZrQP+xJHJVfVqXOO6xVFswPUotP9fv0EMY+oRDksIUZYg4nGVG5J3QrVCl0pzZlk9d+oROEKXlCLpHCOOg2YQwNYeFJjyzuHvQdNCfWE1WKuae5H1L4iEyoPT8FBNYx/QBrDEFGL/gzRD89zbxMq1w+Tr9LQw2Ucs4luBrNab662t3f4QjY367Oz480RxrlAFTxqUdOBVd7UWGrtsHDaYiW1xMbwzndQz+cxLcQMKZq+Gv3jBwY/5AjZtaxivCKYaXQokTfZzLCtGWH7sp+RLDSt5ni5hJff9hU9QUItK98JPZY7cX042Y12Cr0/Wb6oRPs07CHlAAsjy5B9b3IYYvHW47wPEDMq0dmcspmcg3FELdqmCd6j63gfIlwks4PnIT3eHNtWN+stKtFdGv4Bl+9MfkAt4p1TvrOOGqFtZ8HrhtQGtOmEFarznlPXOUY70khpmei48taappG1t5YxVKLbTXHXm9wNafdFr81Rgsv/ocDm9Hwdje1hzrkw/fhjpK1J95BRiyylnVNSa+FZrT0H4xQI2lhPjFIcBHgute6o4pxxRTtFrNKmVsTWIDxHJbocN9lEb/ohwvo7uGnm94PCa+MeQoTicjUDp8ILTTwW1BosZKexAW6wrRstlOW+gdnD50PMIU7DNBaXMcN9WjT7NMTtpjhN7iFkcHlKsxx/NXPiXRr85HLxYTZKKlJxMgO9nfJ+ysU38CGBywsrbyBvNz+nsIYRMkOZUoKYi1ng4nczzipTL33tGICn3EpiO6iVV7Vy0jeNajonakc4rZ0QjnfWa2Ot5o4J1lkBskElgtnbxfKcrGkceEewB6OwUKrBhmuPQVsKXBkpmPjCY3HbL3md9IQRhrVRDRbccqw441gQRiQ3yjXUfLXwpxFV1VRUVFyqhZp33t5ehVCoRFeQIvSfeLkOLg3j0OXizZXFF6Y+NVqFcd+bl1/6/QaSk456K9/vnz+Ae/zVpN+mOO/GYnH7c4j4+yLvanCPkIrZHibEeQ38yLie+hy2DwmMB389ePi56KFEOzDjlGC3bN9lIURIC4zVdLTYZbwOfR9GcEP0I2oFUxUlgr6h/c9rmlVSE3U4HA7/AgAA//8DADlZ+rXpBQAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxTTW/bOBD9K8KcRYUSqQ/q1sQJNkhbB7V3u9gbP0YOEYU0KCptEPi/F5Sdtm572JOE0ejNm/fevIKTTwg9vNvvr5/RxQlyiHYpVbRqCBWkbLe07nnXc1Hwpu2qlv4HOdg7fIEeWq51qQUShg0SbmhNFBs0MQZFSUtDq5onTLmboH8FaQuDz1Zj4ad/MEzWO+jhs3XGf0mzpS306GdTBD/irZuidDqRWV1v7rbre3LTNh+vLy+PnRNOCaCwBnpoBlSdkTVphdCEa6FI14qWSMkGM2CdnsffrIsYnByLyTz+4GB8dBh1XxUVKyipBBxyMDLKRFvJCbcv+8RkkWmV6vlSXl77V3jGAH2Vvwl6hLvQo714mtRsR3Mx+t3Out2Vd4PdzUHGNDiHffB7DNHiItB6k/3gVNKCFlVTUQo5fJD6wTrMblfZekwbq6E1tKIVEbJrCGeKkY5VjHBa0ZbJTjdlInmHweH4E+oHq4Of/BCzk+7Z2Zwr76J1s5+n7NZF3J2I9nAjxwlTwxwCupjdy/iQ/SWnh8TUtKbWFaIpmWqpGrDuTFd3ujVN0zWD5rWmrKw155oNygiplGC64tWgOLYN5LDF8GSdHN/73S5p+X3e+YcVDnIe48bPYUnGZB4hh0vrZHg5dvw9oTl1fTya8Ya0BGocl4Wyk59Xo0UXz/RNaSq54YIawkslCW8HQSQySVTdCN4pZhpMsb4P3sw6/moZowxy+DS7dErZbTLri3Xka8N/ofoTN0y5ypYoCymVarQgjFEkvCkrIriQhCGTkg+a8UFADiuvHzFkyS9p3RnYepO9C/rBRtRxDmnNf5fZ6012P8o4+PB0dnbrOe7nmH1CYwPqiInFNswJ6saO+BvZt71PGvIOctgcr3HZ9n8e45V3k/8D/B8dhx7kHD0cDofDNwAAAP//AwDB5+uJuAQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy07cMBT9lcjrODi248TZwQyoCFEQjNqqOz9uGItgjxyHh9D8e+XMIEp3XcW68jk+j5t35NUToB6d7nbnz+DThEqU3DKihApMJK7bDWl63vVcVpIwSrr2NyqRu4I31KOWG1MbCZiBAMwtabBmg8HWgqxJbQlteOZUDxPq35FylYVnZ6AK0w+Ikwse9ein8za85LeVq8wYZlvFMMKln5LyJotZn99fbW5u8UUrvp+fnR1uTjBlgspZ1CMxgO6sanArpcHcSI27VrZYKTbYAZr8PcCcTxC9GqvJPn5qsCF5SKanFWUVwVSifYmsSirL1mqCzdsuK1liWud5uYyXY/+OniGinpYfgR7oTszoTkx4elLengzOu2mLSrSLYQcxOVgiWVyOo0ou+OL4yGp04BMq0c19cTuqNIT49CWn+4P1y/9wfq3M1nkoLtcZU3PLJbGY11ph3g4SK2AK60ZI3mlmBeTaVsEn5+cwT8WlT/AQF5WoRxdqnOCg7zSarUtg0hyz9F8iA+HVpVWweUBQiW5jsLNJxWfcNalIxQjLHHPazam4A+simATZ0ybOR/p/IFRQkhlXc4zgU3Gr0rb4pqZtvmBb2xgKYGumW6IHaDrbNZ1prRCdGAxvDGF1Yzg3bNBWKq0lM5TTQXNoRdad2y2WhSINlxKswMKQBnMuNdaSMtxo0tq2YVTorP4z1uJmzDg9tJZQQrFUncCcaYY7RhnmhJKWqc6IOu/ORyTHxnmHSnQF0cP4l+VrZ2KYwpCKY/XFlxDuZp9/1mJZgxfn8esS/jqYR4hFLk85n/fy2Nd+v9//AQAA//8DAB8jFwPzAwAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUS2/dNhCF/4rAtSjzJT60S2IHdY3URmw0RXdDcmQL0aUuKMppYNz/XlC+gZvusiMGPMPDj4fzQhIckAzk3fF49YyprKQlZdpLgglNmaPcPLB+UHboWccsF9rwv0lLphv8TgZiVAg8OKQSNVIVWU+9HAONER1nPDLRq9oTHlcyvBCYuojPU8BuWf/EvE5LIgP5MqW4fKtnw9SFedlil5cZr9NaIIVq5vLq/ubh9o5+NPqPq/fvX3euuNYG3RTJQKKzIVghqTGipwowUDcCUAAZIcKoneOvsikVzAnmbo1f3zzEpSQsYRCdkB2jwpFTSyIUqLY9rPjw/Vid7Jgua73dy/tyeCHPmMkg2h9AX9tdhHm6KMtxxmecj5BXzBdhORwgRdKSY16OmMuEO5r9tvMMZVpScz7swzxhKqQlt/fN3QxlXPLhJ173rwiuf4HAM2ZPBiJZL7UxqKSU4LQXDKPRQWvhAzAzeoE8IOudMo4bxqUTzMleG2siMBdISx7nxcP8+7rzc4J5G5kRWqBCJ5TWLjCUFsfeIIugtdc2uCitEcpGK8EBcOfRBKdDDcknCE9Twub6kgxEcxWVY5Eq7oEqMzoKKIH6XjtlvYwaq+bDksqUtmVbm+tU8DHv/MhAPsK84iu5dzk8TQVD2XKF+peuwru8xC2U5i0BnHWsk0xW0VaOW2k+Y5wyhoIV70Pezv3+JxFaMFatbDljKs0dlKfmN1if6oZoYh8EYuTSG+ZH7G20vQ0mam31GFQfmOR9UCrI0UcH3jsZhBKjV2g0aQnWwDV7xsMYjRo9Uo9CUqUsp1b3hkouNToEiLuRN47N7Vx1fjSRCSaoA6upkl5SK2sHJpiRYIPmNc4/kJzDpyxpyQ3mhPN/rvxpCnlZl7E05xQ2P0H4vKU6P5o9kd+mRP/ZaV8u4Svmpr4WTKl+lfMDnVpyQFi3jId9/Awv5K7+k+Zhn0JCdZpz05L7Arlsx3NZ6c4xrU6n0+lfAAAA//8DAMJGy6DDBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUy27dNhCGX0XgWpR5v2iXxA5qGKmN+CAtuuNlFBPRoQ4oymlgnHcvKJ/ATXfdEQPO8J9vfs4Lyu4IaETvTqebZ8h1RT2qaQ8xwhQmFlN9IHIUZpRkoMRYQsVfqEfpDn6gEWkRAg0WMAcFWEQisedTwDGCpYRGwqRoNd3XFY0vyKUhwnMKMCzrFyhrWjIa0R8px+V7e9ulIczLFoeyzHCb1+pyaGKubx7vDvcP+KNWv9+8f/96c4W1FRhSRCOK1oRgGMdaM4mFg4Dt5Bx2jkcX3aSspa9pKVco2c3DGr+9aYhLzVDDyAbGB4KZReceRVddk+3dCocfp6Zkx3Td4v0e3o/jC3qGgkbW/wT6Wu4qzOlq3fwMzzCfXFmhXIXleHQ5oh6dynKCUhPsaJ6h+GVNtVENQTtjKI3GaSF1NFwyriMnEA3RMtponRJqYkHKKXpjtZhEZFREBUJ4jXq0w5tnV9OSu4v2D3OCXFGP7h+7h9nVaSnHX/A/vhK9/R9Am2w0Ik4kV1qD4Jw7qzwjELUKSjEfHNGTZ0ADEGmFtlQTyi0jlkuljY6O2IB69MmFp5Shu71GI1JURGFJxIJ6h4WeLHbAHfZSWWE8b42iHn1Yck15W7a1u80Vvpa9XzSij25e4bXTdyU8pQqhbqVB+FO1xIeyxC3U7s0AlAxk4IS3pK2ettp9hpgKhAoNx6Fsl3r/SWGKEdKkbKVArt2Dq0/db259aheijjIwgEi518RPIE000gQdlTJqCkIGwqkMQgQ++Wid95YHJtjkBWiFegTNb91u8TBJyi112BhtsIheYi8Nw0QqYo0xOoo2kTeO3f3c8vykI2GEYeuMwoJ7jg1nHAvCiObOBEWbm38iuZhFGNSjOygZ5n+1/CmFsqzLVLuLa7pfIHzeclsf3e6g7ynjv3fa10v4BqVr03Ipt59yGdC5R0dw61bguG+f8QU9tG/SHfYlxMSgKNU9eqyu1O10CQs1WKLE+Xw+/wMAAP//AwC8ws6OwgQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoyUTW/bOBCG/4owZ1GhSOrz1sQJNsi2Tmu3u9gbRY4cIgppkFS6QeD/vqDsbOp2D3sTRqN3nnlnRq9g5RNCDx/2++tntDFADtEsIUZZTWhHymZLq160fUWLsmmrqhJ/QQ7mDl+gh0YoVaoOCccaidC0IgMfFdEau5KWmrJKJE25C9C/gjSFxmejsHDhG/pgnIUe/jBWu++ptjSFmtysC+8mvLUhSqsSzOp6c7dd35Obpv50fXl5zAwYkkBhNPSgu1aplnHSNKwiQqIi3SglkZJrqeVYd115/MzYiN7KqQj68Z1Bu2gxqp4VjBeUsA4OOWgZZcIeZMDtyz6RLDatUjxfwstj/wrP6KFn+ZuhR7kLNZmLpzDMZtIXk9vtjN1dOTua3exlTIVz2Hu3Rx8NLgatN9k7U0kLWrCaUQo5fJTqwVjMblfZekodD2OjKaOMdLKtieADJy1nnAjKaMNlq+oyQd6htzj9oPrRKO+CG2N28j07q3PlbDR2dnPIbm3E3Qm0hxs5BUwJs/doY3Yv40P2mwwPiVQ3ulIMUZd8aOgwYtXqtmpVo+u6rUclKkV5WSkhFB8H3clh6Lhigo2DwKaGHLbon4yV0+9ut0te/lvv/MUKRzlPceNmv2xG0I+Qw6Wx0r8cM74G1KesT8dhvCktCzVNS0PZaZ5Xk0Ebz/yFHupSaNFRTUQ5SCKasSMSuSRDVXeiHbiuMa31vXd6VvHnkXHKIYcvs02nlN2mYX03lvxdi59Qf2DDtFfZssp1Q5EPoyZIO0GEGJFIHCnRVOMoq6qsaAc5rJx6RJ+leUljz8TWm+yDVw8mooqzT23+udReb7L7ScbR+aezs1vPcT/H7Atq41FFTBRbPyepGzPhL7BvfZ88FO1xb4J7y/2GfnDBxPSL+DwbTAZvjue62PE/r/VM853pPzcCepBzdHA4HA7/AAAA//8DAL7u70zYBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUy27jOBD8FYFnUaFI6mVgDxknwQbZjI3Ys7vYGx/NmIhMGhSVTBDk3weU5YkzmMPeCLIfVdXVfENO7AEt0OXhcP0MLg4oR9FOV5TQGpMOl82WVAveLipS1C1rSMn/Qzmyd/CKFqjhSpWqA8ygBsw1qbBkRmGtoStJqQmteKopHge0eEPCFhqerYLCD39DGKx3aIH+sU77l9Rb2EL1ftRF8D3cuiEKpxKYq+vN3Xa1xjdN/fX6y5dj5ABDKlBYjRZId61SLWW4aWiFuQCFOyMEFoJpoYWpu648plkXITjRF4N++sCgfXQQ1YIWlBUE0w6950iLKBJsKQbYvh4Skkmmq3SfT9fTcfGGniGgBc1Pgh7LXajeXuwHOdpeX0QRHiGaIPbw4sMTPIse5egQ/AFCtDDps9pkH5BKUpCC1pQQlKN7oXbWQXZ7la36RFiaRhNKKO5EW2POJMMtowxzQknDRKvqMmG8g+CgP6t6b1Xwgzcxm2XPPvVZehetG/04ZLcuwmMQ8Zh3I/oBUI7Wo+ztsHsAoV+3/mFMb27sE5flGAK4mK1F3GV/imGXSOhGV4oC6JLJhkgDVavbqlWNruu2NopXirCyUpwrZqTuhJQdU5RTIzk0NcrRaoyHMZ70/34GYhvsfg/6A8F20njdi2h82N9qcNEam0ZziphM1fcTq2yuuewtuPhJZLRAdck174jGvJQC88Z0WAATWFZ1x1vJdA3J2kvvjH0cf+pEjNGcmapta9nJkgA1TctJYyTRyjDV6UpCQxvStNB2ihBZ8bKVnSIayorrxC54Par4qxUYYShHD6NLG5rdJtov1uHvNf9QZB189Mr3aWC2h83rEGH/u6kt/f7gBxsTfzOP9tsA996l1/kPmDWbky99PAuGtAjZtHtJFg2c4k7VDHNtGiwp0biuW97UXDDQHcrRlVdPELLkMGHdNJSTqVab7DKonY2g4hhS638nVsd53px25kOQ4uv1dukDXB4O+Xz7x3NXJAevNtnJAJ/+lm9Dit75IbGIYTzz0QZ6c4J15qaj8bIH0DaAitPT9pg4a/Q7h12GaI1QcUhr8JdXkzFmp80hv/Y7G8DP7GPzsyHMppgr8XaqM/2AkxX+1wf4/v7+/gMAAP//AwCxdSWE9QUAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTU/cMBD9K9Gc4+DYjvNxoyxVEWpBsGqr3ib2pFgEZ+U4fAjtf6+cXUTprRfLGvnNvPfm+RU8PhB0cLrbnT+SjzPkEN1aElxoxltW1ltedarpKl00Uopa6l+Qg7ukF+igVsaUpiUmSRNTllesl4Nh1lJb8tJyUanUE3/P0L0CusLSozNUTPN3CrObPHTww3k7PaXZ6AozTostwjTShZ8jepPIbM5vL7dX1+xzrb+df/p0eDnTnBoUzkIHtm2MaYRkdS0qppAMawdEhigtWhx025YHmPORgsexmO39Owc7RU/RdKIQsuBMtLDPwWLERLvHmbYvu8RktWmT6vlaXq/dKzxSgE7kb4Ye2p2Y0Z08zP3iRntyOCPO90npLkw7CtHR6ssqdRwxuslnx0lnoyMfIYer2+x6xDhM4eGDWbcH/Rf/IX+bpp8/k1ki2bNp8RE6UAJy+IrmznnKLjbQgS6VVS23TJU9MlUPLUOSyPpKt6rppdWUtno2+ej8Mi1zduEj/Q4rf+jgM44zHZifBnPnIpm4hCTqp07A6zDZxcTs3f6SF7yQXCbQEndLzG7IukAmUpK3Dcux3z8QoQXnicoSAvmYXWO8y77gfJce2NpWRhDZUvY17weqGttUjamt1o0ejKoMl2VllDJy6G2Lfd9KI5QYekW1hhwobTtbA9YS15wImUbNmdKVYNhzYkZz5FXbN7ZMmXj3MbsaE64fassFF6zFRjMle8kaKSRTXPBaYmP0inuz5Lh81UAOlxQ8jX9J/upMmOZpiNkxBdkHE24Wnz5vtibiyXn2vLq9mcw9hSxtC51POT0uaL/f7/8AAAD//wMAd4cViwMEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTXW/bIBT9KxbPwcWAMfitbVotqrpGbbRNe8Nw3aA6EGHcD1X57xNOqq5725t1xTn3fFy/I693gFp0vt9fPYNPI1qg5OYRJVRgonDVbEjdctnWopSMM0bVb7RA7gbeUIsabkxlFGAGAjC3pMYd6w22FlRFKktozTOnfhxR+460Ky08OwNlGH9AHF3wqEU/nbfhJe/WrjRDmGwZwwArPybtTRazvHq42dyt8XUjvl9dXBxfjjBmgtJZ1CKrpDGSMtw0tMZcg8Gq1xprzay2uhdKVUeY8wmi10M52qdPDTYkD8m0tKSsJJgqdFggq5POsjs9wuZtn5XMMS3zfDGP58/2HT1DRC1dfAR6pDszgzszYbfT3p71zrtxixZoH8MeYnIwRzK7HAadXPDFacnl4MAntEB3D8V60KkPcfclp4ej9dV/OL/VZus8FKslapGouOWKWMyrTmPe9AprYBp3tVBcdswKyLVdBp+cn8I0Fiuf4DHOKlGLrvUwwlHfeTRbl8CkKWbpv0QGwqtLl8HmQd69jsFOJhWfcVekJCUjLHNMaT+l4h6si2ASZE+bOJ3o/4FQQQnJyqYYwadirdO2+KbHbX5gG1sbCmAr1jWk66GWVtbSNFYIKXrDa0NYVRvODes7q3TXKWYop33HoRFZd263mA+qMY1QnQbMOasxZ12HOwUWV5KQCqSESvdfYi3uhozr+sYSSihWWoqMY1gyyjAnlDRMSyOqfDsfkZwa5xIt0A1ED8Nflm+diWEMfSpO1RdfQriffP5Zi/kMXpzHr3P4y2CeIBa5PO18vstTX4fD4fAHAAD//wMA/WXCnPMDAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUy27kIBD8FYvz4GDA2PiWpzaKsok2o+xqbzzaCYoDI8B5KJp/X+GZKDun1V4QancX1VUlfyCvngEN6HizOX8BnxNaoeyWEiVUYCJx061JO/B+aEXdU0lb2v1GK+Su4B0NqOPGNEYCZiAAc0tarNlosLUgG9JYQlteMNVDQsMHUq628OIM1CHdQ0wueDSgn87b8FreVq42U5htHcMElz5l5U0hc3Z+d7W+ucUXnfh+fnKy60yQCkDtLBqQlb0xPWW462iLuQKD5agUVopZZdUopGx2Y85niF5NdbJPXxxsyB6yGWhNWU0wlWi7QlZlVWhrlWD9vilMFpnOSn21lJfr8IFeIKKBrj4F3cEdmckdPSc9u8ke7c6s0lNazlGZHOI7WqFNDBuI2cGi0XFK8Kyn97VKTxe7nnJN529g5gz2NMw+owExgVZo0WiaVHbBV3uKp5MDn5ePOTqfnPkXVEG6uatuJ5XHEJ8PLLnbqXz5HyJfK/PoPFSXZwW74ZZLYjFvtMK8GyVWwBTWrZC818wKKAk5DT47P4c5VZc+w0NcVkIDulBTgh2/42geXQaT51j2/CXK4G0Mdja5+rKyITWpGWFlaM6bOVc/wLoIJkNZYh3nPd794QgVlJBCZY4RfK5uVX6svqn0WBpsZ1tDAWzDdEf0CG1v+7Y3nRWiF6PhrSGsaQ3nho3aSqW1ZIZyOmoOXREYSnKqJaySso522mA7tgJzYhusjBW4oVJT23R2pPRAx+pmKnN67CyhhGKpeoE50wz3jDLMCSUdU70RTcnlpyT7PPAerdAVRA/TXytfOxNDCmOu9l5XByL8mH35EVSL76/O47dF7bNgniBWxS3lfMn83qDtdrv9AwAA//8DAJ9uh65PBAAA
|
||||
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRVTW/jNhD9KwKvlbQSKZGSgD1kHWcbLPKB2OgWvQ3JYcxGIQ2KyjYN/N8LyknX2UUPvdjSSDPz5r031Atx8IhkIGf7/foJXZxITqJdQrSivKj6ohbbqh2abmh52bGu5XX/B8mJ/YLPZCCiUapWPRYMORaNrtpCMqMKrbGvq1pXtG1STbifyPBCwJYan6zC0k+/YZisd2QgX63T/lvqDbZUo591GfyIl26K4FQCc77efNne3BYXgl+vP306vjnhlAqUVpOB6L5TqqOsEIK2RQOoit4AFABMgwbD+74+plkXMTgYy0k/fMegfXQY1UBLysqqoD055ERDhARbwoTb531CstB0nuL5El4uhxfyhIEMNH8j9Fjugxrth8dJznbUH5ZfkpN98HsM0eLCyDLkOEK03mWvPVajRRdJTj6llM2sFE4TGcgFjBO+hdfu3jq8CPCI33x4uD72La/XW5KT2+D/RBVvIe7IQJhRnTZS1dD00kjUPa9awB5MI5vOaM1qZRStTcWFkLQxrNNSAuei1VJSSnJys8luR4jGh8d3gm2OGlz+Dwk2Z6u1Azmi/nGkLYR7jGQgLW8lMtNRLjlo6FlnakDWayUZb7GTQncoBKMMVKuqrla1qFvoRdsKzmjqcjltIjgNo3e4/gvVnAgmA9mGOfW7ArWzDrPLczIQXje66StdNLWEohGmLwAZFLLlfdNJpjkmE1+AHeeAK4h470Ny//X8GZNOK++idbOfp+zSRbwPi5wn491ssrOgdjaiinNIQv3OU8nb4PWsYvbdh3VVViWrWEqa436O2R1qG1DFha9X+DebH1Mop1WVoMwhoItZkj77Faakf62FbhVF1DWTopIG2053baeE5rzjRjWtqljdqqZRzEjdg5Q9U7ShRjYoOMkJJttny6Y1XS8qo6DoIFHGelOAkrLg0vTQiKpjlXjHcHYzpjxphK5oRYseOl40TLKiY5QVTUUrwaBTvIb35j4ZsSt5WTclE91CzRtvrwvTdCQn6xB8WPk5nWLD6d2lMz57yVb+cW9HDNnHLM+upqXPZ3QYYDwNrZ9gnI/reBp9s9C77AD73RLYwvSwXGz0wx1OfnzCMC2Ba4wbfXy2uCX7mNV5tqSvdqhen0C0T7j1flQ7sMceK6/xzMH4PNljpTv42x/Bf92b5f9s2l8vFfPsJu6WybIDyckXDA7HE4dcWRX85E3MXjc3e+eZE8rP7bQf4fkn5n9BwSpTa5mk/Y7+50W+m136gGTLifDNuuKvxejnXj1gyNKigHXprPw342oeo93uAoJGfeU1/lj0kJNHhGkO+Lh8opZT02FYYJzPx2W7dFd2HO2Eyjs9kYFz3pSM0/YV7n++JxpaVqzmh8Ph8A8AAAD//wMADw4oQRAHAAA=
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Microsoft.NET.Workload.Emscripten.Current": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net6": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net7": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net8": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net9": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Sdk.Android": "36.1.69/10.0.100",
|
||||
"Microsoft.NET.Sdk.iOS": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Sdk.MacCatalyst": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Sdk.macOS": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Sdk.Maui": "10.0.20/10.0.100",
|
||||
"Microsoft.NET.Sdk.tvOS": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.Current": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net6": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net7": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net8": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net9": "10.0.111/10.0.100"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
10.0.300
|
||||
+1
@@ -0,0 +1 @@
|
||||
10.0.303.1
|
||||
@@ -16,9 +16,16 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<Uom> Uoms { get; set; }
|
||||
public DbSet<UomConversion> UomConversions { get; set; }
|
||||
public DbSet<Item> Items { get; set; }
|
||||
public DbSet<ItemBatch> ItemBatches { get; set; }
|
||||
public DbSet<ItemSerialNumber> ItemSerialNumbers { get; set; }
|
||||
public DbSet<SaleRoute> SaleRoutes { get; set; }
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<OrderItem> OrderItems { get; set; }
|
||||
public DbSet<ShopPO> ShopPOs { get; set; }
|
||||
public DbSet<ShopPOItem> ShopPOItems { get; set; }
|
||||
public DbSet<Invoice> Invoices { get; set; }
|
||||
public DbSet<InvoiceItem> InvoiceItems { get; set; }
|
||||
public DbSet<InvoiceVersion> InvoiceVersions { get; set; }
|
||||
public DbSet<InvoiceVersionItem> InvoiceVersionItems { get; set; }
|
||||
public DbSet<LoadingSheetOrder> LoadingSheetOrders { get; set; }
|
||||
public DbSet<DeliveryRun> DeliveryRuns { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<Role> Roles { get; set; }
|
||||
@@ -27,11 +34,58 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<UserRegionScope> UserRegionScopes { get; set; }
|
||||
public DbSet<UserRouteScope> UserRouteScopes { get; set; }
|
||||
public DbSet<UserStoreScope> UserStoreScopes { get; set; }
|
||||
public DbSet<StockBalance> StockBalances { get; set; }
|
||||
public DbSet<StockMovement> StockMovements { get; set; }
|
||||
public DbSet<PriceList> PriceLists { get; set; }
|
||||
public DbSet<PriceListItem> PriceListItems { get; set; }
|
||||
public DbSet<Supplier> Suppliers { get; set; }
|
||||
public DbSet<DriverProfile> DriverProfiles { get; set; }
|
||||
public DbSet<DriverComplianceConfig> DriverComplianceConfigs { get; set; }
|
||||
public DbSet<Scheme> Schemes { get; set; }
|
||||
public DbSet<SchemeRedemption> SchemeRedemptions { get; set; }
|
||||
public DbSet<SchemeBundleComponent> SchemeBundleComponents { get; set; }
|
||||
public DbSet<LoadingSheet> LoadingSheets { get; set; }
|
||||
public DbSet<LoadingSheetItem> LoadingSheetItems { get; set; }
|
||||
public DbSet<Payment> Payments { get; set; }
|
||||
public DbSet<DeliveryTask> DeliveryTasks { get; set; }
|
||||
public DbSet<PurchaseOrder> PurchaseOrders { get; set; }
|
||||
public DbSet<PurchaseOrderItem> PurchaseOrderItems { get; set; }
|
||||
public DbSet<GoodsReceipt> GoodsReceipts { get; set; }
|
||||
public DbSet<GoodsReceiptItem> GoodsReceiptItems { get; set; }
|
||||
public DbSet<GoodsReceiptItemBatch> GoodsReceiptItemBatches { get; set; }
|
||||
public DbSet<GoodsReceiptItemSerial> GoodsReceiptItemSerials { get; set; }
|
||||
public DbSet<PurchaseReturn> PurchaseReturns { get; set; }
|
||||
public DbSet<PurchaseReturnItem> PurchaseReturnItems { get; set; }
|
||||
public DbSet<SaleReturn> SaleReturns { get; set; }
|
||||
public DbSet<SaleReturnItem> SaleReturnItems { get; set; }
|
||||
public DbSet<CreditNote> CreditNotes { get; set; }
|
||||
public DbSet<Claim> Claims { get; set; }
|
||||
public DbSet<SupplierClaim> SupplierClaims { get; set; }
|
||||
public DbSet<SupplierClaimItem> SupplierClaimItems { get; set; }
|
||||
public DbSet<RegionalTarget> RegionalTargets { get; set; }
|
||||
public DbSet<RegionalBudget> RegionalBudgets { get; set; }
|
||||
public DbSet<RegionAgencyMap> RegionAgencyMaps { get; set; }
|
||||
public DbSet<RegionalTaxRule> RegionalTaxRules { get; set; }
|
||||
public DbSet<RouteVisitLog> RouteVisitLogs { get; set; }
|
||||
public DbSet<SysConfig> SysConfigs { get; set; }
|
||||
public DbSet<BankAccount> BankAccounts { get; set; }
|
||||
public DbSet<ChequeBook> ChequeBooks { get; set; }
|
||||
public DbSet<ChequePage> ChequePages { get; set; }
|
||||
public DbSet<ReceivedCheque> ReceivedCheques { get; set; }
|
||||
public DbSet<SupplierPayment> SupplierPayments { get; set; }
|
||||
public DbSet<SupplierPaymentGRN> SupplierPaymentGRNs { get; set; }
|
||||
public DbSet<Notification> Notifications { get; set; }
|
||||
public DbSet<NotificationRecipient> NotificationRecipients { get; set; }
|
||||
public DbSet<ExpenseCategory> ExpenseCategories { get; set; }
|
||||
public DbSet<ExpenseVendor> ExpenseVendors { get; set; }
|
||||
public DbSet<Expense> Expenses { get; set; }
|
||||
public DbSet<ExpenseAttachment> ExpenseAttachments { get; set; }
|
||||
public DbSet<RecurringExpenseTemplate> RecurringExpenseTemplates { get; set; }
|
||||
public DbSet<StoreCreditMovement> StoreCreditMovements { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<OrderItem>()
|
||||
.HasKey(oi => new { oi.FK_OrderId, oi.FK_ItemId });
|
||||
// --- Composite PKs ---
|
||||
|
||||
modelBuilder.Entity<RolePermission>()
|
||||
.HasKey(rp => new { rp.FK_RoleId, rp.FK_PermissionId });
|
||||
@@ -45,12 +99,812 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
modelBuilder.Entity<UserStoreScope>()
|
||||
.HasKey(s => new { s.FK_UserId, s.FK_StoreId });
|
||||
|
||||
modelBuilder.Entity<LoadingSheetOrder>()
|
||||
.HasKey(lo => new { lo.FK_LoadingSheetId, lo.FK_ShopPOId });
|
||||
|
||||
// --- Unique indexes ---
|
||||
|
||||
// NOTE: MySQL treats each NULL as distinct in a unique index, so this does not by itself
|
||||
// prevent duplicate rows for untracked items (both FK columns null) — StockRepository's
|
||||
// get-then-update pattern is what keeps those to one row per (Item, Location), same as before.
|
||||
modelBuilder.Entity<StockBalance>()
|
||||
.HasIndex(sb => new { sb.FK_ItemId, sb.LocationId, sb.FK_ItemBatchId, sb.FK_ItemSerialNumberId })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<ItemSerialNumber>()
|
||||
.HasIndex(s => new { s.FK_ItemId, s.SerialNumber })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<PriceListItem>()
|
||||
.HasIndex(pli => new { pli.FK_PriceListId, pli.FK_ItemId })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<DriverProfile>()
|
||||
.HasIndex(dp => dp.FK_UserId)
|
||||
.IsUnique();
|
||||
|
||||
// --- User ---
|
||||
|
||||
modelBuilder.Entity<User>()
|
||||
.HasOne(u => u.ReportsTo)
|
||||
.WithMany()
|
||||
.HasForeignKey(u => u.FK_ReportsToUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Vehicle ---
|
||||
|
||||
modelBuilder.Entity<Vehicle>()
|
||||
.HasOne<Warehouse>()
|
||||
.WithMany()
|
||||
.HasForeignKey(nameof(Vehicle.DefaultWarehouseId))
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- DeliveryRun ---
|
||||
|
||||
modelBuilder.Entity<DeliveryRun>()
|
||||
.HasOne(r => r.DriverProfile)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_DriverProfileId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- ShopPO ---
|
||||
|
||||
modelBuilder.Entity<ShopPO>()
|
||||
.HasOne(p => p.Store)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_StoreId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// InvoiceId is a plain column — no FK constraint
|
||||
modelBuilder.Entity<ShopPO>()
|
||||
.Property(p => p.InvoiceId)
|
||||
.HasColumnName("InvoiceId");
|
||||
|
||||
// --- ShopPOItem ---
|
||||
|
||||
modelBuilder.Entity<ShopPOItem>()
|
||||
.HasOne(i => i.ShopPO)
|
||||
.WithMany(p => p.Items)
|
||||
.HasForeignKey(i => i.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<ShopPOItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Invoice ---
|
||||
|
||||
modelBuilder.Entity<Invoice>()
|
||||
.HasOne(i => i.ShopPO)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceItem>()
|
||||
.HasOne(i => i.Invoice)
|
||||
.WithMany(inv => inv.Items)
|
||||
.HasForeignKey(i => i.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceItem>()
|
||||
.HasOne(i => i.ShopPOItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<InvoiceItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersion>()
|
||||
.HasOne(v => v.Invoice)
|
||||
.WithMany()
|
||||
.HasForeignKey(v => v.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersion>()
|
||||
.HasIndex(v => new { v.FK_InvoiceId, v.VersionNumber })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<InvoiceVersionItem>()
|
||||
.HasOne(i => i.InvoiceVersion)
|
||||
.WithMany(v => v.Items)
|
||||
.HasForeignKey(i => i.FK_InvoiceVersionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersionItem>()
|
||||
.HasOne(i => i.ShopPOItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersionItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<Invoice>()
|
||||
.HasMany(i => i.Payments)
|
||||
.WithOne(p => p.Invoice)
|
||||
.HasForeignKey(p => p.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- LoadingSheet ---
|
||||
|
||||
modelBuilder.Entity<LoadingSheet>()
|
||||
.HasOne(s => s.Ride)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_RideId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- LoadingSheetOrder ---
|
||||
|
||||
modelBuilder.Entity<LoadingSheetOrder>()
|
||||
.HasOne(lo => lo.LoadingSheet)
|
||||
.WithMany(s => s.Orders)
|
||||
.HasForeignKey(lo => lo.FK_LoadingSheetId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<LoadingSheetOrder>()
|
||||
.HasOne(lo => lo.ShopPO)
|
||||
.WithMany()
|
||||
.HasForeignKey(lo => lo.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- LoadingSheetItem ---
|
||||
|
||||
modelBuilder.Entity<LoadingSheetItem>()
|
||||
.HasOne(i => i.LoadingSheet)
|
||||
.WithMany(s => s.Items)
|
||||
.HasForeignKey(i => i.FK_LoadingSheetId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<LoadingSheetItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<LoadingSheetItem>()
|
||||
.HasOne(i => i.Warehouse)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_WarehouseId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<LoadingSheetItem>()
|
||||
.HasOne(i => i.ShopPOItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOItemId)
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- SchemeRedemption ---
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>()
|
||||
.HasOne(r => r.Scheme)
|
||||
.WithMany(s => s.Redemptions)
|
||||
.HasForeignKey(r => r.FK_SchemeId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>()
|
||||
.HasOne(r => r.ShopPO)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>()
|
||||
.HasOne(r => r.FreeItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_FreeItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Scheme ---
|
||||
|
||||
modelBuilder.Entity<Scheme>()
|
||||
.HasOne(s => s.TriggerItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_TriggerItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<Scheme>()
|
||||
.HasOne(s => s.FreeItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_FreeItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SchemeBundleComponent>()
|
||||
.HasOne(c => c.Scheme)
|
||||
.WithMany(s => s.BundleComponents)
|
||||
.HasForeignKey(c => c.FK_SchemeId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<SchemeBundleComponent>()
|
||||
.HasOne(c => c.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SchemeBundleComponent>()
|
||||
.HasIndex(c => new { c.FK_SchemeId, c.FK_ItemId })
|
||||
.IsUnique();
|
||||
|
||||
// --- PurchaseOrder ---
|
||||
|
||||
modelBuilder.Entity<PurchaseOrder>()
|
||||
.HasOne(po => po.Supplier)
|
||||
.WithMany()
|
||||
.HasForeignKey(po => po.FK_SupplierId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrderItem>()
|
||||
.HasOne(i => i.PurchaseOrder)
|
||||
.WithMany(po => po.Items)
|
||||
.HasForeignKey(i => i.FK_PurchaseOrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrderItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrderItem>()
|
||||
.HasIndex(i => i.FK_PurchaseOrderId);
|
||||
|
||||
// --- GoodsReceipt ---
|
||||
|
||||
modelBuilder.Entity<GoodsReceipt>()
|
||||
.HasOne(gr => gr.PurchaseOrder)
|
||||
.WithMany()
|
||||
.HasForeignKey(gr => gr.FK_PurchaseOrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceipt>()
|
||||
.HasOne(gr => gr.Supplier)
|
||||
.WithMany()
|
||||
.HasForeignKey(gr => gr.FK_SupplierId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<GoodsReceipt>()
|
||||
.HasOne(gr => gr.Warehouse)
|
||||
.WithMany()
|
||||
.HasForeignKey(gr => gr.FK_WarehouseId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasOne(i => i.GoodsReceipt)
|
||||
.WithMany(gr => gr.Items)
|
||||
.HasForeignKey(i => i.FK_GoodsReceiptId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasOne(i => i.PurchaseOrderItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_PurchaseOrderItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasIndex(i => i.FK_GoodsReceiptId);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasIndex(i => i.FK_PurchaseOrderItemId);
|
||||
|
||||
// --- ItemBatch / ItemSerialNumber ---
|
||||
|
||||
modelBuilder.Entity<ItemBatch>()
|
||||
.HasOne(b => b.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(b => b.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<ItemBatch>()
|
||||
.HasIndex(b => new { b.FK_ItemId, b.BatchNumber });
|
||||
|
||||
modelBuilder.Entity<ItemSerialNumber>()
|
||||
.HasOne(s => s.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- StockBalance / StockMovement batch & serial links ---
|
||||
|
||||
modelBuilder.Entity<StockBalance>()
|
||||
.HasOne(sb => sb.ItemBatch)
|
||||
.WithMany()
|
||||
.HasForeignKey(sb => sb.FK_ItemBatchId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StockBalance>()
|
||||
.HasOne(sb => sb.ItemSerialNumber)
|
||||
.WithMany()
|
||||
.HasForeignKey(sb => sb.FK_ItemSerialNumberId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StockMovement>()
|
||||
.HasOne(sm => sm.ItemBatch)
|
||||
.WithMany()
|
||||
.HasForeignKey(sm => sm.FK_ItemBatchId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StockMovement>()
|
||||
.HasOne(sm => sm.ItemSerialNumber)
|
||||
.WithMany()
|
||||
.HasForeignKey(sm => sm.FK_ItemSerialNumberId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- GoodsReceiptItemBatch / GoodsReceiptItemSerial ---
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItemBatch>()
|
||||
.HasOne(b => b.GoodsReceiptItem)
|
||||
.WithMany(i => i.Batches)
|
||||
.HasForeignKey(b => b.FK_GoodsReceiptItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItemBatch>()
|
||||
.HasOne(b => b.ItemBatch)
|
||||
.WithMany()
|
||||
.HasForeignKey(b => b.FK_ItemBatchId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItemSerial>()
|
||||
.HasOne(s => s.GoodsReceiptItem)
|
||||
.WithMany(i => i.Serials)
|
||||
.HasForeignKey(s => s.FK_GoodsReceiptItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItemSerial>()
|
||||
.HasOne(s => s.ItemSerialNumber)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_ItemSerialNumberId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- PurchaseReturn ---
|
||||
|
||||
modelBuilder.Entity<PurchaseReturn>()
|
||||
.HasOne(pr => pr.PurchaseOrder)
|
||||
.WithMany()
|
||||
.HasForeignKey(pr => pr.FK_PurchaseOrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseReturnItem>()
|
||||
.HasOne(i => i.PurchaseReturn)
|
||||
.WithMany(pr => pr.Items)
|
||||
.HasForeignKey(i => i.FK_PurchaseReturnId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseReturnItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseReturnItem>()
|
||||
.HasIndex(i => i.FK_PurchaseReturnId);
|
||||
|
||||
// --- SaleReturn ---
|
||||
|
||||
modelBuilder.Entity<SaleReturn>()
|
||||
.HasOne(r => r.ShopPO)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SaleReturn>()
|
||||
.HasOne(r => r.CreatedByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.CreatedByUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SaleReturnItem>()
|
||||
.HasOne(i => i.SaleReturn)
|
||||
.WithMany(r => r.Items)
|
||||
.HasForeignKey(i => i.FK_SaleReturnId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SaleReturnItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SaleReturnItem>()
|
||||
.HasIndex(i => i.FK_SaleReturnId);
|
||||
|
||||
// --- CreditNote ---
|
||||
|
||||
modelBuilder.Entity<CreditNote>()
|
||||
.HasOne(n => n.SaleReturn)
|
||||
.WithMany()
|
||||
.HasForeignKey(n => n.FK_SaleReturnId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Claims ---
|
||||
|
||||
modelBuilder.Entity<Claim>()
|
||||
.HasOne(c => c.AgencyUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_AgencyUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<Claim>()
|
||||
.HasOne(c => c.SettledByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.SettledByUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierClaim>()
|
||||
.HasOne(c => c.PurchaseOrder)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_PurchaseOrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierClaim>()
|
||||
.HasOne(c => c.GoodsReceipt)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_GoodsReceiptId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<SupplierClaim>()
|
||||
.HasOne(c => c.RaisedByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.RaisedByUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierClaim>()
|
||||
.HasOne(c => c.ResolvedByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.ResolvedByUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierClaimItem>()
|
||||
.HasOne(i => i.SupplierClaim)
|
||||
.WithMany(c => c.Items)
|
||||
.HasForeignKey(i => i.FK_SupplierClaimId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierClaimItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierClaimItem>()
|
||||
.HasIndex(i => i.FK_SupplierClaimId);
|
||||
|
||||
// --- DeliveryTask ---
|
||||
|
||||
modelBuilder.Entity<DeliveryTask>()
|
||||
.HasOne(t => t.Ride)
|
||||
.WithMany()
|
||||
.HasForeignKey(t => t.FK_RideId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeliveryTask>()
|
||||
.HasOne(t => t.ShopPO)
|
||||
.WithMany()
|
||||
.HasForeignKey(t => t.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Regional ---
|
||||
|
||||
modelBuilder.Entity<Region>()
|
||||
.Property(r => r.Code)
|
||||
.HasMaxLength(50);
|
||||
|
||||
modelBuilder.Entity<Region>()
|
||||
.HasIndex(r => r.Code)
|
||||
.IsUnique();
|
||||
|
||||
|
||||
modelBuilder.Entity<RegionalTarget>()
|
||||
.HasIndex(t => new { t.FK_RegionId, t.Period })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<RegionalBudget>()
|
||||
.HasIndex(b => new { b.FK_RegionId, b.Period })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<RegionAgencyMap>()
|
||||
.HasIndex(m => new { m.FK_RegionId, m.FK_AgencyUserId })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<RegionAgencyMap>()
|
||||
.HasOne(m => m.AgencyUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_AgencyUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<RegionalTaxRule>()
|
||||
.HasIndex(r => r.FK_RegionId)
|
||||
.IsUnique();
|
||||
|
||||
// --- SysConfig ---
|
||||
|
||||
modelBuilder.Entity<SysConfig>()
|
||||
.HasKey(c => c.Key);
|
||||
|
||||
modelBuilder.Entity<SysConfig>()
|
||||
.Property(c => c.Key)
|
||||
.HasMaxLength(100);
|
||||
|
||||
modelBuilder.Entity<SysConfig>().HasData(
|
||||
new SysConfig { Key = "InvoiceCreationMode", Value = "ModeB", UpdatedAt = new DateTime(2026, 9, 17), UpdatedBy = null },
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeySerialNumberTrackingEnabled, Value = "false", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null },
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyBatchTrackingEnabled, Value = "false", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null },
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyExpiryDateTrackingEnabled, Value = "false", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null },
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyWarrantyTrackingEnabled, Value = "false", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null },
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyCustomWarrantyOverrideEnabled, Value = "false", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null },
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyDefaultWarrantyPeriodDays, Value = "0", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null }
|
||||
);
|
||||
|
||||
// --- Payment service: Bank Accounts & Cheques ---
|
||||
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.AccountName).HasMaxLength(200);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.BankName).HasMaxLength(200);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.CashAccountTypeName).HasMaxLength(100);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.AccountNumber).HasMaxLength(100);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.CurrencyCode).HasMaxLength(10);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.HasIndex(a => new { a.AccountType, a.AccountName })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<BankAccount>().HasData(new BankAccount
|
||||
{
|
||||
Id = new Guid("11111111-1111-1111-1111-000000000001"),
|
||||
CreatedAt = new DateTime(2026, 10, 19),
|
||||
IsActive = true,
|
||||
AccountType = BankAccountType.Cash,
|
||||
AccountName = "Petty Cash",
|
||||
CashAccountTypeName = "Petty Cash",
|
||||
CurrencyCode = "LKR",
|
||||
IsDefault = true
|
||||
});
|
||||
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.Property(b => b.ChequeBookNo).HasMaxLength(100);
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.Property(b => b.StartChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.Property(b => b.EndChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.HasIndex(b => b.ChequeBookNo).IsUnique();
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.HasOne(b => b.BankAccount)
|
||||
.WithMany()
|
||||
.HasForeignKey(b => b.FK_BankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.Property(p => p.ChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.Property(p => p.CurrencyCode).HasMaxLength(10);
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.HasIndex(p => p.ChequeNo).IsUnique();
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.HasOne(p => p.ChequeBook)
|
||||
.WithMany(b => b.Pages)
|
||||
.HasForeignKey(p => p.FK_ChequeBookId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.Property(c => c.ChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.Property(c => c.ReceivedFromName).HasMaxLength(200);
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.HasIndex(c => c.ChequeNo);
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.HasOne(c => c.DepositBankAccount)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_DepositBankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- SupplierPayment ---
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.HasOne(p => p.Supplier)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_SupplierId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.HasOne(p => p.BankAccount)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_BankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.HasOne(p => p.ChequePage)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_ChequePageId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.Property(p => p.Amount).HasPrecision(18, 2);
|
||||
|
||||
modelBuilder.Entity<SupplierPaymentGRN>()
|
||||
.HasOne(a => a.SupplierPayment)
|
||||
.WithMany(p => p.Allocations)
|
||||
.HasForeignKey(a => a.FK_SupplierPaymentId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<SupplierPaymentGRN>()
|
||||
.HasOne(a => a.GoodsReceipt)
|
||||
.WithMany()
|
||||
.HasForeignKey(a => a.FK_GoodsReceiptId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierPaymentGRN>()
|
||||
.HasIndex(a => a.FK_GoodsReceiptId);
|
||||
|
||||
// --- Notifications ---
|
||||
|
||||
modelBuilder.Entity<Notification>()
|
||||
.HasIndex(n => new { n.EntityType, n.EntityId });
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasOne(r => r.Notification)
|
||||
.WithMany(n => n.Recipients)
|
||||
.HasForeignKey(r => r.FK_NotificationId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasOne(r => r.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasIndex(r => new { r.FK_UserId, r.IsRead });
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasIndex(r => new { r.FK_NotificationId, r.FK_UserId })
|
||||
.IsUnique();
|
||||
|
||||
// --- StoreCreditMovement ---
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasOne(m => m.Store)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_StoreId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasOne(m => m.Invoice)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasOne(m => m.Payment)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_PaymentId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.Property(m => m.Amount).HasPrecision(18, 2);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasIndex(m => new { m.FK_StoreId, m.OccurredAt });
|
||||
|
||||
// --- Expense ---
|
||||
|
||||
modelBuilder.Entity<ExpenseCategory>()
|
||||
.HasOne(c => c.Parent)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_ParentCategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<ExpenseCategory>()
|
||||
.Property(c => c.Name).HasMaxLength(100);
|
||||
modelBuilder.Entity<ExpenseCategory>()
|
||||
.Property(c => c.Code).HasMaxLength(50);
|
||||
modelBuilder.Entity<ExpenseCategory>()
|
||||
.HasIndex(c => new { c.FK_ParentCategoryId, c.Name }).IsUnique();
|
||||
|
||||
modelBuilder.Entity<ExpenseVendor>()
|
||||
.Property(v => v.Name).HasMaxLength(200);
|
||||
modelBuilder.Entity<ExpenseVendor>()
|
||||
.HasIndex(v => v.Name).IsUnique();
|
||||
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasOne(e => e.Category).WithMany()
|
||||
.HasForeignKey(e => e.FK_CategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasOne(e => e.Warehouse).WithMany()
|
||||
.HasForeignKey(e => e.FK_WarehouseId)
|
||||
.OnDelete(DeleteBehavior.Restrict).IsRequired(false);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasOne(e => e.Vendor).WithMany()
|
||||
.HasForeignKey(e => e.FK_VendorId)
|
||||
.OnDelete(DeleteBehavior.Restrict).IsRequired(false);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasOne(e => e.BankAccount).WithMany()
|
||||
.HasForeignKey(e => e.FK_BankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict).IsRequired(false);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasOne(e => e.ChequePage).WithMany()
|
||||
.HasForeignKey(e => e.FK_ChequePageId)
|
||||
.OnDelete(DeleteBehavior.Restrict).IsRequired(false);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasOne(e => e.RecurringTemplate).WithMany()
|
||||
.HasForeignKey(e => e.FK_RecurringTemplateId)
|
||||
.OnDelete(DeleteBehavior.SetNull).IsRequired(false);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.Property(e => e.Payee).HasMaxLength(200);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.Property(e => e.BillRef).HasMaxLength(100);
|
||||
modelBuilder.Entity<Expense>()
|
||||
.HasIndex(e => new { e.Status, e.BillDate });
|
||||
|
||||
modelBuilder.Entity<ExpenseAttachment>()
|
||||
.HasOne(a => a.Expense).WithMany(e => e.Attachments)
|
||||
.HasForeignKey(a => a.FK_ExpenseId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<ExpenseAttachment>()
|
||||
.Property(a => a.OriginalFileName).HasMaxLength(260);
|
||||
modelBuilder.Entity<ExpenseAttachment>()
|
||||
.Property(a => a.StoredPath).HasMaxLength(500);
|
||||
modelBuilder.Entity<ExpenseAttachment>()
|
||||
.Property(a => a.ContentType).HasMaxLength(100);
|
||||
|
||||
modelBuilder.Entity<RecurringExpenseTemplate>()
|
||||
.HasOne(t => t.Category).WithMany()
|
||||
.HasForeignKey(t => t.FK_CategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
modelBuilder.Entity<RecurringExpenseTemplate>()
|
||||
.HasOne(t => t.Warehouse).WithMany()
|
||||
.HasForeignKey(t => t.FK_WarehouseId)
|
||||
.OnDelete(DeleteBehavior.Restrict).IsRequired(false);
|
||||
modelBuilder.Entity<RecurringExpenseTemplate>()
|
||||
.HasOne(t => t.Vendor).WithMany()
|
||||
.HasForeignKey(t => t.FK_VendorId)
|
||||
.OnDelete(DeleteBehavior.Restrict).IsRequired(false);
|
||||
modelBuilder.Entity<RecurringExpenseTemplate>()
|
||||
.Property(t => t.Name).HasMaxLength(200);
|
||||
modelBuilder.Entity<RecurringExpenseTemplate>()
|
||||
.HasIndex(t => new { t.IsPaused, t.NextRunDate });
|
||||
|
||||
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
|
||||
Seed.ExpenseSeed.Apply(modelBuilder);
|
||||
Seed.MasterDataSeed.Apply(modelBuilder);
|
||||
Seed.OperationsDataSeed.Apply(modelBuilder);
|
||||
Seed.ProcurementSeed.Apply(modelBuilder);
|
||||
Seed.CommerceSeed.Apply(modelBuilder);
|
||||
Seed.OperationsExtrasSeed.Apply(modelBuilder);
|
||||
Seed.AdminSeed.Apply(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class AdminSeed
|
||||
{
|
||||
private static readonly DateTime SeedDate = new(2026, 10, 13);
|
||||
|
||||
// Same GUID as the AuthHex user — the sub claim from the JWT maps to this Id
|
||||
internal static readonly Guid SuperAdminUserId = new("00000001-0000-0000-0000-000000000001");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<User>().HasData(new User
|
||||
{
|
||||
Id = SuperAdminUserId,
|
||||
Username = "superadmin",
|
||||
FullName = "Super Admin",
|
||||
FK_RoleId = RoleAndPermissionSeed.SuperAdminRoleId,
|
||||
IsActive = true,
|
||||
CreatedAt = SeedDate
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class CommerceSeed
|
||||
{
|
||||
private static readonly Guid PriceList1Id = new("39000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid PriceListItem1Id = new("40000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid PriceListItem2Id = new("40000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid Scheme1Id = new("48000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid Scheme2Id = new("48000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid SchemeBundleComponent1Id = new("49000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid SchemeRedemption1Id = new("50000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid Payment1Id = new("47000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid Payment2Id = new("47000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid Payment3Id = new("47000000-0000-0000-0000-000000000003");
|
||||
private static readonly Guid Payment4Id = new("47000000-0000-0000-0000-000000000004");
|
||||
|
||||
private static readonly Guid SaleReturn1Id = new("51000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid SaleReturnItem1Id = new("52000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid CreditNote1Id = new("54000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid Claim1Id = new("53000000-0000-0000-0000-000000000001");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<PriceList>().HasData(
|
||||
new PriceList { Id = PriceList1Id, Name = "Western Province Price List", FK_RegionId = MasterDataSeed.WesternRegionId, EffectiveFrom = new DateTime(2026, 1, 1), EffectiveTo = new DateTime(2026, 12, 31), CreatedAt = new DateTime(2026, 8, 1), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<PriceListItem>().HasData(
|
||||
new PriceListItem { Id = PriceListItem1Id, FK_PriceListId = PriceList1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, Price = 60m, MinPrice = 55m, CreatedAt = new DateTime(2026, 8, 1), IsActive = true },
|
||||
new PriceListItem { Id = PriceListItem2Id, FK_PriceListId = PriceList1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Price = 50m, MinPrice = 45m, CreatedAt = new DateTime(2026, 8, 1), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Scheme>().HasData(
|
||||
new Scheme
|
||||
{
|
||||
Id = Scheme1Id,
|
||||
Name = "Buy 10 Coke Get 1 Free",
|
||||
RuleType = SchemeRuleType.BuyXGetY,
|
||||
RuleConfig = null,
|
||||
FK_TriggerItemId = MasterDataSeed.Coke500ItemId,
|
||||
TriggerQty = 10,
|
||||
FK_FreeItemId = MasterDataSeed.Coke500ItemId,
|
||||
FreeQty = 1m,
|
||||
FK_RegionId = MasterDataSeed.WesternRegionId,
|
||||
EligibleSegment = null,
|
||||
DateRangeFrom = new DateTime(2026, 8, 1),
|
||||
DateRangeTo = new DateTime(2026, 12, 31),
|
||||
AgentLimitExempt = false,
|
||||
BaselineExpectedSales = 500000m,
|
||||
CreatedAt = new DateTime(2026, 8, 1),
|
||||
IsActive = true
|
||||
},
|
||||
new Scheme
|
||||
{
|
||||
Id = Scheme2Id,
|
||||
Name = "Snack Combo Bundle",
|
||||
RuleType = SchemeRuleType.FreeBundle,
|
||||
RuleConfig = null,
|
||||
FK_TriggerItemId = null,
|
||||
TriggerQty = null,
|
||||
FK_FreeItemId = MasterDataSeed.LaysFamilyItemId,
|
||||
FreeQty = 1m,
|
||||
FK_RegionId = MasterDataSeed.CentralRegionId,
|
||||
EligibleSegment = null,
|
||||
DateRangeFrom = new DateTime(2026, 8, 1),
|
||||
DateRangeTo = new DateTime(2026, 12, 31),
|
||||
AgentLimitExempt = false,
|
||||
BaselineExpectedSales = null,
|
||||
CreatedAt = new DateTime(2026, 8, 1),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SchemeBundleComponent>().HasData(
|
||||
new SchemeBundleComponent { Id = SchemeBundleComponent1Id, FK_SchemeId = Scheme2Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Qty = 5 }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>().HasData(
|
||||
new SchemeRedemption { Id = SchemeRedemption1Id, FK_SchemeId = Scheme1Id, FK_ShopPOId = OperationsDataSeed.ShopPO1Id, FieldInvoiceId = null, DiscountAmount = 0m, FK_FreeItemId = MasterDataSeed.Coke500ItemId, FreeQty = 1m, FreeItemCostValue = 40m, RedeemedAt = new DateTime(2026, 8, 25, 9, 30, 0) }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Payment>().HasData(
|
||||
new Payment { Id = Payment1Id, FK_InvoiceId = OperationsDataSeed.Invoice1Id, Amount = 3000m, Method = PaymentMethod.Cash, EntryMode = EntryMode.RealTime, CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0), CollectedBy = MasterDataSeed.SalesRep1UserId, CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0), IsActive = true },
|
||||
new Payment { Id = Payment2Id, FK_InvoiceId = OperationsDataSeed.Invoice2Id, Amount = 1000m, Method = PaymentMethod.Cash, EntryMode = EntryMode.RealTime, CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0), CollectedBy = MasterDataSeed.SalesRep2UserId, CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0), IsActive = true },
|
||||
new Payment { Id = Payment3Id, FK_InvoiceId = OperationsDataSeed.Invoice2Id, Amount = 800m, Method = PaymentMethod.Cheque, EntryMode = EntryMode.DayEndManual, ChequeNumber = "000456", ChequeDate = new DateTime(2026, 8, 31), CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0), CollectedBy = MasterDataSeed.SalesRep2UserId, RecordedBy = MasterDataSeed.Supervisor1UserId, CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0), IsActive = true },
|
||||
new Payment { Id = Payment4Id, FK_InvoiceId = OperationsDataSeed.Invoice2Id, Amount = 300m, Method = PaymentMethod.Credit, EntryMode = EntryMode.RealTime, CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0), CollectedBy = MasterDataSeed.SalesRep2UserId, CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SaleReturn>().HasData(
|
||||
new SaleReturn { Id = SaleReturn1Id, FK_ShopPOId = OperationsDataSeed.ShopPO1Id, Mode = ReturnMode.CreditDeduction, TotalCreditAmount = 300m, CreatedByUserId = MasterDataSeed.SalesRep1UserId, CreatedAt = new DateTime(2026, 8, 26), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SaleReturnItem>().HasData(
|
||||
new SaleReturnItem { Id = SaleReturnItem1Id, FK_SaleReturnId = SaleReturn1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, Quantity = 5m, ReasonCode = ReturnReasonCode.Damaged, UnitPrice = 60m, CreatedAt = new DateTime(2026, 8, 26), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<CreditNote>().HasData(
|
||||
new CreditNote { Id = CreditNote1Id, FK_SaleReturnId = SaleReturn1Id, Amount = 300m, IssuedAt = new DateTime(2026, 8, 26), CreatedAt = new DateTime(2026, 8, 26), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Claim>().HasData(
|
||||
new Claim
|
||||
{
|
||||
Id = Claim1Id,
|
||||
FK_AgencyUserId = OperationsExtrasSeed.RegionalAgency1UserId,
|
||||
Details = "Distributor margin claim for Q3 promotional shortfall",
|
||||
Status = ClaimStatus.Settled,
|
||||
SettlementAmount = 15000m,
|
||||
SettledByUserId = MasterDataSeed.RegionalManager1UserId,
|
||||
SettledAt = new DateTime(2026, 8, 28),
|
||||
CreatedAt = new DateTime(2026, 8, 27),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class ExpenseSeed
|
||||
{
|
||||
private static readonly DateTime SeedDate = new(2026, 10, 21);
|
||||
|
||||
// Top-level categories
|
||||
private static readonly Guid CatSalaries = new("30000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid CatUtilities = new("30000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid CatRent = new("30000000-0000-0000-0000-000000000003");
|
||||
private static readonly Guid CatVehicle = new("30000000-0000-0000-0000-000000000004");
|
||||
private static readonly Guid CatOffice = new("30000000-0000-0000-0000-000000000005");
|
||||
private static readonly Guid CatBankFees = new("30000000-0000-0000-0000-000000000006");
|
||||
private static readonly Guid CatMisc = new("30000000-0000-0000-0000-000000000007");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<ExpenseCategory>().HasData(
|
||||
// Top-level
|
||||
new ExpenseCategory { Id = CatSalaries, Name = "Salaries", Code = "SAL", CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = CatUtilities, Name = "Utilities", Code = "UTL", CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = CatRent, Name = "Rent", Code = "RNT", CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = CatVehicle, Name = "Vehicle", Code = "VEH", CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = CatOffice, Name = "Office", Code = "OFF", CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = CatBankFees, Name = "Bank Fees", Code = "BNK", CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = CatMisc, Name = "Miscellaneous", Code = "MSC", CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Salaries
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000101"), Name = "Basic Salary", FK_ParentCategoryId = CatSalaries, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000102"), Name = "Bonus", FK_ParentCategoryId = CatSalaries, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000103"), Name = "Overtime", FK_ParentCategoryId = CatSalaries, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Utilities
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000201"), Name = "Electricity", FK_ParentCategoryId = CatUtilities, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000202"), Name = "Water", FK_ParentCategoryId = CatUtilities, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000203"), Name = "Internet", FK_ParentCategoryId = CatUtilities, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Rent
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000301"), Name = "Warehouse Rent", FK_ParentCategoryId = CatRent, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000302"), Name = "Office Rent", FK_ParentCategoryId = CatRent, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Vehicle
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000401"), Name = "Fuel", FK_ParentCategoryId = CatVehicle, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000402"), Name = "Maintenance", FK_ParentCategoryId = CatVehicle, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000403"), Name = "Insurance", FK_ParentCategoryId = CatVehicle, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Office
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000501"), Name = "Stationery", FK_ParentCategoryId = CatOffice, CreatedAt = SeedDate, IsActive = true },
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000502"), Name = "Cleaning", FK_ParentCategoryId = CatOffice, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Bank Fees
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000601"), Name = "Service Charges", FK_ParentCategoryId = CatBankFees, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// Titles under Misc
|
||||
new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000701"), Name = "Other", FK_ParentCategoryId = CatMisc, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class MasterDataSeed
|
||||
{
|
||||
private static readonly DateTime SeedDate = new(2026, 8, 20);
|
||||
|
||||
// Role ids match Data/Seed/RoleAndPermissionSeed.cs (private there, so re-declared here by value).
|
||||
internal static readonly Guid SalesRepRoleId = new("10000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid SupervisorRoleId = new("10000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid RegionalManagerRoleId = new("10000000-0000-0000-0000-000000000005");
|
||||
|
||||
internal static readonly Guid BeveragesCategoryId = new("20000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid SnacksCategoryId = new("20000000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid CocaColaBrandId = new("21000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid LaysBrandId = new("21000000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid PieceUomId = new("22000000-0000-0000-0000-000000000001");
|
||||
|
||||
internal static readonly Guid Coke500ItemId = new("23000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid Coke1500ItemId = new("23000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid LaysClassicItemId = new("23000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid LaysFamilyItemId = new("23000000-0000-0000-0000-000000000004");
|
||||
|
||||
internal static readonly Guid WesternRegionId = new("24000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid CentralRegionId = new("24000000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid ColomboRouteId = new("25000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid NegomboRouteId = new("25000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid KandyRouteId = new("25000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid MataleRouteId = new("25000000-0000-0000-0000-000000000004");
|
||||
|
||||
internal static readonly Guid FortStoreId = new("26000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid DehiwalaStoreId = new("26000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid WattalaStoreId = new("26000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid NegomboStoreId = new("26000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid KandyStoreId = new("26000000-0000-0000-0000-000000000005");
|
||||
internal static readonly Guid MataleStoreId = new("26000000-0000-0000-0000-000000000006");
|
||||
|
||||
internal static readonly Guid ColomboWarehouseId = new("27000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid KandyWarehouseId = new("27000000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid VanVehicleId = new("28000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid TruckVehicleId = new("28000000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid Supervisor1UserId = new("29000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid SalesRep1UserId = new("29000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid SalesRep2UserId = new("29000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid Driver1UserId = new("29000000-0000-0000-0000-000000000004");
|
||||
internal static readonly Guid Driver2UserId = new("29000000-0000-0000-0000-000000000005");
|
||||
internal static readonly Guid RegionalManager1UserId = new("29000000-0000-0000-0000-000000000006");
|
||||
|
||||
internal static readonly Guid Driver1ProfileId = new("30000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid Driver2ProfileId = new("30000000-0000-0000-0000-000000000002");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Category>().HasData(
|
||||
new Category { Id = BeveragesCategoryId, Name = "Beverages", CreatedAt = SeedDate, IsActive = true },
|
||||
new Category { Id = SnacksCategoryId, Name = "Snacks", CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Brand>().HasData(
|
||||
new Brand { Id = CocaColaBrandId, Name = "Coca-Cola", CreatedAt = SeedDate, IsActive = true },
|
||||
new Brand { Id = LaysBrandId, Name = "Lays", CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Uom>().HasData(
|
||||
new Uom { Id = PieceUomId, Name = "Piece", CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Item>().HasData(
|
||||
new Item { Id = Coke500ItemId, Sku = "BEV-COKE-500", Name = "Coca-Cola 500ml", Description = "Coca-Cola 500ml bottle", FK_CategoryId = BeveragesCategoryId, FK_BrandId = CocaColaBrandId, FK_BaseUomId = PieceUomId, SalePrice = 60m, SyncSource = SyncSource.Manual, CreatedAt = SeedDate, IsActive = true },
|
||||
new Item { Id = Coke1500ItemId, Sku = "BEV-COKE-1500", Name = "Coca-Cola 1.5L", Description = "Coca-Cola 1.5L bottle", FK_CategoryId = BeveragesCategoryId, FK_BrandId = CocaColaBrandId, FK_BaseUomId = PieceUomId, SalePrice = 120m, SyncSource = SyncSource.Manual, CreatedAt = SeedDate, IsActive = true },
|
||||
new Item { Id = LaysClassicItemId, Sku = "SNK-LAYS-52", Name = "Lays Classic 52g", Description = "Lays Classic Salted 52g pack", FK_CategoryId = SnacksCategoryId, FK_BrandId = LaysBrandId, FK_BaseUomId = PieceUomId, SalePrice = 50m, SyncSource = SyncSource.Manual, CreatedAt = SeedDate, IsActive = true },
|
||||
new Item { Id = LaysFamilyItemId, Sku = "SNK-LAYS-FAM", Name = "Lays Family Pack 150g", Description = "Lays Family Pack 150g", FK_CategoryId = SnacksCategoryId, FK_BrandId = LaysBrandId, FK_BaseUomId = PieceUomId, SalePrice = 150m, SyncSource = SyncSource.Manual, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Region>().HasData(
|
||||
new Region { Id = WesternRegionId, Code = "REG-0001", Name = "Western Province", CreatedAt = SeedDate, IsActive = true },
|
||||
new Region { Id = CentralRegionId, Code = "REG-0002", Name = "Central Province", CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Models.Route>().HasData(
|
||||
new Models.Route { Id = ColomboRouteId, RouteName = "Colombo City Route", StartPoint = "Colombo Fort", EndPoint = "Dehiwala", Length = 18.5m, FK_RegionId = WesternRegionId, FK_OwnerUserId = SalesRep1UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new Models.Route { Id = NegomboRouteId, RouteName = "Negombo Coastal Route", StartPoint = "Wattala", EndPoint = "Negombo", Length = 32.0m, FK_RegionId = WesternRegionId, FK_OwnerUserId = SalesRep1UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new Models.Route { Id = KandyRouteId, RouteName = "Kandy Town Route", StartPoint = "Kandy Lake", EndPoint = "Peradeniya", Length = 12.0m, FK_RegionId = CentralRegionId, FK_OwnerUserId = SalesRep2UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new Models.Route { Id = MataleRouteId, RouteName = "Matale Route", StartPoint = "Kandy", EndPoint = "Matale", Length = 26.0m, FK_RegionId = CentralRegionId, FK_OwnerUserId = SalesRep2UserId, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Store>().HasData(
|
||||
new Store { Id = FortStoreId, FK_RouteId = ColomboRouteId, OwnerName = "Saman Kumara", StoreName = "Fort Mini Mart", StoreAddress = "12 York Street, Colombo Fort", Latitude = 6.9344m, Longitude = 79.8428m, CreditLimit = 50000m, OutstandingBalance = 0m, Segment = "Gold", CreatedAt = SeedDate, IsActive = true },
|
||||
new Store { Id = DehiwalaStoreId, FK_RouteId = ColomboRouteId, OwnerName = "Nadeesha Silva", StoreName = "Dehiwala Grocery", StoreAddress = "45 Galle Road, Dehiwala", Latitude = 6.8508m, Longitude = 79.8654m, CreditLimit = 30000m, OutstandingBalance = 5000m, Segment = "Silver", CreatedAt = SeedDate, IsActive = true },
|
||||
new Store { Id = WattalaStoreId, FK_RouteId = NegomboRouteId, OwnerName = "Ruwan Gunasekara", StoreName = "Wattala Supermarket", StoreAddress = "88 Negombo Road, Wattala", Latitude = 6.9890m, Longitude = 79.8912m, CreditLimit = 40000m, OutstandingBalance = 1200m, Segment = "Gold", CreatedAt = SeedDate, IsActive = true },
|
||||
new Store { Id = NegomboStoreId, FK_RouteId = NegomboRouteId, OwnerName = "Chamari Peiris", StoreName = "Negombo Beach Store", StoreAddress = "5 Beach Road, Negombo", Latitude = 7.2094m, Longitude = 79.8380m, CreditLimit = 25000m, OutstandingBalance = 0m, CreatedAt = SeedDate, IsActive = true },
|
||||
new Store { Id = KandyStoreId, FK_RouteId = KandyRouteId, OwnerName = "Dinesh Wickramasinghe", StoreName = "Kandy Lake View Store", StoreAddress = "3 Lake Road, Kandy", Latitude = 7.2906m, Longitude = 80.6337m, CreditLimit = 35000m, OutstandingBalance = 0m, Segment = "Key Account", CreatedAt = SeedDate, IsActive = true },
|
||||
new Store { Id = MataleStoreId, FK_RouteId = MataleRouteId, OwnerName = "Ishara Bandara", StoreName = "Matale Central Store", StoreAddress = "20 Main Street, Matale", Latitude = 7.4675m, Longitude = 80.6234m, CreditLimit = 20000m, OutstandingBalance = 0m, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Warehouse>().HasData(
|
||||
new Warehouse { Id = ColomboWarehouseId, Name = "Colombo Distribution Center", Location = "Peliyagoda, Colombo", Type = WareHouseType.DistributionCenter, VehicleId = null, CreatedAt = SeedDate, IsActive = true },
|
||||
new Warehouse { Id = KandyWarehouseId, Name = "Kandy Fulfillment Center", Location = "Katugastota, Kandy", Type = WareHouseType.FulfillmentCenter, VehicleId = null, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Vehicle>().HasData(
|
||||
new Vehicle { Id = VanVehicleId, RegNumber = "WP-CAB-1234", NumberPlate = "CAB-1234", Type = VehicleType.Van, DefaultWarehouseId = ColomboWarehouseId, CreatedAt = SeedDate, IsActive = true },
|
||||
new Vehicle { Id = TruckVehicleId, RegNumber = "CP-KY-5678", NumberPlate = "KY-5678", Type = VehicleType.Truck, DefaultWarehouseId = KandyWarehouseId, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<User>().HasData(
|
||||
new User { Id = Supervisor1UserId, Username = "sunil.fernando",FullName = "Sunil Fernando", FK_RoleId = SupervisorRoleId, FK_ReportsToUserId = null, CreatedAt = SeedDate, IsActive = true },
|
||||
new User { Id = SalesRep1UserId, Username = "nimal.perera",FullName = "Nimal Perera", FK_RoleId = SalesRepRoleId, FK_ReportsToUserId = Supervisor1UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new User { Id = SalesRep2UserId, Username = "kamal.silva",FullName = "Kamal Silva", FK_RoleId = SalesRepRoleId, FK_ReportsToUserId = Supervisor1UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new User { Id = Driver1UserId, Username = "ruwan.jayasuriya",FullName = "Ruwan Jayasuriya", FK_RoleId = SalesRepRoleId, FK_ReportsToUserId = Supervisor1UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new User { Id = Driver2UserId, Username = "priyantha.kumara",FullName = "Priyantha Kumara", FK_RoleId = SalesRepRoleId, FK_ReportsToUserId = Supervisor1UserId, CreatedAt = SeedDate, IsActive = true },
|
||||
new User { Id = RegionalManager1UserId, Username = "priya.rajapaksa",FullName = "Priya Rajapaksa", FK_RoleId = RegionalManagerRoleId, FK_ReportsToUserId = null, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<DriverProfile>().HasData(
|
||||
new DriverProfile { Id = Driver1ProfileId, FK_UserId = Driver1UserId, LicenseNumber = "B1234567", LicenseExpiry = new DateTime(2027, 6, 30), MedicalClearanceExpiry = new DateTime(2027, 1, 31), CreatedAt = SeedDate, IsActive = true },
|
||||
new DriverProfile { Id = Driver2ProfileId, FK_UserId = Driver2UserId, LicenseNumber = "B7654321", LicenseExpiry = new DateTime(2027, 9, 30), MedicalClearanceExpiry = new DateTime(2027, 3, 31), CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class OperationsDataSeed
|
||||
{
|
||||
internal static readonly Guid ClosedSaleRunId = new("31000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid ActiveDeliveryRunId = new("31000000-0000-0000-0000-000000000002");
|
||||
|
||||
// Kept for OperationsExtrasSeed stock movement reference (ReferenceId on MovementType.Sale)
|
||||
internal static readonly Guid Sale1Id = new("32000000-0000-0000-0000-000000000001");
|
||||
|
||||
internal static readonly Guid ShopPO1Id = new("33000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid ShopPO2Id = new("33000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid ShopPO3Id = new("33000000-0000-0000-0000-000000000003");
|
||||
|
||||
internal static readonly Guid ShopPOItem1Id = new("34000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid ShopPOItem2Id = new("34000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid ShopPOItem3Id = new("34000000-0000-0000-0000-000000000003");
|
||||
internal static readonly Guid ShopPOItem4Id = new("34000000-0000-0000-0000-000000000004");
|
||||
|
||||
internal static readonly Guid Invoice1Id = new("34500000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid Invoice2Id = new("34500000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid LoadingSheet1Id = new("35000000-0000-0000-0000-000000000001");
|
||||
|
||||
internal static readonly Guid LoadingSheetItem1Id = new("36000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid LoadingSheetItem2Id = new("36000000-0000-0000-0000-000000000002");
|
||||
|
||||
internal static readonly Guid DeliveryTask1Id = new("37000000-0000-0000-0000-000000000001");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<DeliveryRun>().HasData(
|
||||
new DeliveryRun
|
||||
{
|
||||
Id = ClosedSaleRunId,
|
||||
Status = RideStatus.Closed,
|
||||
FK_VehicleId = MasterDataSeed.VanVehicleId,
|
||||
FK_DriverProfileId = MasterDataSeed.Driver1ProfileId,
|
||||
Meterstart = 10500,
|
||||
Meterend = 10550,
|
||||
StartedAt = new DateTime(2026, 8, 25, 8, 0, 0),
|
||||
ClosedAt = new DateTime(2026, 8, 25, 14, 0, 0),
|
||||
CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0),
|
||||
IsActive = true
|
||||
},
|
||||
new DeliveryRun
|
||||
{
|
||||
Id = ActiveDeliveryRunId,
|
||||
Status = RideStatus.Open,
|
||||
FK_VehicleId = MasterDataSeed.TruckVehicleId,
|
||||
FK_DriverProfileId = MasterDataSeed.Driver2ProfileId,
|
||||
Meterstart = 20300,
|
||||
Meterend = 0,
|
||||
StartedAt = new DateTime(2026, 8, 30, 9, 0, 0),
|
||||
ClosedAt = null,
|
||||
CreatedAt = new DateTime(2026, 8, 30, 8, 45, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<ShopPO>().HasData(
|
||||
new ShopPO
|
||||
{
|
||||
Id = ShopPO1Id,
|
||||
FK_StoreId = MasterDataSeed.FortStoreId,
|
||||
RaisedById = MasterDataSeed.SalesRep1UserId,
|
||||
InvoiceId = Invoice1Id,
|
||||
TotalAmount = 3000m,
|
||||
Status = ShopPOStatus.Fulfilled,
|
||||
ConfirmedAt = new DateTime(2026, 8, 25, 8, 30, 0),
|
||||
ConfirmedBy = MasterDataSeed.Supervisor1UserId,
|
||||
FulfilledAt = new DateTime(2026, 8, 25, 13, 0, 0),
|
||||
CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0),
|
||||
IsActive = true
|
||||
},
|
||||
new ShopPO
|
||||
{
|
||||
Id = ShopPO2Id,
|
||||
FK_StoreId = MasterDataSeed.KandyStoreId,
|
||||
RaisedById = MasterDataSeed.SalesRep2UserId,
|
||||
InvoiceId = null,
|
||||
TotalAmount = 1200m,
|
||||
Status = ShopPOStatus.Pending,
|
||||
ConfirmedAt = null,
|
||||
ConfirmedBy = null,
|
||||
FulfilledAt = null,
|
||||
CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0),
|
||||
IsActive = true
|
||||
},
|
||||
new ShopPO
|
||||
{
|
||||
Id = ShopPO3Id,
|
||||
FK_StoreId = MasterDataSeed.KandyStoreId,
|
||||
RaisedById = MasterDataSeed.SalesRep2UserId,
|
||||
InvoiceId = Invoice2Id,
|
||||
TotalAmount = 2400m,
|
||||
Status = ShopPOStatus.Fulfilled,
|
||||
ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0),
|
||||
ConfirmedBy = MasterDataSeed.Supervisor1UserId,
|
||||
FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0),
|
||||
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<ShopPOItem>().HasData(
|
||||
new ShopPOItem { Id = ShopPOItem1Id, FK_ShopPOId = ShopPO1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, Quantity = 30, QuantityFulfilled = 30, UnitPrice = 60m, CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
|
||||
new ShopPOItem { Id = ShopPOItem2Id, FK_ShopPOId = ShopPO1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Quantity = 24, QuantityFulfilled = 24, UnitPrice = 50m, CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
|
||||
new ShopPOItem { Id = ShopPOItem3Id, FK_ShopPOId = ShopPO2Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 10, QuantityFulfilled = 0, UnitPrice = 120m, CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0), IsActive = true },
|
||||
new ShopPOItem { Id = ShopPOItem4Id, FK_ShopPOId = ShopPO3Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 20, QuantityFulfilled = 20, UnitPrice = 120m, CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Invoice>().HasData(
|
||||
new Invoice
|
||||
{
|
||||
Id = Invoice1Id,
|
||||
FK_ShopPOId = ShopPO1Id,
|
||||
Status = InvoiceStatus.Finalized,
|
||||
PaymentStatus = PaymentStatus.Paid,
|
||||
CreatedBy = MasterDataSeed.SalesRep1UserId,
|
||||
PrintedAt = new DateTime(2026, 8, 25, 8, 45, 0),
|
||||
PrintedBy = MasterDataSeed.SalesRep1UserId,
|
||||
FinalizedAt = new DateTime(2026, 8, 25, 9, 0, 0),
|
||||
FinalizedBy = MasterDataSeed.Supervisor1UserId,
|
||||
TotalAmount = 3000m,
|
||||
CreatedAt = new DateTime(2026, 8, 25, 8, 30, 0),
|
||||
IsActive = true
|
||||
},
|
||||
new Invoice
|
||||
{
|
||||
Id = Invoice2Id,
|
||||
FK_ShopPOId = ShopPO3Id,
|
||||
Status = InvoiceStatus.Printed,
|
||||
PaymentStatus = PaymentStatus.PartiallyPaid,
|
||||
CreatedBy = MasterDataSeed.SalesRep2UserId,
|
||||
PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0),
|
||||
PrintedBy = MasterDataSeed.SalesRep2UserId,
|
||||
FinalizedAt = null,
|
||||
FinalizedBy = null,
|
||||
TotalAmount = 2400m,
|
||||
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<LoadingSheet>().HasData(
|
||||
new LoadingSheet
|
||||
{
|
||||
Id = LoadingSheet1Id,
|
||||
FK_RideId = ClosedSaleRunId,
|
||||
Status = LoadingSheetStatus.Confirmed,
|
||||
PopulatedBy = MasterDataSeed.Supervisor1UserId,
|
||||
PopulatedAt = new DateTime(2026, 8, 25, 7, 30, 0),
|
||||
ConfirmedAt = new DateTime(2026, 8, 25, 7, 45, 0),
|
||||
ConfirmedBy = MasterDataSeed.Supervisor1UserId,
|
||||
CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<LoadingSheetItem>().HasData(
|
||||
new LoadingSheetItem { Id = LoadingSheetItem1Id, FK_LoadingSheetId = LoadingSheet1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FK_WarehouseId = MasterDataSeed.ColomboWarehouseId, QuantityDispatched = 50m, CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0), IsActive = true },
|
||||
new LoadingSheetItem { Id = LoadingSheetItem2Id, FK_LoadingSheetId = LoadingSheet1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, FK_WarehouseId = MasterDataSeed.ColomboWarehouseId, QuantityDispatched = 30m, CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<DeliveryTask>().HasData(
|
||||
new DeliveryTask
|
||||
{
|
||||
Id = DeliveryTask1Id,
|
||||
FK_RideId = ActiveDeliveryRunId,
|
||||
FK_ShopPOId = ShopPO2Id,
|
||||
Sequence = 1,
|
||||
Status = DeliveryTaskStatus.Pending,
|
||||
CustomerConfirmed = false,
|
||||
ConfirmedAt = null,
|
||||
Note = null,
|
||||
CreatedAt = new DateTime(2026, 8, 30, 10, 5, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class OperationsExtrasSeed
|
||||
{
|
||||
// Role id matches RegionalAgency in Data/Seed/RoleAndPermissionSeed.cs (private there).
|
||||
private static readonly Guid RegionalAgencyRoleId = new("10000000-0000-0000-0000-000000000006");
|
||||
|
||||
internal static readonly Guid RegionalAgency1UserId = new("70000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid CaseUomId = new("67000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid UomConversion1Id = new("67000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid RegionAgencyMap1Id = new("57000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid RegionalBudget1Id = new("58000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid RegionalTarget1Id = new("59000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid RegionalTaxRule1Id = new("60000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid StockBalance1Id = new("61000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid StockBalance2Id = new("61000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid StockBalance3Id = new("61000000-0000-0000-0000-000000000003");
|
||||
|
||||
private static readonly Guid StockMovement1Id = new("62000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid StockMovement2Id = new("62000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid StockMovement3Id = new("62000000-0000-0000-0000-000000000003");
|
||||
|
||||
|
||||
private static readonly Guid DriverComplianceConfig1Id = new("64000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid RouteVisitLog1Id = new("66000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid RouteVisitLog2Id = new("66000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid UserRegionScope1Id = new("68000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid UserRouteScope1Id = new("68000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid UserStoreScope1Id = new("68000000-0000-0000-0000-000000000003");
|
||||
|
||||
private static readonly Guid SaleRoute1Id = new("69000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid SaleRoute2Id = new("69000000-0000-0000-0000-000000000002");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<User>().HasData(
|
||||
new User { Id = RegionalAgency1UserId, Username = "channa.desilva", FullName = "Channa De Silva", FK_RoleId = RegionalAgencyRoleId, FK_ReportsToUserId = null, CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Uom>().HasData(
|
||||
new Uom { Id = CaseUomId, Name = "Case", CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<UomConversion>().HasData(
|
||||
new UomConversion { Id = UomConversion1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FK_FromUomId = CaseUomId, FK_ToUomId = MasterDataSeed.PieceUomId, Factor = 24m }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<RegionAgencyMap>().HasData(
|
||||
new RegionAgencyMap { Id = RegionAgencyMap1Id, FK_RegionId = MasterDataSeed.WesternRegionId, FK_AgencyUserId = RegionalAgency1UserId, CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<RegionalBudget>().HasData(
|
||||
new RegionalBudget { Id = RegionalBudget1Id, FK_RegionId = MasterDataSeed.WesternRegionId, Period = "2026-08", BudgetAmount = 200000m, CreatedAt = new DateTime(2026, 8, 1), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<RegionalTarget>().HasData(
|
||||
new RegionalTarget { Id = RegionalTarget1Id, FK_RegionId = MasterDataSeed.WesternRegionId, Period = "2026-08", TargetValue = 500000m, CreatedAt = new DateTime(2026, 8, 1), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<RegionalTaxRule>().HasData(
|
||||
new RegionalTaxRule { Id = RegionalTaxRule1Id, FK_RegionId = MasterDataSeed.WesternRegionId, RuleConfig = "{\"rate\": 0.15}", CreatedAt = new DateTime(2026, 8, 1), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<StockBalance>().HasData(
|
||||
new StockBalance { Id = StockBalance1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, LocationId = MasterDataSeed.ColomboWarehouseId, LocationType = LocationType.Warehouse, Quantity = 150m, UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0) },
|
||||
new StockBalance { Id = StockBalance2Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, LocationId = MasterDataSeed.ColomboWarehouseId, LocationType = LocationType.Warehouse, Quantity = 90m, UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0) },
|
||||
new StockBalance { Id = StockBalance3Id, FK_ItemId = MasterDataSeed.Coke500ItemId, LocationId = MasterDataSeed.VanVehicleId, LocationType = LocationType.Vehicle, Quantity = 20m, UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0) }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<StockMovement>().HasData(
|
||||
new StockMovement { Id = StockMovement1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FromLocationId = StockService.ExternalLocationId, FromLocationType = LocationType.External, ToLocationId = MasterDataSeed.ColomboWarehouseId, ToLocationType = LocationType.Warehouse, Quantity = 200m, Type = MovementType.PurchaseReceipt, ReasonCode = null, ReferenceId = ProcurementSeed.GoodsReceipt1Id, CreatedAt = new DateTime(2026, 8, 15), CreatedBy = MasterDataSeed.Supervisor1UserId },
|
||||
new StockMovement { Id = StockMovement2Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FromLocationId = MasterDataSeed.ColomboWarehouseId, FromLocationType = LocationType.Warehouse, ToLocationId = MasterDataSeed.VanVehicleId, ToLocationType = LocationType.Vehicle, Quantity = 50m, Type = MovementType.Load, ReasonCode = null, ReferenceId = OperationsDataSeed.LoadingSheet1Id, CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0), CreatedBy = MasterDataSeed.Supervisor1UserId },
|
||||
new StockMovement { Id = StockMovement3Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FromLocationId = MasterDataSeed.VanVehicleId, FromLocationType = LocationType.Vehicle, ToLocationId = StockService.ExternalLocationId, ToLocationType = LocationType.External, Quantity = 30m, Type = MovementType.Sale, ReasonCode = null, ReferenceId = OperationsDataSeed.Sale1Id, CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0), CreatedBy = MasterDataSeed.SalesRep1UserId }
|
||||
);
|
||||
modelBuilder.Entity<DriverComplianceConfig>().HasData(
|
||||
new DriverComplianceConfig { Id = DriverComplianceConfig1Id, EnforceLicenseExpiry = true, EnforceMedicalClearance = true, UpdatedAt = new DateTime(2026, 8, 20), UpdatedBy = MasterDataSeed.Supervisor1UserId }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<RouteVisitLog>().HasData(
|
||||
new RouteVisitLog { Id = RouteVisitLog1Id, FK_RouteId = MasterDataSeed.ColomboRouteId, FK_StoreId = MasterDataSeed.FortStoreId, FK_UserId = MasterDataSeed.SalesRep1UserId, CheckInAt = new DateTime(2026, 8, 25, 9, 0, 0), CheckInLatitude = 6.9344m, CheckInLongitude = 79.8428m, IsDeviationFlagged = false, CheckOutAt = new DateTime(2026, 8, 25, 9, 25, 0), CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
|
||||
new RouteVisitLog { Id = RouteVisitLog2Id, FK_RouteId = MasterDataSeed.ColomboRouteId, FK_StoreId = MasterDataSeed.DehiwalaStoreId, FK_UserId = MasterDataSeed.SalesRep1UserId, CheckInAt = new DateTime(2026, 8, 25, 10, 0, 0), CheckInLatitude = 6.9000m, CheckInLongitude = 79.8654m, IsDeviationFlagged = true, CheckOutAt = null, CreatedAt = new DateTime(2026, 8, 25, 10, 0, 0), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<UserRegionScope>().HasData(
|
||||
new UserRegionScope { Id = UserRegionScope1Id, FK_UserId = MasterDataSeed.RegionalManager1UserId, FK_RegionId = MasterDataSeed.WesternRegionId, CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<UserRouteScope>().HasData(
|
||||
new UserRouteScope { Id = UserRouteScope1Id, FK_UserId = MasterDataSeed.Supervisor1UserId, FK_RouteId = MasterDataSeed.ColomboRouteId, CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<UserStoreScope>().HasData(
|
||||
new UserStoreScope { Id = UserStoreScope1Id, FK_UserId = MasterDataSeed.SalesRep2UserId, FK_StoreId = MasterDataSeed.KandyStoreId, CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SaleRoute>().HasData(
|
||||
new SaleRoute { Id = SaleRoute1Id, CreatedAt = new DateTime(2026, 8, 20), IsActive = true },
|
||||
new SaleRoute { Id = SaleRoute2Id, CreatedAt = new DateTime(2026, 8, 20), IsActive = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data.Seed;
|
||||
|
||||
public static class ProcurementSeed
|
||||
{
|
||||
private static readonly Guid Supplier1Id = new("38000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid Supplier2Id = new("38000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid PurchaseOrder1Id = new("41000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid PurchaseOrder2Id = new("41000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid PurchaseOrderItem1Id = new("42000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid PurchaseOrderItem2Id = new("42000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid PurchaseOrderItem3Id = new("42000000-0000-0000-0000-000000000003");
|
||||
|
||||
internal static readonly Guid GoodsReceipt1Id = new("43000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid GoodsReceiptItem1Id = new("44000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid GoodsReceiptItem2Id = new("44000000-0000-0000-0000-000000000002");
|
||||
|
||||
private static readonly Guid PurchaseReturn1Id = new("45000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid PurchaseReturnItem1Id = new("46000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid SupplierClaim1Id = new("55000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid SupplierClaimItem1Id = new("56000000-0000-0000-0000-000000000001");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Supplier>().HasData(
|
||||
new Supplier { Id = Supplier1Id, Name = "Ceylon Beverages Ltd", ContactInfo = "+94 11 234 5678", PerformanceScore = 4.2m, CreatedAt = new DateTime(2026, 8, 14), IsActive = true },
|
||||
new Supplier { Id = Supplier2Id, Name = "Snack Foods (Pvt) Ltd", ContactInfo = "+94 11 876 5432", PerformanceScore = null, CreatedAt = new DateTime(2026, 8, 14), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrder>().HasData(
|
||||
new PurchaseOrder { Id = PurchaseOrder1Id, FK_SupplierId = Supplier1Id, FK_CreatedByUserId = MasterDataSeed.Supervisor1UserId, Status = PurchaseOrderStatus.Received, CancelReason = null, CreatedAt = new DateTime(2026, 8, 14), IsActive = true },
|
||||
new PurchaseOrder { Id = PurchaseOrder2Id, FK_SupplierId = Supplier2Id, FK_CreatedByUserId = MasterDataSeed.Supervisor1UserId, Status = PurchaseOrderStatus.Received, CancelReason = null, CreatedAt = new DateTime(2026, 8, 14), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrderItem>().HasData(
|
||||
new PurchaseOrderItem { Id = PurchaseOrderItem1Id, FK_PurchaseOrderId = PurchaseOrder1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, Quantity = 200, UnitCost = 40m, ReceivedQuantity = 200, CancelledQuantity = 0, IsCancelled = false, CreatedAt = new DateTime(2026, 8, 14), IsActive = true },
|
||||
new PurchaseOrderItem { Id = PurchaseOrderItem2Id, FK_PurchaseOrderId = PurchaseOrder1Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 100, UnitCost = 80m, ReceivedQuantity = 98, CancelledQuantity = 0, IsCancelled = false, CreatedAt = new DateTime(2026, 8, 14), IsActive = true },
|
||||
new PurchaseOrderItem { Id = PurchaseOrderItem3Id, FK_PurchaseOrderId = PurchaseOrder2Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Quantity = 150, UnitCost = 35m, ReceivedQuantity = 150, CancelledQuantity = 0, IsCancelled = false, CreatedAt = new DateTime(2026, 8, 14), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<GoodsReceipt>().HasData(
|
||||
new GoodsReceipt { Id = GoodsReceipt1Id, FK_PurchaseOrderId = PurchaseOrder1Id, FK_ReceivedByUserId = MasterDataSeed.Supervisor1UserId, FK_WarehouseId = MasterDataSeed.ColomboWarehouseId, ReceivedAt = new DateTime(2026, 8, 15), CreatedAt = new DateTime(2026, 8, 15), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>().HasData(
|
||||
new GoodsReceiptItem { Id = GoodsReceiptItem1Id, FK_GoodsReceiptId = GoodsReceipt1Id, FK_PurchaseOrderItemId = PurchaseOrderItem1Id, AcceptedQuantity = 200, RejectedQuantity = 0, CreatedAt = new DateTime(2026, 8, 15), IsActive = true },
|
||||
new GoodsReceiptItem { Id = GoodsReceiptItem2Id, FK_GoodsReceiptId = GoodsReceipt1Id, FK_PurchaseOrderItemId = PurchaseOrderItem2Id, AcceptedQuantity = 98, RejectedQuantity = 2, CreatedAt = new DateTime(2026, 8, 15), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<PurchaseReturn>().HasData(
|
||||
new PurchaseReturn { Id = PurchaseReturn1Id, FK_PurchaseOrderId = PurchaseOrder2Id, Reason = "Damaged in transit", CreatedAt = new DateTime(2026, 8, 16), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<PurchaseReturnItem>().HasData(
|
||||
new PurchaseReturnItem { Id = PurchaseReturnItem1Id, FK_PurchaseReturnId = PurchaseReturn1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Quantity = 10, CreatedAt = new DateTime(2026, 8, 16), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SupplierClaim>().HasData(
|
||||
new SupplierClaim
|
||||
{
|
||||
Id = SupplierClaim1Id,
|
||||
FK_PurchaseOrderId = PurchaseOrder1Id,
|
||||
FK_GoodsReceiptId = GoodsReceipt1Id,
|
||||
Details = "2 units of Coke 1.5L received damaged",
|
||||
EvidenceUrl = null,
|
||||
Status = SupplierClaimStatus.Approved,
|
||||
ResolutionType = SupplierClaimResolutionType.Credit,
|
||||
ResolutionAmount = 160m,
|
||||
ResolutionNotes = "Credited against next invoice",
|
||||
RaisedByUserId = MasterDataSeed.Supervisor1UserId,
|
||||
ResolvedByUserId = MasterDataSeed.RegionalManager1UserId,
|
||||
ResolvedAt = new DateTime(2026, 8, 16),
|
||||
CreatedAt = new DateTime(2026, 8, 15),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SupplierClaimItem>().HasData(
|
||||
new SupplierClaimItem { Id = SupplierClaimItem1Id, FK_SupplierClaimId = SupplierClaim1Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 2m, IssueType = SupplierClaimIssueType.Damaged, CreatedAt = new DateTime(2026, 8, 15), IsActive = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,140 +7,212 @@ public static class RoleAndPermissionSeed
|
||||
{
|
||||
private static readonly DateTime SeedDate = new(2026, 8, 18);
|
||||
|
||||
private static readonly Guid SalesRepId = new("10000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid SupervisorId = new("10000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid AreaManagerId = new("10000000-0000-0000-0000-000000000003");
|
||||
private static readonly Guid AreaSupervisorId = new("10000000-0000-0000-0000-000000000004");
|
||||
private static readonly Guid RegionalManagerId = new("10000000-0000-0000-0000-000000000005");
|
||||
private static readonly Guid RegionalAgencyId = new("10000000-0000-0000-0000-000000000006");
|
||||
private static readonly Guid SalesRepId = new("10000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid SupervisorId = new("10000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid AreaManagerId = new("10000000-0000-0000-0000-000000000003");
|
||||
private static readonly Guid AreaSupervisorId = new("10000000-0000-0000-0000-000000000004");
|
||||
private static readonly Guid RegionalManagerId = new("10000000-0000-0000-0000-000000000005");
|
||||
private static readonly Guid RegionalAgencyId = new("10000000-0000-0000-0000-000000000006");
|
||||
internal static readonly Guid SuperAdminRoleId = new("10000000-0000-0000-0000-000000000007");
|
||||
|
||||
private static readonly (string Code, string Description) [] PermissionDefs =
|
||||
[
|
||||
("regions.view", "View regions"),
|
||||
("regions.create", "Create regions"),
|
||||
("regions.update", "Update regions"),
|
||||
("regions.delete", "Delete regions"),
|
||||
// ── Legacy broad permissions (nav.ts uses these for sidebar visibility) ────
|
||||
private static readonly Guid PermOrdersView = new("20000000-0000-0000-0000-000000000001");
|
||||
private static readonly Guid PermRidesView = new("20000000-0000-0000-0000-000000000002");
|
||||
private static readonly Guid PermLoadingView = new("20000000-0000-0000-0000-000000000003");
|
||||
private static readonly Guid PermStoresView = new("20000000-0000-0000-0000-000000000004");
|
||||
private static readonly Guid PermSalesVisitsCheckin = new("20000000-0000-0000-0000-000000000005");
|
||||
private static readonly Guid PermItemsView = new("20000000-0000-0000-0000-000000000006");
|
||||
private static readonly Guid PermCategoriesView = new("20000000-0000-0000-0000-000000000007");
|
||||
private static readonly Guid PermWarehousesView = new("20000000-0000-0000-0000-000000000008");
|
||||
private static readonly Guid PermPurchaseOrdersView = new("20000000-0000-0000-0000-000000000009");
|
||||
private static readonly Guid PermPricelistsView = new("20000000-0000-0000-0000-000000000010");
|
||||
private static readonly Guid PermSchemesView = new("20000000-0000-0000-0000-000000000011");
|
||||
private static readonly Guid PermSuppliersView = new("20000000-0000-0000-0000-000000000012");
|
||||
private static readonly Guid PermDriverProfilesView = new("20000000-0000-0000-0000-000000000013");
|
||||
private static readonly Guid PermComplianceManage = new("20000000-0000-0000-0000-000000000014");
|
||||
private static readonly Guid PermUsersView = new("20000000-0000-0000-0000-000000000015");
|
||||
private static readonly Guid PermRolesView = new("20000000-0000-0000-0000-000000000016");
|
||||
private static readonly Guid PermRegionsView = new("20000000-0000-0000-0000-000000000017");
|
||||
private static readonly Guid PermRoutesView = new("20000000-0000-0000-0000-000000000018");
|
||||
private static readonly Guid PermVehiclesView = new("20000000-0000-0000-0000-000000000019");
|
||||
|
||||
("routes.view", "View routes"),
|
||||
("routes.create", "Create routes"),
|
||||
("routes.update", "Update routes"),
|
||||
("routes.delete", "Delete routes"),
|
||||
|
||||
("vehicles.view", "View vehicles"),
|
||||
("vehicles.create", "Create vehicles"),
|
||||
("vehicles.update", "Update vehicles"),
|
||||
("vehicles.delete", "Delete vehicles"),
|
||||
|
||||
("stores.view", "View stores"),
|
||||
("stores.create", "Create stores"),
|
||||
("stores.update", "Update stores"),
|
||||
("stores.delete", "Delete stores"),
|
||||
|
||||
("warehouses.view", "View warehouses"),
|
||||
("warehouses.create", "Create warehouses"),
|
||||
("warehouses.update", "Update warehouses"),
|
||||
("warehouses.delete", "Delete warehouses"),
|
||||
|
||||
("orders.view", "View orders"),
|
||||
("orders.create", "Create orders"),
|
||||
("orders.update", "Update orders"),
|
||||
("orders.delete", "Delete orders"),
|
||||
|
||||
("users.view", "View users"),
|
||||
("users.create", "Create users"),
|
||||
("users.update", "Update users"),
|
||||
("users.deactivate", "Deactivate users"),
|
||||
("users.assign_role", "Assign a role to a user"),
|
||||
("users.assign_scope", "Assign region/route/store scope to a user"),
|
||||
|
||||
("items.view", "View items, brands and UOMs"),
|
||||
("items.create", "Create items, brands and UOMs"),
|
||||
("items.update", "Update items, brands and UOMs"),
|
||||
("items.delete", "Delete items, brands and UOMs"),
|
||||
|
||||
("categories.view", "View categories"),
|
||||
("categories.create", "Create categories and subcategories"),
|
||||
("categories.update", "Update categories and subcategories"),
|
||||
("categories.delete", "Delete categories and subcategories"),
|
||||
];
|
||||
|
||||
private static readonly Dictionary<string, Guid> PermissionIds = PermissionDefs
|
||||
.Select((p, i) => (p.Code, Id: DeterministicPermissionId(i)))
|
||||
.ToDictionary(x => x.Code, x => x.Id);
|
||||
|
||||
private static Guid DeterministicPermissionId(int index) =>
|
||||
new($"20000000-0000-0000-0000-{index + 1:D12}");
|
||||
// ── Granular API permissions (used in [Authorize("...")] on controller actions) ─
|
||||
private static readonly Guid PermClaimView = new("20000000-0000-0000-0000-000000000020");
|
||||
private static readonly Guid PermClaimManage = new("20000000-0000-0000-0000-000000000021");
|
||||
private static readonly Guid PermDeliveryTaskView = new("20000000-0000-0000-0000-000000000022");
|
||||
private static readonly Guid PermDeliveryTaskManage = new("20000000-0000-0000-0000-000000000023");
|
||||
private static readonly Guid PermDriverView = new("20000000-0000-0000-0000-000000000024");
|
||||
private static readonly Guid PermDriverManage = new("20000000-0000-0000-0000-000000000025");
|
||||
private static readonly Guid PermGrnView = new("20000000-0000-0000-0000-000000000026");
|
||||
private static readonly Guid PermGrnManage = new("20000000-0000-0000-0000-000000000027");
|
||||
private static readonly Guid PermLoadingSheetView = new("20000000-0000-0000-0000-000000000028");
|
||||
private static readonly Guid PermLoadingSheetManage = new("20000000-0000-0000-0000-000000000029");
|
||||
private static readonly Guid PermPriceListView = new("20000000-0000-0000-0000-000000000030");
|
||||
private static readonly Guid PermPriceListManage = new("20000000-0000-0000-0000-000000000031");
|
||||
private static readonly Guid PermPurchaseOrderView = new("20000000-0000-0000-0000-000000000032");
|
||||
private static readonly Guid PermPurchaseOrderManage= new("20000000-0000-0000-0000-000000000033");
|
||||
private static readonly Guid PermPurchaseReturnManage=new("20000000-0000-0000-0000-000000000034");
|
||||
private static readonly Guid PermRegionView = new("20000000-0000-0000-0000-000000000035");
|
||||
private static readonly Guid PermRegionManage = new("20000000-0000-0000-0000-000000000036");
|
||||
private static readonly Guid PermReturnView = new("20000000-0000-0000-0000-000000000037");
|
||||
private static readonly Guid PermReturnManage = new("20000000-0000-0000-0000-000000000038");
|
||||
private static readonly Guid PermRouteView = new("20000000-0000-0000-0000-000000000039");
|
||||
private static readonly Guid PermRouteManage = new("20000000-0000-0000-0000-000000000040");
|
||||
private static readonly Guid PermSchemeView = new("20000000-0000-0000-0000-000000000041");
|
||||
private static readonly Guid PermSchemeManage = new("20000000-0000-0000-0000-000000000042");
|
||||
private static readonly Guid PermStockView = new("20000000-0000-0000-0000-000000000043");
|
||||
private static readonly Guid PermStockManage = new("20000000-0000-0000-0000-000000000044");
|
||||
private static readonly Guid PermStoreView = new("20000000-0000-0000-0000-000000000045");
|
||||
private static readonly Guid PermStoreManage = new("20000000-0000-0000-0000-000000000046");
|
||||
private static readonly Guid PermSupplierClaimView = new("20000000-0000-0000-0000-000000000047");
|
||||
private static readonly Guid PermSupplierClaimManage= new("20000000-0000-0000-0000-000000000048");
|
||||
private static readonly Guid PermSysConfigView = new("20000000-0000-0000-0000-000000000049");
|
||||
private static readonly Guid PermSysConfigManage = new("20000000-0000-0000-0000-000000000050");
|
||||
private static readonly Guid PermExpenseView = new("20000000-0000-0000-0000-000000000051");
|
||||
private static readonly Guid PermExpenseCreate = new("20000000-0000-0000-0000-000000000052");
|
||||
private static readonly Guid PermExpensePost = new("20000000-0000-0000-0000-000000000053");
|
||||
private static readonly Guid PermExpensePay = new("20000000-0000-0000-0000-000000000054");
|
||||
private static readonly Guid PermExpenseVoid = new("20000000-0000-0000-0000-000000000055");
|
||||
private static readonly Guid PermExpenseManageCats = new("20000000-0000-0000-0000-000000000056");
|
||||
private static readonly Guid PermExpenseManageRec = new("20000000-0000-0000-0000-000000000057");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Role>().HasData(
|
||||
new Role { Id = SalesRepId, Name = "SalesRep", Level = 10, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = SupervisorId, Name = "Supervisor", Level = 20, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = AreaManagerId, Name = "AreaManager", Level = 30, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = AreaSupervisorId, Name = "AreaSupervisor", Level = 40, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = RegionalManagerId, Name = "RegionalManager", Level = 50, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = RegionalAgencyId, Name = "RegionalAgency", Level = 60, CreatedAt = SeedDate, IsActive = true }
|
||||
new Role { Id = SalesRepId, Name = "SalesRep", Level = 10, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = SupervisorId, Name = "Supervisor", Level = 20, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = AreaManagerId, Name = "AreaManager", Level = 30, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = AreaSupervisorId, Name = "AreaSupervisor", Level = 40, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = RegionalManagerId,Name = "RegionalManager",Level = 50, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = RegionalAgencyId, Name = "RegionalAgency", Level = 60, CreatedAt = SeedDate, IsActive = true },
|
||||
new Role { Id = SuperAdminRoleId, Name = "SuperAdmin", Level = 100, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Permission>().HasData(
|
||||
PermissionDefs.Select(p => new Permission
|
||||
{
|
||||
Id = PermissionIds[p.Code],
|
||||
Code = p.Code,
|
||||
Description = p.Description,
|
||||
CreatedAt = SeedDate,
|
||||
IsActive = true
|
||||
})
|
||||
// ── Legacy broad permissions ──────────────────────────────────────────
|
||||
new Permission { Id = PermOrdersView, Code = "orders.view", Description = "View shop POs, invoices, payments and returns", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRidesView, Code = "rides.view", Description = "View and manage delivery rides and delivery tasks", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermLoadingView, Code = "loading.view", Description = "View and manage loading sheets", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermStoresView, Code = "stores.view", Description = "View store accounts, credit limits and balances", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSalesVisitsCheckin, Code = "sales_visits.checkin", Description = "Check in and out of scheduled store visits", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermItemsView, Code = "items.view", Description = "View and manage catalogue products", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermCategoriesView, Code = "categories.view", Description = "View and manage product categories and brands", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermWarehousesView, Code = "warehouses.view", Description = "View stock balances, movements, warehouses and reports", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPurchaseOrdersView, Code = "purchase_orders.view", Description = "View and manage purchase orders, GRNs and supplier claims", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPricelistsView, Code = "pricelists.view", Description = "View and manage regional price lists", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSchemesView, Code = "schemes.view", Description = "View and manage pricing schemes and promotions", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSuppliersView, Code = "suppliers.view", Description = "View and manage supplier accounts", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermDriverProfilesView, Code = "driver_profiles.view", Description = "View and manage driver profiles and licensing", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermComplianceManage, Code = "compliance.manage", Description = "Manage driver compliance rules", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermUsersView, Code = "users.view", Description = "View and manage staff user accounts", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRolesView, Code = "roles.view", Description = "View and manage roles and their permission sets", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRegionsView, Code = "regions.view", Description = "View and manage regions and region configuration", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRoutesView, Code = "routes.view", Description = "View and manage delivery routes", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermVehiclesView, Code = "vehicles.view", Description = "View and manage fleet vehicles", CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
// ── Granular API permissions ──────────────────────────────────────────
|
||||
new Permission { Id = PermClaimView, Code = "claim.view", Description = "View agency claims and settlement status", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermClaimManage, Code = "claim.manage", Description = "Create and settle agency claims", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermDeliveryTaskView, Code = "delivery-task.view", Description = "View delivery tasks within a ride", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermDeliveryTaskManage, Code = "delivery-task.manage", Description = "Confirm and fail delivery tasks", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermDriverView, Code = "driver.view", Description = "View driver profiles and license details", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermDriverManage, Code = "driver.manage", Description = "Create and update driver profiles", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermGrnView, Code = "grn.view", Description = "View goods receipt notes", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermGrnManage, Code = "grn.manage", Description = "Create and confirm goods receipts", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermLoadingSheetView, Code = "loading-sheet.view", Description = "View loading sheets and their items", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermLoadingSheetManage, Code = "loading-sheet.manage", Description = "Create, edit, and confirm loading sheets", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPriceListView, Code = "price-list.view", Description = "View regional price lists", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPriceListManage, Code = "price-list.manage", Description = "Create and manage item price lists", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPurchaseOrderView, Code = "purchase-order.view", Description = "View purchase orders and line items", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPurchaseOrderManage,Code = "purchase-order.manage", Description = "Create, confirm, and close purchase orders", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermPurchaseReturnManage,Code= "purchase-return.manage",Description = "Create and manage purchase returns to suppliers", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRegionView, Code = "region.view", Description = "View regions and their configuration", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRegionManage, Code = "region.manage", Description = "Create and update regions, targets, and budgets", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermReturnView, Code = "return.view", Description = "View order returns", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermReturnManage, Code = "return.manage", Description = "Create and manage order returns", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRouteView, Code = "route.view", Description = "View delivery routes", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermRouteManage, Code = "route.manage", Description = "Create and manage delivery routes", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSchemeView, Code = "scheme.view", Description = "View pricing schemes and promotions", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSchemeManage, Code = "scheme.manage", Description = "Create and configure pricing schemes", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermStockView, Code = "stock.view", Description = "View stock balances and movements", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermStockManage, Code = "stock.manage", Description = "Transfer stock and record damage or adjustments", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermStoreView, Code = "store.view", Description = "View store accounts and balances", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermStoreManage, Code = "store.manage", Description = "Create and update store accounts", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSupplierClaimView, Code = "supplier-claim.view", Description = "View supplier claims", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSupplierClaimManage,Code = "supplier-claim.manage", Description = "Create and resolve supplier claims", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSysConfigView, Code = "sys-config.view", Description = "View system configuration settings", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSysConfigManage, Code = "sys-config.manage", Description = "Update system configuration settings", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpenseView, Code = "expense.view", Description = "View expenses, categories, vendors and recurring templates", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpenseCreate, Code = "expense.create", Description = "Create and edit draft expenses", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpensePost, Code = "expense.post", Description = "Post a draft expense (approve it)", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpensePay, Code = "expense.pay", Description = "Pay a posted expense (debit a bank account or issue a cheque)", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpenseVoid, Code = "expense.void", Description = "Void an expense", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpenseManageCats, Code = "expense.manage_categories", Description = "Manage expense categories, titles and vendors", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermExpenseManageRec, Code = "expense.manage_recurring", Description = "Manage recurring expense templates", CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
var itemViewPerms = new[] { "items.view", "categories.view" };
|
||||
var itemManagePerms = new[] { "items.view", "items.create", "items.update", "categories.view" };
|
||||
var itemPerms = new[] { "items.view", "items.create", "items.update", "items.delete", "categories.view", "categories.create", "categories.update", "categories.delete" };
|
||||
|
||||
var salesRepPerms = new[] { "orders.view", "orders.create", "orders.update", "stores.view", "routes.view" }.Concat(itemViewPerms).ToArray();
|
||||
var supervisorPerms = salesRepPerms.Concat(["orders.delete", "stores.create", "stores.update"]).ToArray();
|
||||
var areaManagerPerms = supervisorPerms.Concat(["vehicles.view", "warehouses.view"]).Concat(itemManagePerms).Distinct().ToArray();
|
||||
var areaSupervisorPerms = areaManagerPerms.Concat(["routes.create", "routes.update"]).Concat(itemPerms).Distinct().ToArray();
|
||||
|
||||
var regionalManagerPerms = new[]
|
||||
{
|
||||
"orders.view", "orders.create", "orders.update", "orders.delete",
|
||||
"stores.view", "stores.create", "stores.update", "stores.delete",
|
||||
"routes.view", "routes.create", "routes.update", "routes.delete",
|
||||
"vehicles.view", "vehicles.create", "vehicles.update", "vehicles.delete",
|
||||
"warehouses.view", "warehouses.create", "warehouses.update", "warehouses.delete",
|
||||
"regions.view", "users.view",
|
||||
"items.view", "items.create", "items.update", "items.delete",
|
||||
"categories.view", "categories.create", "categories.update", "categories.delete"
|
||||
};
|
||||
var regionalAgencyPerms = PermissionDefs.Select(p => p.Code).ToArray();
|
||||
|
||||
var rolePermissionMap = new (Guid RoleId, string[] Codes)[]
|
||||
{
|
||||
(SalesRepId, salesRepPerms),
|
||||
(SupervisorId, supervisorPerms),
|
||||
(AreaManagerId, areaManagerPerms),
|
||||
(AreaSupervisorId, areaSupervisorPerms),
|
||||
(RegionalManagerId, regionalManagerPerms),
|
||||
(RegionalAgencyId, regionalAgencyPerms),
|
||||
};
|
||||
|
||||
// SuperAdmin gets every permission
|
||||
modelBuilder.Entity<RolePermission>().HasData(
|
||||
rolePermissionMap.SelectMany(rp => rp.Codes.Select(code => new RolePermission
|
||||
{
|
||||
Id = DeterministicRolePermissionId(rp.RoleId, PermissionIds[code]),
|
||||
FK_RoleId = rp.RoleId,
|
||||
FK_PermissionId = PermissionIds[code],
|
||||
CreatedAt = SeedDate,
|
||||
IsActive = true
|
||||
}))
|
||||
);
|
||||
}
|
||||
// ── Legacy broad permissions ──────────────────────────────────────────
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermOrdersView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRidesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermLoadingView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermStoresView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSalesVisitsCheckin, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermItemsView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermCategoriesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermWarehousesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPurchaseOrdersView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPricelistsView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSchemesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSuppliersView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermDriverProfilesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermComplianceManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermUsersView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRolesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRegionsView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRoutesView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermVehiclesView, CreatedAt = SeedDate, IsActive = true },
|
||||
|
||||
private static Guid DeterministicRolePermissionId(Guid roleId, Guid permissionId)
|
||||
{
|
||||
using var md5 = System.Security.Cryptography.MD5.Create();
|
||||
var bytes = md5.ComputeHash(roleId.ToByteArray().Concat(permissionId.ToByteArray()).ToArray());
|
||||
return new Guid(bytes);
|
||||
// ── Granular API permissions ──────────────────────────────────────────
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermClaimView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermClaimManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermDeliveryTaskView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermDeliveryTaskManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermDriverView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermDriverManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermGrnView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermGrnManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermLoadingSheetView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermLoadingSheetManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPriceListView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPriceListManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPurchaseOrderView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPurchaseOrderManage,CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermPurchaseReturnManage,CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRegionView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRegionManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermReturnView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermReturnManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRouteView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermRouteManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSchemeView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSchemeManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermStockView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermStockManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermStoreView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermStoreManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSupplierClaimView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSupplierClaimManage,CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseCreate, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpensePost, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpensePay, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseVoid, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseManageCats, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseManageRec, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,100 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using Distribution.Features.User;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController(AuthService service) : ControllerBase
|
||||
public class AuthController(AuthService service, ICurrentUserService currentUser, IUserRepository userRepo) : ControllerBase
|
||||
{
|
||||
private static IActionResult ToActionResult(AuthHexResponse result) =>
|
||||
result.Success ? new OkObjectResult(result) : new ObjectResult(result) { StatusCode = result.StatusCode is >= 400 and < 600 ? result.StatusCode : 401 };
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login(LoginRequest request)
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(LoginRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.LoginAsync(request);
|
||||
return result is null ? Unauthorized() : Ok(result);
|
||||
var result = await service.LoginAsync(request, ct);
|
||||
|
||||
if (!result.Success || result.Data is not { } data)
|
||||
return ToActionResult(result);
|
||||
|
||||
string? token = null;
|
||||
TimeSpan maxAge = TimeSpan.FromHours(1);
|
||||
|
||||
if (data.TryGetProperty("accessToken", out var tokenEl))
|
||||
token = tokenEl.GetString();
|
||||
|
||||
if (data.TryGetProperty("expiresIn", out var expEl))
|
||||
maxAge = TimeSpan.FromSeconds(expEl.GetInt32());
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
Response.Cookies.Append("access-token", token, new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Path = "/",
|
||||
MaxAge = maxAge,
|
||||
Secure = Request.IsHttps,
|
||||
});
|
||||
}
|
||||
|
||||
MeResponse? profile = null;
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
|
||||
var userIdStr = jwt.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;
|
||||
if (Guid.TryParse(userIdStr, out var userId))
|
||||
{
|
||||
var user = await userRepo.GetByIdAsync(userId);
|
||||
if (user is not null)
|
||||
profile = new MeResponse(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.FK_RoleId,
|
||||
user.Role?.Name ?? string.Empty,
|
||||
user.Role?.Level ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new { result.StatusCode, result.Success, result.Message, result.Data, profile });
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(RegisterRequest request, CancellationToken ct) =>
|
||||
ToActionResult(await service.RegisterAsync(request, ct));
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Me()
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
var user = await userRepo.GetByIdAsync(currentUser.UserId.Value);
|
||||
if (user is null) return Unauthorized();
|
||||
return Ok(new MeResponse(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.FK_RoleId,
|
||||
user.Role?.Name ?? string.Empty,
|
||||
user.Role?.Level ?? 0));
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Logout()
|
||||
{
|
||||
Response.Cookies.Delete("access-token", new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Path = "/",
|
||||
Secure = Request.IsHttps,
|
||||
});
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public record LoginRequest(string Username, string Password);
|
||||
public record LoginRequest(
|
||||
string Identifier,
|
||||
string Password,
|
||||
Guid? UserTypeId = null,
|
||||
string DeviceName = "Unknown Device");
|
||||
|
||||
public record LoginResponse(string Token, Guid UserId, string Username, string RoleName);
|
||||
public record RegisterRequest(string Username, string email, string Password, string? FullName);
|
||||
|
||||
public record MeResponse(
|
||||
Guid UserId,
|
||||
string Username,
|
||||
Guid RoleId,
|
||||
string RoleCode,
|
||||
int RoleLevel);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Proxies own-user auth calls (login/register/getUserDetails/logout) to the external AuthHex
|
||||
/// identity service. AuthHex owns credentials and issues the RS256-signed JWT this app validates;
|
||||
/// this app never sees or stores a plaintext/hashed password.
|
||||
/// </summary>
|
||||
public class AuthHexClient(HttpClient http) : IAuthHexClient
|
||||
{
|
||||
public async Task<AuthHexResponse> CallAsync(string functionName, object? payload, string? reference = null, CancellationToken ct = default)
|
||||
{
|
||||
var body = new { functionName, payload, reference };
|
||||
var response = await http.PostAsJsonAsync("/api/user", body, ct);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AuthHexResponse>(cancellationToken: ct);
|
||||
return result ?? new AuthHexResponse((int)response.StatusCode, false, "Empty response from AuthHex.", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>Raw response shape returned by AuthHex's `/api/user` endpoint for every functionName.</summary>
|
||||
public record AuthHexResponse(
|
||||
[property: JsonPropertyName("statusCode")] int StatusCode,
|
||||
[property: JsonPropertyName("success")] bool Success,
|
||||
[property: JsonPropertyName("message")] string? Message,
|
||||
[property: JsonPropertyName("data")] JsonElement? Data);
|
||||
@@ -1,24 +1,28 @@
|
||||
using Distribution.Features.User;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public class AuthService(IUserRepository userRepo, IJwtTokenService tokenService)
|
||||
/// <summary>
|
||||
/// Own-user auth is fully delegated to AuthHex — this service only proxies the four functions
|
||||
/// AuthHex exposes (loginUser/registerUser/getUserDetails/LogoutUser). No local password check or
|
||||
/// token issuance happens here; AuthHex owns credentials and signs the JWT this app validates.
|
||||
/// </summary>
|
||||
public class AuthService(IAuthHexClient authHex)
|
||||
{
|
||||
private readonly PasswordHasher<Models.User> _hasher = new();
|
||||
public Task<AuthHexResponse> LoginAsync(LoginRequest request, CancellationToken ct = default) =>
|
||||
authHex.CallAsync("loginUser", new
|
||||
{
|
||||
identifier = request.Identifier,
|
||||
password = request.Password,
|
||||
userTypeId = request.UserTypeId,
|
||||
deviceName = request.DeviceName
|
||||
}, reference: Guid.NewGuid().ToString(), ct: ct);
|
||||
|
||||
public async Task<LoginResponse?> LoginAsync(LoginRequest request)
|
||||
{
|
||||
var user = await userRepo.GetByUsernameAsync(request.Username);
|
||||
if (user is null || !user.IsActive)
|
||||
return null;
|
||||
public Task<AuthHexResponse> RegisterAsync(RegisterRequest request, CancellationToken ct = default) =>
|
||||
authHex.CallAsync("registerUser", request, reference: Guid.NewGuid().ToString(), ct: ct);
|
||||
|
||||
var result = _hasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
|
||||
if (result == PasswordVerificationResult.Failed)
|
||||
return null;
|
||||
public Task<AuthHexResponse> GetUserDetailsAsync(Guid userId, CancellationToken ct = default) =>
|
||||
authHex.CallAsync("getUserDetails", new { userId }, ct: ct);
|
||||
|
||||
var token = tokenService.GenerateToken(user);
|
||||
return new LoginResponse(token, user.Id, user.Username, user.Role?.Name ?? string.Empty);
|
||||
}
|
||||
public Task<AuthHexResponse> LogoutAsync(string token, CancellationToken ct = default) =>
|
||||
authHex.CallAsync("LogoutUser", new { token }, ct: ct);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public interface IAuthHexClient
|
||||
{
|
||||
Task<AuthHexResponse> CallAsync(string functionName, object? payload, string? reference = null, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class BankAccountController(BankAccountService service) : ControllerBase
|
||||
{
|
||||
[HttpGet("bank-accounts")]
|
||||
public async Task<IActionResult> List([FromQuery] BankAccountType? accountType)
|
||||
{
|
||||
var result = await service.ListAsync(accountType);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("bank-accounts")]
|
||||
public async Task<IActionResult> CreateBank(CreateBankAccountRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateBankAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("cash-accounts")]
|
||||
public async Task<IActionResult> CreateCash(CreateCashAccountRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateCashAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("cash-accounts/{id:guid}")]
|
||||
public async Task<IActionResult> UpdateCash(Guid id, UpdateCashAccountRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateCashAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("cash-accounts/{id:guid}/status")]
|
||||
public async Task<IActionResult> SetCashStatus(Guid id, SetCashAccountStatusRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.SetCashStatusAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpDelete("cash-accounts/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteCash(Guid id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = await service.DeleteCashAsync(id, ct);
|
||||
return ok ? NoContent() : NotFound();
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("cash-account-types")]
|
||||
public async Task<IActionResult> ListCashAccountTypes() => Ok(await service.ListCashAccountTypesAsync());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public record BankAccountResponse(
|
||||
BankAccountType AccountType,
|
||||
Guid AccountId,
|
||||
string AccountName,
|
||||
string? BankName,
|
||||
string? CashAccountTypeName,
|
||||
string? AccountNumber,
|
||||
string CurrencyCode,
|
||||
DateTime CreatedAt,
|
||||
bool? IsActive,
|
||||
bool? IsDefault);
|
||||
|
||||
public record CreateBankAccountRequest(
|
||||
string AccountName,
|
||||
string? BankName,
|
||||
string? AccountNumber,
|
||||
string? CurrencyCode);
|
||||
|
||||
public record CreateCashAccountRequest(
|
||||
string AccountName,
|
||||
string CashAccountTypeName,
|
||||
string? AccountNumber,
|
||||
string? CurrencyCode);
|
||||
|
||||
public record UpdateCashAccountRequest(string AccountName);
|
||||
|
||||
public record SetCashAccountStatusRequest(bool IsActive);
|
||||
|
||||
public record CashAccountTypeDto(string Name);
|
||||
@@ -0,0 +1,40 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public class BankAccountRepository(AppDBContext context) : IBankAccountRepository
|
||||
{
|
||||
public async Task<Models.BankAccount?> GetByIdAsync(Guid id) =>
|
||||
await context.BankAccounts.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
public async Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType)
|
||||
{
|
||||
var query = context.BankAccounts.AsQueryable();
|
||||
if (accountType is not null)
|
||||
query = query.Where(a => a.AccountType == accountType);
|
||||
return await query.OrderByDescending(a => a.CreatedAt).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<string>> ListCashAccountTypeNamesAsync() =>
|
||||
await context.BankAccounts
|
||||
.Where(a => a.AccountType == BankAccountType.Cash && a.CashAccountTypeName != null)
|
||||
.Select(a => a.CashAccountTypeName!)
|
||||
.Distinct()
|
||||
.OrderBy(n => n)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null) =>
|
||||
await context.BankAccounts.AnyAsync(a =>
|
||||
a.AccountType == type &&
|
||||
a.AccountName == accountName &&
|
||||
(excludeId == null || a.Id != excludeId));
|
||||
|
||||
public async Task AddAsync(Models.BankAccount account) =>
|
||||
await context.BankAccounts.AddAsync(account);
|
||||
|
||||
public void Update(Models.BankAccount account) => context.BankAccounts.Update(account);
|
||||
|
||||
public void Remove(Models.BankAccount account) => context.BankAccounts.Remove(account);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public class BankAccountService(IBankAccountRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private const string DefaultCurrency = "LKR";
|
||||
|
||||
private static BankAccountResponse ToResponse(Models.BankAccount a) =>
|
||||
new(
|
||||
a.AccountType,
|
||||
a.Id,
|
||||
a.AccountName,
|
||||
a.AccountType == BankAccountType.Bank ? a.BankName : null,
|
||||
a.AccountType == BankAccountType.Cash ? a.CashAccountTypeName : null,
|
||||
a.AccountNumber,
|
||||
a.CurrencyCode,
|
||||
a.CreatedAt,
|
||||
a.AccountType == BankAccountType.Cash ? a.IsActive : null,
|
||||
a.AccountType == BankAccountType.Cash ? a.IsDefault : null);
|
||||
|
||||
public async Task<List<BankAccountResponse>> ListAsync(BankAccountType? accountType) =>
|
||||
(await repo.ListAsync(accountType)).Select(ToResponse).ToList();
|
||||
|
||||
public async Task<List<CashAccountTypeDto>> ListCashAccountTypesAsync() =>
|
||||
(await repo.ListCashAccountTypeNamesAsync()).Select(n => new CashAccountTypeDto(n)).ToList();
|
||||
|
||||
public async Task<BankAccountResponse> CreateBankAsync(CreateBankAccountRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.AccountName))
|
||||
throw new InvalidOperationException("AccountName is required");
|
||||
if (await repo.ExistsByNameAsync(BankAccountType.Bank, request.AccountName))
|
||||
throw new InvalidOperationException("A bank account with this name already exists");
|
||||
|
||||
var account = new Models.BankAccount
|
||||
{
|
||||
AccountType = BankAccountType.Bank,
|
||||
AccountName = request.AccountName,
|
||||
BankName = request.BankName,
|
||||
AccountNumber = request.AccountNumber,
|
||||
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
|
||||
IsActive = true,
|
||||
IsDefault = false
|
||||
};
|
||||
|
||||
await repo.AddAsync(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<BankAccountResponse> CreateCashAsync(CreateCashAccountRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.AccountName))
|
||||
throw new InvalidOperationException("AccountName is required");
|
||||
if (string.IsNullOrWhiteSpace(request.CashAccountTypeName))
|
||||
throw new InvalidOperationException("CashAccountTypeName is required");
|
||||
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName))
|
||||
throw new InvalidOperationException("A cash account with this name already exists");
|
||||
|
||||
var account = new Models.BankAccount
|
||||
{
|
||||
AccountType = BankAccountType.Cash,
|
||||
AccountName = request.AccountName,
|
||||
CashAccountTypeName = request.CashAccountTypeName,
|
||||
AccountNumber = request.AccountNumber,
|
||||
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
|
||||
IsActive = true,
|
||||
IsDefault = false
|
||||
};
|
||||
|
||||
await repo.AddAsync(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<BankAccountResponse?> UpdateCashAsync(Guid id, UpdateCashAccountRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var account = await repo.GetByIdAsync(id);
|
||||
if (account is null || account.AccountType != BankAccountType.Cash) return null;
|
||||
if (string.IsNullOrWhiteSpace(request.AccountName))
|
||||
throw new InvalidOperationException("AccountName is required");
|
||||
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName, excludeId: id))
|
||||
throw new InvalidOperationException("A cash account with this name already exists");
|
||||
|
||||
account.AccountName = request.AccountName;
|
||||
repo.Update(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<BankAccountResponse?> SetCashStatusAsync(Guid id, SetCashAccountStatusRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var account = await repo.GetByIdAsync(id);
|
||||
if (account is null || account.AccountType != BankAccountType.Cash) return null;
|
||||
if (account.IsDefault && !request.IsActive)
|
||||
throw new InvalidOperationException("The default cash account cannot be deactivated");
|
||||
|
||||
account.IsActive = request.IsActive;
|
||||
repo.Update(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCashAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var account = await repo.GetByIdAsync(id);
|
||||
if (account is null || account.AccountType != BankAccountType.Cash) return false;
|
||||
if (account.IsDefault)
|
||||
throw new InvalidOperationException("The default cash account cannot be deleted");
|
||||
|
||||
repo.Remove(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public interface IBankAccountRepository
|
||||
{
|
||||
Task<Models.BankAccount?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType);
|
||||
Task<List<string>> ListCashAccountTypeNamesAsync();
|
||||
Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null);
|
||||
Task AddAsync(Models.BankAccount account);
|
||||
void Update(Models.BankAccount account);
|
||||
void Remove(Models.BankAccount account);
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Brand;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/brands")]
|
||||
[Authorize]
|
||||
public class BrandController(BrandService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "items.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize(Policy = "items.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -22,7 +18,6 @@ public class BrandController(BrandService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "items.create")]
|
||||
public async Task<IActionResult> Create(CreateBrandRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -30,7 +25,6 @@ public class BrandController(BrandService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Policy = "items.update")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateBrandRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -38,7 +32,6 @@ public class BrandController(BrandService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Policy = "items.delete")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Distribution.Features.Brand;
|
||||
|
||||
public record CreateBrandRequest(string Name);
|
||||
public record UpdateBrandRequest(string Name);
|
||||
public record UpdateBrandRequest(string Name, bool IsActive = true);
|
||||
public record BrandResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
|
||||
|
||||
@@ -5,15 +5,16 @@ namespace Distribution.Features.Brand;
|
||||
public class BrandService(IBrandRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
public async Task<List<BrandResponse>> GetAllAsync() =>
|
||||
// Deleted brands stay IsActive=false but remain visible showing Inactive — delete
|
||||
// never removes the row, only marks it (same rule as Items/Stores/Categories).
|
||||
(await repo.GetAllAsync())
|
||||
.Where(b => b.IsActive)
|
||||
.Select(b => new BrandResponse(b.Id, b.Name, b.IsActive, b.CreatedAt))
|
||||
.ToList();
|
||||
|
||||
public async Task<BrandResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var brand = await repo.GetByIdAsync(id);
|
||||
if (brand is null || !brand.IsActive) return null;
|
||||
if (brand is null) return null;
|
||||
return new BrandResponse(brand.Id, brand.Name, brand.IsActive, brand.CreatedAt);
|
||||
}
|
||||
|
||||
@@ -29,9 +30,10 @@ public class BrandService(IBrandRepository repo, IUnitOfWork uow)
|
||||
public async Task<BrandResponse?> UpdateAsync(Guid id, UpdateBrandRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var brand = await repo.GetByIdAsync(id);
|
||||
if (brand is null || !brand.IsActive) return null;
|
||||
if (brand is null) return null;
|
||||
if (brand.ExternalErpId.HasValue) return null;
|
||||
brand.Name = request.Name;
|
||||
brand.IsActive = request.IsActive;
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(brand); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
@@ -40,10 +42,11 @@ public class BrandService(IBrandRepository repo, IUnitOfWork uow)
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
// Soft-delete only — never blocked by items still referencing this brand, same rule
|
||||
// as Categories.
|
||||
var brand = await repo.GetByIdAsync(id);
|
||||
if (brand is null || !brand.IsActive) return false;
|
||||
if (brand.ExternalErpId.HasValue) return false;
|
||||
if (await repo.HasActiveItemsAsync(id)) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Remove(brand); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user