Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2d9ba4183 | |||
| dd1d93d032 |
Binary file not shown.
Binary file not shown.
@@ -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+85a16396aad31f1e82cf28f330a97dee2eafa51d")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+aa7ea37ab61a149a38d83283c5dabd76bf5a39b3")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
100cae0f5643c7476420ef14e72b5b17dcb877fe4318c0b6cb2cff0baa955aa5
|
||||
5c052f1f25bc37b6a51a3abe0e2b79d99d21422d5ab89151faea450c0ddb9cc0
|
||||
|
||||
+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 = D:\HexDrive Projects\Distribution\ERP_Distribution\Distribution.Tests\
|
||||
build_property.ProjectDir = C:\Users\lenovo\Desktop\NEWGITEA\ERP_Distribution\Distribution.Tests\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 9.0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {}
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectUniqueName": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectName": "Distribution.Tests",
|
||||
"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\\",
|
||||
"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\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\dell\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Users\\lenovo\\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,7 +26,6 @@
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@@ -34,8 +33,8 @@
|
||||
"framework": "net9.0",
|
||||
"targetAlias": "net9.0",
|
||||
"projectReferences": {
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +49,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.400"
|
||||
"SdkAnalysisLevel": "10.0.300"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
@@ -98,24 +97,24 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.302/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj",
|
||||
"projectUniqueName": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj",
|
||||
"projectName": "Distribution",
|
||||
"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\\",
|
||||
"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\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\dell\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Users\\lenovo\\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"
|
||||
],
|
||||
@@ -124,7 +123,6 @@
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@@ -144,7 +142,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.400"
|
||||
"SdkAnalysisLevel": "10.0.300"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
@@ -193,7 +191,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.302/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\dell\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\lenovo\.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\dell\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Users\lenovo\.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\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>
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -2542,23 +2542,23 @@
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\dell\\.nuget\\packages\\": {},
|
||||
"C:\\Users\\lenovo\\.nuget\\packages\\": {},
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectUniqueName": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectName": "Distribution.Tests",
|
||||
"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\\",
|
||||
"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\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\dell\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Users\\lenovo\\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,7 +2567,6 @@
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@@ -2575,8 +2574,8 @@
|
||||
"framework": "net9.0",
|
||||
"targetAlias": "net9.0",
|
||||
"projectReferences": {
|
||||
"D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
"C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj": {
|
||||
"projectPath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution\\Distribution.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2591,7 +2590,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.400"
|
||||
"SdkAnalysisLevel": "10.0.300"
|
||||
},
|
||||
"frameworks": {
|
||||
"net9.0": {
|
||||
@@ -2639,7 +2638,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.302/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "JPCR3IFZHKY=",
|
||||
"dgSpecHash": "XdH8cqlThLk=",
|
||||
"success": true,
|
||||
"projectFilePath": "D:\\HexDrive Projects\\Distribution\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"projectFilePath": "C:\\Users\\lenovo\\Desktop\\NEWGITEA\\ERP_Distribution\\Distribution.Tests\\Distribution.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"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"
|
||||
"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"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
False
|
||||
@@ -1 +0,0 @@
|
||||
bf7d0202-9a86-43b3-8323-402073a8c61a
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,5 +0,0 @@
|
||||
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
|
||||
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"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
@@ -1 +0,0 @@
|
||||
10.0.300
|
||||
-1
@@ -1 +0,0 @@
|
||||
10.0.303.1
|
||||
@@ -17,10 +17,9 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<UomConversion> UomConversions { get; set; }
|
||||
public DbSet<Item> Items { get; set; }
|
||||
public DbSet<SaleRoute> SaleRoutes { get; set; }
|
||||
public DbSet<ShopPO> ShopPOs { get; set; }
|
||||
public DbSet<ShopPOItem> ShopPOItems { get; set; }
|
||||
public DbSet<Invoice> Invoices { get; set; }
|
||||
public DbSet<LoadingSheetOrder> LoadingSheetOrders { get; set; }
|
||||
public DbSet<Sale> Sales { get; set; }
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<OrderItem> OrderItems { get; set; }
|
||||
public DbSet<DeliveryRun> DeliveryRuns { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<Role> Roles { get; set; }
|
||||
@@ -37,6 +36,7 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<Supplier> Suppliers { get; set; }
|
||||
public DbSet<DriverProfile> DriverProfiles { get; set; }
|
||||
public DbSet<DriverComplianceConfig> DriverComplianceConfigs { get; set; }
|
||||
public DbSet<RideCancelRequest> RideCancelRequests { get; set; }
|
||||
public DbSet<Scheme> Schemes { get; set; }
|
||||
public DbSet<SchemeRedemption> SchemeRedemptions { get; set; }
|
||||
public DbSet<SchemeBundleComponent> SchemeBundleComponents { get; set; }
|
||||
@@ -61,11 +61,11 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<RegionAgencyMap> RegionAgencyMaps { get; set; }
|
||||
public DbSet<RegionalTaxRule> RegionalTaxRules { get; set; }
|
||||
public DbSet<RouteVisitLog> RouteVisitLogs { get; set; }
|
||||
public DbSet<SysConfig> SysConfigs { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// --- Composite PKs ---
|
||||
modelBuilder.Entity<OrderItem>()
|
||||
.HasKey(oi => new { oi.FK_OrderId, oi.FK_ItemId });
|
||||
|
||||
modelBuilder.Entity<RolePermission>()
|
||||
.HasKey(rp => new { rp.FK_RoleId, rp.FK_PermissionId });
|
||||
@@ -79,10 +79,11 @@ 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 ---
|
||||
modelBuilder.Entity<User>()
|
||||
.HasOne(u => u.ReportsTo)
|
||||
.WithMany()
|
||||
.HasForeignKey(u => u.FK_ReportsToUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<StockBalance>()
|
||||
.HasIndex(sb => new { sb.FK_ItemId, sb.LocationId })
|
||||
@@ -92,20 +93,14 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasIndex(pli => new { pli.FK_PriceListId, pli.FK_ItemId })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<Order>()
|
||||
.HasIndex(o => o.FK_SaleId)
|
||||
.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()
|
||||
@@ -113,7 +108,11 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- DeliveryRun ---
|
||||
modelBuilder.Entity<DeliveryRun>()
|
||||
.HasOne(r => r.SourceWarehouse)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_SourceWarehouseId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeliveryRun>()
|
||||
.HasOne(r => r.DriverProfile)
|
||||
@@ -121,97 +120,30 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasForeignKey(r => r.FK_DriverProfileId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- ShopPO ---
|
||||
|
||||
modelBuilder.Entity<ShopPO>()
|
||||
.HasOne(p => p.Store)
|
||||
modelBuilder.Entity<Sale>()
|
||||
.HasOne(s => s.Store)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_StoreId)
|
||||
.HasForeignKey(s => s.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)
|
||||
modelBuilder.Entity<Sale>()
|
||||
.HasOne(s => s.CreatedByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.HasForeignKey(s => s.CreatedByUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Invoice ---
|
||||
|
||||
modelBuilder.Entity<Invoice>()
|
||||
.HasOne(i => i.ShopPO)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOId)
|
||||
.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>()
|
||||
modelBuilder.Entity<Sale>()
|
||||
.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)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- SchemeRedemption ---
|
||||
modelBuilder.Entity<Order>()
|
||||
.HasOne(o => o.Sale)
|
||||
.WithMany()
|
||||
.HasForeignKey(o => o.FK_SaleId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>()
|
||||
.HasOne(r => r.Scheme)
|
||||
@@ -220,11 +152,10 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>()
|
||||
.HasOne(r => r.ShopPO)
|
||||
.HasOne(r => r.Order)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
.HasForeignKey(r => r.FK_OrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SchemeRedemption>()
|
||||
.HasOne(r => r.FreeItem)
|
||||
@@ -232,8 +163,6 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasForeignKey(r => r.FK_FreeItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Scheme ---
|
||||
|
||||
modelBuilder.Entity<Scheme>()
|
||||
.HasOne(s => s.TriggerItem)
|
||||
.WithMany()
|
||||
@@ -262,7 +191,23 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasIndex(c => new { c.FK_SchemeId, c.FK_ItemId })
|
||||
.IsUnique();
|
||||
|
||||
// --- PurchaseOrder ---
|
||||
modelBuilder.Entity<LoadingSheet>()
|
||||
.HasOne(s => s.SourceWarehouse)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_SourceWarehouseId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<LoadingSheet>()
|
||||
.HasOne(s => s.Ride)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.FK_RideId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<LoadingSheetItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrder>()
|
||||
.HasOne(po => po.Supplier)
|
||||
@@ -282,11 +227,6 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.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()
|
||||
@@ -311,14 +251,6 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasForeignKey(i => i.FK_PurchaseOrderItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasIndex(i => i.FK_GoodsReceiptId);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasIndex(i => i.FK_PurchaseOrderItemId);
|
||||
|
||||
// --- PurchaseReturn ---
|
||||
|
||||
modelBuilder.Entity<PurchaseReturn>()
|
||||
.HasOne(pr => pr.PurchaseOrder)
|
||||
.WithMany()
|
||||
@@ -337,15 +269,22 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<PurchaseOrderItem>()
|
||||
.HasIndex(i => i.FK_PurchaseOrderId);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasIndex(i => i.FK_GoodsReceiptId);
|
||||
|
||||
modelBuilder.Entity<GoodsReceiptItem>()
|
||||
.HasIndex(i => i.FK_PurchaseOrderItemId);
|
||||
|
||||
modelBuilder.Entity<PurchaseReturnItem>()
|
||||
.HasIndex(i => i.FK_PurchaseReturnId);
|
||||
|
||||
// --- SaleReturn ---
|
||||
|
||||
modelBuilder.Entity<SaleReturn>()
|
||||
.HasOne(r => r.ShopPO)
|
||||
.HasOne(r => r.Order)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_ShopPOId)
|
||||
.HasForeignKey(r => r.FK_OrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SaleReturn>()
|
||||
@@ -369,16 +308,12 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
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()
|
||||
@@ -431,8 +366,6 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
modelBuilder.Entity<SupplierClaimItem>()
|
||||
.HasIndex(i => i.FK_SupplierClaimId);
|
||||
|
||||
// --- DeliveryTask ---
|
||||
|
||||
modelBuilder.Entity<DeliveryTask>()
|
||||
.HasOne(t => t.Ride)
|
||||
.WithMany()
|
||||
@@ -440,13 +373,11 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<DeliveryTask>()
|
||||
.HasOne(t => t.ShopPO)
|
||||
.HasOne(t => t.Order)
|
||||
.WithMany()
|
||||
.HasForeignKey(t => t.FK_ShopPOId)
|
||||
.HasForeignKey(t => t.FK_OrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// --- Regional ---
|
||||
|
||||
modelBuilder.Entity<RegionalTarget>()
|
||||
.HasIndex(t => new { t.FK_RegionId, t.Period })
|
||||
.IsUnique();
|
||||
@@ -469,19 +400,6 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.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 }
|
||||
);
|
||||
|
||||
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
|
||||
Seed.MasterDataSeed.Apply(modelBuilder);
|
||||
Seed.OperationsDataSeed.Apply(modelBuilder);
|
||||
|
||||
@@ -16,9 +16,6 @@ public static class CommerceSeed
|
||||
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");
|
||||
@@ -83,18 +80,15 @@ public static class CommerceSeed
|
||||
);
|
||||
|
||||
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) }
|
||||
new SchemeRedemption { Id = SchemeRedemption1Id, FK_SchemeId = Scheme1Id, FK_OrderId = OperationsDataSeed.Order1Id, 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 }
|
||||
new Payment { Id = Payment1Id, FK_SaleId = OperationsDataSeed.Sale1Id, FK_StoreId = MasterDataSeed.FortStoreId, Amount = 3000m, Method = PaymentMethod.Cash, CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0), CollectedBy = MasterDataSeed.SalesRep1UserId, CreatedAt = new DateTime(2026, 8, 25, 9, 30, 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 }
|
||||
new SaleReturn { Id = SaleReturn1Id, FK_OrderId = OperationsDataSeed.Order1Id, Mode = ReturnMode.CreditDeduction, TotalCreditAmount = 300m, CreatedByUserId = MasterDataSeed.SalesRep1UserId, CreatedAt = new DateTime(2026, 8, 26), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SaleReturnItem>().HasData(
|
||||
|
||||
@@ -8,20 +8,15 @@ 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 Sale2Id = new("32000000-0000-0000-0000-000000000002");
|
||||
|
||||
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 Order1Id = new("33000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid Order2Id = new("33000000-0000-0000-0000-000000000002");
|
||||
|
||||
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 OrderItem1Id = new("34000000-0000-0000-0000-000000000001");
|
||||
internal static readonly Guid OrderItem2Id = new("34000000-0000-0000-0000-000000000002");
|
||||
internal static readonly Guid OrderItem3Id = new("34000000-0000-0000-0000-000000000003");
|
||||
|
||||
internal static readonly Guid LoadingSheet1Id = new("35000000-0000-0000-0000-000000000001");
|
||||
|
||||
@@ -36,12 +31,16 @@ public static class OperationsDataSeed
|
||||
new DeliveryRun
|
||||
{
|
||||
Id = ClosedSaleRunId,
|
||||
RideType = RideType.Sale,
|
||||
Status = RideStatus.Closed,
|
||||
FK_VehicleId = MasterDataSeed.VanVehicleId,
|
||||
FK_DriverProfileId = MasterDataSeed.Driver1ProfileId,
|
||||
FK_RouteId = MasterDataSeed.ColomboRouteId,
|
||||
FK_SourceWarehouseId = MasterDataSeed.ColomboWarehouseId,
|
||||
Meterstart = 10500,
|
||||
Meterend = 10550,
|
||||
StartedAt = new DateTime(2026, 8, 25, 8, 0, 0),
|
||||
ActivatedAt = new DateTime(2026, 8, 25, 8, 15, 0),
|
||||
ClosedAt = new DateTime(2026, 8, 25, 14, 0, 0),
|
||||
CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0),
|
||||
IsActive = true
|
||||
@@ -49,101 +48,86 @@ public static class OperationsDataSeed
|
||||
new DeliveryRun
|
||||
{
|
||||
Id = ActiveDeliveryRunId,
|
||||
Status = RideStatus.Open,
|
||||
RideType = RideType.Delivery,
|
||||
Status = RideStatus.Active,
|
||||
FK_VehicleId = MasterDataSeed.TruckVehicleId,
|
||||
FK_DriverProfileId = MasterDataSeed.Driver2ProfileId,
|
||||
FK_RouteId = MasterDataSeed.KandyRouteId,
|
||||
FK_SourceWarehouseId = MasterDataSeed.KandyWarehouseId,
|
||||
Meterstart = 20300,
|
||||
Meterend = 0,
|
||||
StartedAt = new DateTime(2026, 8, 30, 9, 0, 0),
|
||||
ActivatedAt = new DateTime(2026, 8, 30, 9, 20, 0),
|
||||
ClosedAt = null,
|
||||
CreatedAt = new DateTime(2026, 8, 30, 8, 45, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<ShopPO>().HasData(
|
||||
new ShopPO
|
||||
modelBuilder.Entity<Sale>().HasData(
|
||||
new Sale
|
||||
{
|
||||
Id = ShopPO1Id,
|
||||
Id = Sale1Id,
|
||||
SaleDate = new DateTime(2026, 8, 25, 9, 0, 0),
|
||||
FK_StoreId = MasterDataSeed.FortStoreId,
|
||||
RaisedById = MasterDataSeed.SalesRep1UserId,
|
||||
InvoiceId = Invoice1Id,
|
||||
CreatedByUserId = MasterDataSeed.SalesRep1UserId,
|
||||
FK_RideId = ClosedSaleRunId,
|
||||
TotalAmount = 3000m,
|
||||
Status = ShopPOStatus.Fulfilled,
|
||||
ConfirmedAt = new DateTime(2026, 8, 25, 8, 30, 0),
|
||||
ConfirmedBy = MasterDataSeed.Supervisor1UserId,
|
||||
Status = SaleStatus.Completed,
|
||||
PaymentStatus = PaymentStatus.Paid,
|
||||
FulfillImmediately = false,
|
||||
CompletedAt = new DateTime(2026, 8, 25, 9, 30, 0),
|
||||
CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0),
|
||||
IsActive = true
|
||||
},
|
||||
new Sale
|
||||
{
|
||||
Id = Sale2Id,
|
||||
SaleDate = new DateTime(2026, 8, 30, 10, 0, 0),
|
||||
FK_StoreId = MasterDataSeed.KandyStoreId,
|
||||
CreatedByUserId = MasterDataSeed.SalesRep2UserId,
|
||||
FK_RideId = ActiveDeliveryRunId,
|
||||
TotalAmount = 1200m,
|
||||
Status = SaleStatus.Pending,
|
||||
PaymentStatus = PaymentStatus.Pending,
|
||||
FulfillImmediately = false,
|
||||
CompletedAt = null,
|
||||
CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0),
|
||||
IsActive = true
|
||||
}
|
||||
);
|
||||
|
||||
modelBuilder.Entity<Order>().HasData(
|
||||
new Order
|
||||
{
|
||||
Id = Order1Id,
|
||||
FK_SaleId = Sale1Id,
|
||||
OrderDate = new DateTime(2026, 8, 25, 9, 0, 0),
|
||||
FK_StoreId = MasterDataSeed.FortStoreId,
|
||||
TotalAmount = 3000m,
|
||||
Status = OrderStatus.Fulfilled,
|
||||
FulfilledAt = new DateTime(2026, 8, 25, 13, 0, 0),
|
||||
CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0),
|
||||
IsActive = true
|
||||
},
|
||||
new ShopPO
|
||||
new Order
|
||||
{
|
||||
Id = ShopPO2Id,
|
||||
Id = Order2Id,
|
||||
FK_SaleId = Sale2Id,
|
||||
OrderDate = new DateTime(2026, 8, 30, 10, 0, 0),
|
||||
FK_StoreId = MasterDataSeed.KandyStoreId,
|
||||
RaisedById = MasterDataSeed.SalesRep2UserId,
|
||||
InvoiceId = null,
|
||||
TotalAmount = 1200m,
|
||||
Status = ShopPOStatus.Pending,
|
||||
ConfirmedAt = null,
|
||||
ConfirmedBy = null,
|
||||
Status = OrderStatus.Pending,
|
||||
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<OrderItem>().HasData(
|
||||
new OrderItem { Id = OrderItem1Id, FK_OrderId = Order1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, Quantity = 30, QuantityFulfilled = 30, UnitPrice = 60m, CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
|
||||
new OrderItem { Id = OrderItem2Id, FK_OrderId = Order1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Quantity = 24, QuantityFulfilled = 24, UnitPrice = 50m, CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
|
||||
new OrderItem { Id = OrderItem3Id, FK_OrderId = Order2Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 10, QuantityFulfilled = 0, UnitPrice = 120m, CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0), IsActive = true }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<LoadingSheet>().HasData(
|
||||
@@ -151,19 +135,20 @@ public static class OperationsDataSeed
|
||||
{
|
||||
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,
|
||||
FK_SourceWarehouseId = MasterDataSeed.ColomboWarehouseId,
|
||||
Status = LoadingSheetStatus.ReceiptConfirmed,
|
||||
DispatchConfirmedAt = new DateTime(2026, 8, 25, 7, 45, 0),
|
||||
DispatchConfirmedBy = MasterDataSeed.Supervisor1UserId,
|
||||
ReceiptConfirmedAt = new DateTime(2026, 8, 25, 14, 5, 0),
|
||||
ReceiptConfirmedBy = MasterDataSeed.Driver1UserId,
|
||||
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 }
|
||||
new LoadingSheetItem { Id = LoadingSheetItem1Id, FK_LoadingSheetId = LoadingSheet1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, QuantityDispatched = 50m, QuantityReceived = 50m, QuantityResolved = null },
|
||||
new LoadingSheetItem { Id = LoadingSheetItem2Id, FK_LoadingSheetId = LoadingSheet1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, QuantityDispatched = 30m, QuantityReceived = 30m, QuantityResolved = null }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<DeliveryTask>().HasData(
|
||||
@@ -171,7 +156,7 @@ public static class OperationsDataSeed
|
||||
{
|
||||
Id = DeliveryTask1Id,
|
||||
FK_RideId = ActiveDeliveryRunId,
|
||||
FK_ShopPOId = ShopPO2Id,
|
||||
FK_OrderId = Order2Id,
|
||||
Sequence = 1,
|
||||
Status = DeliveryTaskStatus.Pending,
|
||||
CustomerConfirmed = false,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -32,6 +31,8 @@ public static class OperationsExtrasSeed
|
||||
|
||||
private static readonly Guid DriverComplianceConfig1Id = new("64000000-0000-0000-0000-000000000001");
|
||||
|
||||
private static readonly Guid RideCancelRequest1Id = new("65000000-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");
|
||||
|
||||
@@ -79,9 +80,9 @@ public static class OperationsExtrasSeed
|
||||
);
|
||||
|
||||
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 = StockMovement1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FromLocationId = null, FromLocationType = null, 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 }
|
||||
new StockMovement { Id = StockMovement3Id, FK_ItemId = MasterDataSeed.Coke500ItemId, FromLocationId = MasterDataSeed.VanVehicleId, FromLocationType = LocationType.Vehicle, ToLocationId = null, ToLocationType = null, Quantity = 30m, Type = MovementType.Sale, ReasonCode = null, ReferenceId = OperationsDataSeed.Sale1Id, CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0), CreatedBy = MasterDataSeed.SalesRep1UserId }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<StoreKycDocument>().HasData(
|
||||
@@ -93,6 +94,21 @@ public static class OperationsExtrasSeed
|
||||
new DriverComplianceConfig { Id = DriverComplianceConfig1Id, EnforceLicenseExpiry = true, EnforceMedicalClearance = true, UpdatedAt = new DateTime(2026, 8, 20), UpdatedBy = MasterDataSeed.Supervisor1UserId }
|
||||
);
|
||||
|
||||
modelBuilder.Entity<RideCancelRequest>().HasData(
|
||||
new RideCancelRequest
|
||||
{
|
||||
Id = RideCancelRequest1Id,
|
||||
FK_RideId = OperationsDataSeed.ActiveDeliveryRunId,
|
||||
TypeCode = CancelRequestType.VehicleBreakdown,
|
||||
Reason = "Reported minor engine issue, resolved after inspection",
|
||||
RequestedBy = MasterDataSeed.Driver2UserId,
|
||||
RequestedAt = new DateTime(2026, 8, 30, 9, 10, 0),
|
||||
Status = CancelRequestStatus.Rejected,
|
||||
ApprovedBy = MasterDataSeed.Supervisor1UserId,
|
||||
ApprovedAt = new DateTime(2026, 8, 30, 9, 15, 0)
|
||||
}
|
||||
);
|
||||
|
||||
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 }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Distribution.Features.Brand;
|
||||
|
||||
public record CreateBrandRequest(string Name);
|
||||
public record UpdateBrandRequest(string Name, bool IsActive = true);
|
||||
public record UpdateBrandRequest(string Name);
|
||||
public record BrandResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
|
||||
|
||||
@@ -5,16 +5,15 @@ 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) return null;
|
||||
if (brand is null || !brand.IsActive) return null;
|
||||
return new BrandResponse(brand.Id, brand.Name, brand.IsActive, brand.CreatedAt);
|
||||
}
|
||||
|
||||
@@ -30,10 +29,9 @@ 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) return null;
|
||||
if (brand is null || !brand.IsActive) 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; }
|
||||
@@ -42,11 +40,10 @@ 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; }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace Distribution.Features.Category;
|
||||
|
||||
public record CreateCategoryRequest(string Name);
|
||||
public record UpdateCategoryRequest(string Name, bool IsActive = true);
|
||||
public record UpdateCategoryRequest(string Name);
|
||||
public record CreateSubCategoryRequest(string Name);
|
||||
public record UpdateSubCategoryRequest(string Name, bool IsActive = true);
|
||||
public record UpdateSubCategoryRequest(string Name);
|
||||
|
||||
public record SubCategoryResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
|
||||
public record CategorySummaryResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
|
||||
|
||||
@@ -4,27 +4,18 @@ namespace Distribution.Features.Category;
|
||||
|
||||
public class CategoryService(ICategoryRepository categoryRepo, ISubCategoryRepository subCategoryRepo, IUnitOfWork uow)
|
||||
{
|
||||
public async Task<List<CategoryResponse>> GetAllAsync()
|
||||
{
|
||||
// Deleted categories/sub-categories stay IsActive=false but remain visible showing
|
||||
// Inactive — delete never removes the row, only marks it (same rule as Orders/Stores/Items).
|
||||
var categoryList = await categoryRepo.GetAllAsync();
|
||||
var result = new List<CategoryResponse>();
|
||||
foreach (var category in categoryList)
|
||||
{
|
||||
var subs = (await subCategoryRepo.GetByCategoryIdAsync(category.Id))
|
||||
.Select(s => new SubCategoryResponse(s.Id, s.Name, s.IsActive, s.CreatedAt))
|
||||
.ToList();
|
||||
result.Add(new CategoryResponse(category.Id, category.Name, category.IsActive, category.CreatedAt, subs));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public async Task<List<CategorySummaryResponse>> GetAllAsync() =>
|
||||
(await categoryRepo.GetAllAsync())
|
||||
.Where(c => c.IsActive)
|
||||
.Select(c => new CategorySummaryResponse(c.Id, c.Name, c.IsActive, c.CreatedAt))
|
||||
.ToList();
|
||||
|
||||
public async Task<CategoryResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var category = await categoryRepo.GetByIdAsync(id);
|
||||
if (category is null) return null;
|
||||
if (category is null || !category.IsActive) return null;
|
||||
var subs = (await subCategoryRepo.GetByCategoryIdAsync(id))
|
||||
.Where(s => s.IsActive)
|
||||
.Select(s => new SubCategoryResponse(s.Id, s.Name, s.IsActive, s.CreatedAt))
|
||||
.ToList();
|
||||
return new CategoryResponse(category.Id, category.Name, category.IsActive, category.CreatedAt, subs);
|
||||
@@ -42,10 +33,9 @@ public class CategoryService(ICategoryRepository categoryRepo, ISubCategoryRepos
|
||||
public async Task<CategorySummaryResponse?> UpdateAsync(Guid id, UpdateCategoryRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var category = await categoryRepo.GetByIdAsync(id);
|
||||
if (category is null) return null;
|
||||
if (category is null || !category.IsActive) return null;
|
||||
if (category.ExternalErpId.HasValue) return null;
|
||||
category.Name = request.Name;
|
||||
category.IsActive = request.IsActive;
|
||||
await uow.BeginAsync(ct);
|
||||
try { categoryRepo.Update(category); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
@@ -54,11 +44,11 @@ public class CategoryService(ICategoryRepository categoryRepo, ISubCategoryRepos
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
// Soft-delete only — never blocked by child sub-categories/items, same rule as
|
||||
// Items/Stores/Orders. The row stays visible showing Inactive.
|
||||
var category = await categoryRepo.GetByIdAsync(id);
|
||||
if (category is null || !category.IsActive) return false;
|
||||
if (category.ExternalErpId.HasValue) return false;
|
||||
if (await categoryRepo.HasActiveSubCategoriesAsync(id)) return false;
|
||||
if (await categoryRepo.HasActiveItemsAsync(id)) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try { categoryRepo.Remove(category); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
@@ -79,10 +69,9 @@ public class CategoryService(ICategoryRepository categoryRepo, ISubCategoryRepos
|
||||
public async Task<SubCategoryResponse?> UpdateSubCategoryAsync(Guid id, UpdateSubCategoryRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var sub = await subCategoryRepo.GetByIdAsync(id);
|
||||
if (sub is null) return null;
|
||||
if (sub is null || !sub.IsActive) return null;
|
||||
if (sub.ExternalErpId.HasValue) return null;
|
||||
sub.Name = request.Name;
|
||||
sub.IsActive = request.IsActive;
|
||||
await uow.BeginAsync(ct);
|
||||
try { subCategoryRepo.Update(sub); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
@@ -91,10 +80,10 @@ public class CategoryService(ICategoryRepository categoryRepo, ISubCategoryRepos
|
||||
|
||||
public async Task<bool> DeleteSubCategoryAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
// Soft-delete only — never blocked by child items, same rule as the parent category.
|
||||
var sub = await subCategoryRepo.GetByIdAsync(id);
|
||||
if (sub is null || !sub.IsActive) return false;
|
||||
if (sub.ExternalErpId.HasValue) return false;
|
||||
if (await subCategoryRepo.HasActiveItemsAsync(id)) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try { subCategoryRepo.Remove(sub); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
@@ -6,10 +6,6 @@ namespace Distribution.Features.Claims;
|
||||
[ApiController]
|
||||
public class ClaimController(ClaimService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/claims")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/claims/{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
@@ -28,25 +24,13 @@ public class ClaimController(ClaimService service, ICurrentUserService currentUs
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("api/claims/{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("api/claims/{id:guid}/settle")]
|
||||
public async Task<IActionResult> Settle(Guid id, SettleClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.SettledByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "SettledByUserId is required while auth is unavailable" });
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.SettleAsync(id, request, userId.Value, ct);
|
||||
var result = await service.SettleAsync(id, request, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
|
||||
@@ -4,14 +4,7 @@ namespace Distribution.Features.Claims;
|
||||
|
||||
public record CreateClaimRequest(Guid AgencyUserId, string Details);
|
||||
|
||||
// Only editable while the claim is still Open — once settled, the settlement amount is a
|
||||
// financial record and the claim details it was settled against should not silently change.
|
||||
public record UpdateClaimRequest(Guid AgencyUserId, string Details);
|
||||
|
||||
public record SettleClaimRequest(
|
||||
decimal SettlementAmount,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? SettledByUserId = null);
|
||||
public record SettleClaimRequest(decimal SettlementAmount);
|
||||
|
||||
public record ClaimResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -8,12 +8,6 @@ public class ClaimRepository(AppDBContext context) : IClaimRepository
|
||||
public async Task<Models.Claim?> GetByIdAsync(Guid id) =>
|
||||
await context.Claims.FirstOrDefaultAsync(c => c.Id == id);
|
||||
|
||||
public async Task<List<Models.Claim>> GetAllAsync() =>
|
||||
await context.Claims
|
||||
.Where(c => c.IsActive)
|
||||
.OrderByDescending(c => c.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Models.Claim>> GetByAgencyUserIdAsync(Guid agencyUserId) =>
|
||||
await context.Claims
|
||||
.Where(c => c.FK_AgencyUserId == agencyUserId && c.IsActive)
|
||||
|
||||
@@ -15,9 +15,6 @@ public class ClaimService(IClaimRepository repo, IUnitOfWork uow)
|
||||
return Map(c);
|
||||
}
|
||||
|
||||
public async Task<List<ClaimResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync()).Select(Map).ToList();
|
||||
|
||||
public async Task<List<ClaimResponse>> GetByAgencyUserIdAsync(Guid agencyUserId) =>
|
||||
(await repo.GetByAgencyUserIdAsync(agencyUserId)).Select(Map).ToList();
|
||||
|
||||
@@ -43,26 +40,6 @@ public class ClaimService(IClaimRepository repo, IUnitOfWork uow)
|
||||
return Map(claim);
|
||||
}
|
||||
|
||||
public async Task<ClaimResponse> UpdateAsync(Guid id, UpdateClaimRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var claim = await repo.GetByIdAsync(id);
|
||||
if (claim is null || !claim.IsActive)
|
||||
throw new InvalidOperationException("Claim not found.");
|
||||
if (claim.Status == ClaimStatus.Settled)
|
||||
throw new InvalidOperationException("Cannot edit a claim that has already been settled.");
|
||||
if (string.IsNullOrWhiteSpace(request.Details))
|
||||
throw new InvalidOperationException("Claim details are required.");
|
||||
|
||||
claim.FK_AgencyUserId = request.AgencyUserId;
|
||||
claim.Details = request.Details;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(claim); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(claim);
|
||||
}
|
||||
|
||||
public async Task<ClaimResponse> SettleAsync(Guid id, SettleClaimRequest request, Guid approverId, CancellationToken ct = default)
|
||||
{
|
||||
var claim = await repo.GetByIdAsync(id);
|
||||
|
||||
@@ -3,7 +3,6 @@ namespace Distribution.Features.Claims;
|
||||
public interface IClaimRepository
|
||||
{
|
||||
Task<Models.Claim?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Claim>> GetAllAsync();
|
||||
Task<List<Models.Claim>> GetByAgencyUserIdAsync(Guid agencyUserId);
|
||||
Task AddAsync(Models.Claim entity);
|
||||
void Update(Models.Claim entity);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.DeliveryRide;
|
||||
|
||||
@@ -7,7 +7,7 @@ public record DeliveryTaskItemResponse(Guid ItemId, string ItemName, int Quantit
|
||||
public record DeliveryTaskResponse(
|
||||
Guid Id,
|
||||
Guid FK_RideId,
|
||||
Guid FK_ShopPOId,
|
||||
Guid FK_OrderId,
|
||||
Guid StoreId,
|
||||
string StoreName,
|
||||
int Sequence,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -8,27 +8,27 @@ public class DeliveryTaskRepository(AppDBContext context) : IDeliveryTaskReposit
|
||||
{
|
||||
public async Task<DeliveryTask?> GetByIdAsync(Guid id) =>
|
||||
await context.DeliveryTasks
|
||||
.Include(t => t.ShopPO)
|
||||
.Include(t => t.Order)
|
||||
.ThenInclude(o => o.Store)
|
||||
.Include(t => t.ShopPO)
|
||||
.ThenInclude(o => o.Items.Where(i => i.IsActive))
|
||||
.Include(t => t.Order)
|
||||
.ThenInclude(o => o.OrderItems)
|
||||
.ThenInclude(i => i.Item)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
public async Task<List<DeliveryTask>> GetByRideIdAsync(Guid rideId) =>
|
||||
await context.DeliveryTasks
|
||||
.Include(t => t.ShopPO)
|
||||
.Include(t => t.Order)
|
||||
.ThenInclude(o => o.Store)
|
||||
.Include(t => t.ShopPO)
|
||||
.ThenInclude(o => o.Items.Where(i => i.IsActive))
|
||||
.Include(t => t.Order)
|
||||
.ThenInclude(o => o.OrderItems)
|
||||
.ThenInclude(i => i.Item)
|
||||
.Where(t => t.FK_RideId == rideId)
|
||||
.OrderBy(t => t.Sequence)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<bool> ExistsForOrderAndRideAsync(Guid shopPOId, Guid rideId) =>
|
||||
public async Task<bool> ExistsForOrderAndRideAsync(Guid orderId, Guid rideId) =>
|
||||
await context.DeliveryTasks
|
||||
.AnyAsync(t => t.FK_ShopPOId == shopPOId && t.FK_RideId == rideId);
|
||||
.AnyAsync(t => t.FK_OrderId == orderId && t.FK_RideId == rideId);
|
||||
|
||||
public async Task AddAsync(DeliveryTask task) =>
|
||||
await context.DeliveryTasks.AddAsync(task);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Distribution.Features.Order;
|
||||
using Distribution.Features.Ride;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Sale;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -9,22 +10,23 @@ namespace Distribution.Features.DeliveryRide;
|
||||
public class DeliveryTaskService(
|
||||
IDeliveryTaskRepository repo,
|
||||
IRideRepository rideRepo,
|
||||
IShopPORepository shopPORepo,
|
||||
IOrderRepository orderRepo,
|
||||
ISaleRepository saleRepo,
|
||||
IStockRepository stockRepo,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static DeliveryTaskResponse ToResponse(DeliveryTask t) =>
|
||||
new(t.Id,
|
||||
t.FK_RideId,
|
||||
t.FK_ShopPOId,
|
||||
t.ShopPO.FK_StoreId,
|
||||
t.ShopPO.Store?.StoreName ?? string.Empty,
|
||||
t.FK_OrderId,
|
||||
t.Order.FK_StoreId,
|
||||
t.Order.Store?.StoreName ?? string.Empty,
|
||||
t.Sequence,
|
||||
t.Status,
|
||||
t.CustomerConfirmed,
|
||||
t.ConfirmedAt,
|
||||
t.Note,
|
||||
t.ShopPO.Items.Select(i => new DeliveryTaskItemResponse(
|
||||
t.Order.OrderItems.Select(i => new DeliveryTaskItemResponse(
|
||||
i.FK_ItemId,
|
||||
i.Item?.Name ?? string.Empty,
|
||||
i.Quantity,
|
||||
@@ -32,8 +34,52 @@ public class DeliveryTaskService(
|
||||
i.UnitPrice)).ToList(),
|
||||
t.CreatedAt);
|
||||
|
||||
public Task<List<DeliveryTaskResponse>> GenerateAsync(Guid rideId, CancellationToken ct = default) =>
|
||||
throw new NotImplementedException("Delivery task auto-generation is deferred — route-based dispatch not yet implemented.");
|
||||
public async Task<List<DeliveryTaskResponse>> GenerateAsync(Guid rideId, CancellationToken ct = default)
|
||||
{
|
||||
var ride = await rideRepo.GetByIdAsync(rideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
|
||||
if (ride.Status != RideStatus.Active)
|
||||
throw new InvalidOperationException("Tasks can only be generated for an Active ride");
|
||||
|
||||
if (ride.RideType != RideType.Delivery)
|
||||
throw new InvalidOperationException("Tasks can only be generated for a Delivery ride");
|
||||
|
||||
var orders = await orderRepo.GetPendingByRouteIdAsync(ride.FK_RouteId);
|
||||
|
||||
var created = new List<DeliveryTask>();
|
||||
int sequence = 1;
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
if (await repo.ExistsForOrderAndRideAsync(order.Id, rideId))
|
||||
continue;
|
||||
|
||||
var task = new DeliveryTask
|
||||
{
|
||||
FK_RideId = rideId,
|
||||
FK_OrderId = order.Id,
|
||||
Sequence = sequence++,
|
||||
Status = DeliveryTaskStatus.Pending
|
||||
};
|
||||
created.Add(task);
|
||||
}
|
||||
|
||||
if (created.Count == 0)
|
||||
return new List<DeliveryTaskResponse>();
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
foreach (var task in created)
|
||||
await repo.AddAsync(task);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
var saved = await repo.GetByRideIdAsync(rideId);
|
||||
return saved.Select(ToResponse).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<DeliveryTaskResponse>> GetByRideIdAsync(Guid rideId)
|
||||
{
|
||||
@@ -57,16 +103,16 @@ public class DeliveryTaskService(
|
||||
|
||||
foreach (var input in request.Items)
|
||||
{
|
||||
var shopPOItem = task.ShopPO.Items.FirstOrDefault(i => i.FK_ItemId == input.ItemId)
|
||||
?? throw new InvalidOperationException($"Item {input.ItemId} is not on this ShopPO");
|
||||
var orderItem = task.Order.OrderItems.FirstOrDefault(i => i.FK_ItemId == input.ItemId)
|
||||
?? throw new InvalidOperationException($"Item {input.ItemId} is not on this order");
|
||||
|
||||
if (input.QuantityFulfilled < 0)
|
||||
throw new InvalidOperationException($"QuantityFulfilled cannot be negative for item {input.ItemId}");
|
||||
|
||||
if (input.QuantityFulfilled > shopPOItem.Quantity)
|
||||
if (input.QuantityFulfilled > orderItem.Quantity)
|
||||
throw new InvalidOperationException($"QuantityFulfilled exceeds ordered quantity for item {input.ItemId}");
|
||||
|
||||
shopPOItem.QuantityFulfilled = input.QuantityFulfilled;
|
||||
orderItem.QuantityFulfilled = input.QuantityFulfilled;
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
@@ -89,7 +135,7 @@ public class DeliveryTaskService(
|
||||
if (task.Status != DeliveryTaskStatus.Pending)
|
||||
throw new InvalidOperationException("Only a Pending task can be confirmed");
|
||||
|
||||
if (!task.ShopPO.Items.Any(i => i.QuantityFulfilled > 0))
|
||||
if (!task.Order.OrderItems.Any(i => i.QuantityFulfilled > 0))
|
||||
throw new InvalidOperationException("Set quantities before confirming — at least one item must have a fulfilled quantity greater than zero");
|
||||
|
||||
var ride = await rideRepo.GetByIdAsync(task.FK_RideId)
|
||||
@@ -97,31 +143,29 @@ public class DeliveryTaskService(
|
||||
|
||||
var vehicleId = ride.FK_VehicleId;
|
||||
|
||||
bool fullyDelivered = task.ShopPO.Items.All(i => i.QuantityFulfilled == i.Quantity);
|
||||
bool fullyDelivered = task.Order.OrderItems.All(i => i.QuantityFulfilled == i.Quantity);
|
||||
|
||||
task.CustomerConfirmed = true;
|
||||
task.ConfirmedAt = DateTime.Now;
|
||||
task.Status = fullyDelivered ? DeliveryTaskStatus.Delivered : DeliveryTaskStatus.PartiallyDelivered;
|
||||
|
||||
var shopPO = task.ShopPO;
|
||||
shopPO.Status = fullyDelivered ? ShopPOStatus.Fulfilled : ShopPOStatus.PartiallyFulfilled;
|
||||
var order = task.Order;
|
||||
order.Status = fullyDelivered ? OrderStatus.Fulfilled : OrderStatus.PartiallyFulfilled;
|
||||
if (fullyDelivered)
|
||||
shopPO.FulfilledAt = DateTime.Now;
|
||||
order.FulfilledAt = DateTime.Now;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
foreach (var item in task.ShopPO.Items.Where(i => i.QuantityFulfilled > 0))
|
||||
foreach (var item in task.Order.OrderItems.Where(i => i.QuantityFulfilled > 0))
|
||||
{
|
||||
var vehicleBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId);
|
||||
var availableQuantity = vehicleBalance?.Quantity ?? 0;
|
||||
var itemName = item.Item?.Name ?? item.FK_ItemId.ToString();
|
||||
var vehicleBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId)
|
||||
?? throw new InvalidOperationException($"No vehicle stock found for item {item.FK_ItemId}");
|
||||
|
||||
if (availableQuantity < item.QuantityFulfilled)
|
||||
throw new InvalidOperationException(
|
||||
$"Insufficient vehicle stock for item '{itemName}': available {availableQuantity}, required {item.QuantityFulfilled}");
|
||||
if (vehicleBalance.Quantity < item.QuantityFulfilled)
|
||||
throw new InvalidOperationException($"Insufficient vehicle stock for item {item.FK_ItemId}");
|
||||
|
||||
vehicleBalance!.Quantity -= item.QuantityFulfilled;
|
||||
vehicleBalance.Quantity -= item.QuantityFulfilled;
|
||||
vehicleBalance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(vehicleBalance);
|
||||
|
||||
@@ -130,16 +174,23 @@ public class DeliveryTaskService(
|
||||
FK_ItemId = item.FK_ItemId,
|
||||
FromLocationId = vehicleId,
|
||||
FromLocationType = LocationType.Vehicle,
|
||||
ToLocationId = StockService.ExternalLocationId,
|
||||
ToLocationType = LocationType.External,
|
||||
Quantity = item.QuantityFulfilled,
|
||||
Type = MovementType.Sale,
|
||||
ReferenceId = task.FK_ShopPOId
|
||||
ReferenceId = task.FK_OrderId
|
||||
});
|
||||
}
|
||||
|
||||
repo.Update(task);
|
||||
shopPORepo.Update(shopPO);
|
||||
orderRepo.Update(order);
|
||||
|
||||
if (fullyDelivered)
|
||||
{
|
||||
var sale = await saleRepo.GetByIdAsync(order.FK_SaleId)
|
||||
?? throw new KeyNotFoundException("Sale not found");
|
||||
sale.Status = SaleStatus.Completed;
|
||||
sale.CompletedAt = DateTime.Now;
|
||||
saleRepo.Update(sale);
|
||||
}
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
|
||||
@@ -26,14 +26,11 @@ public class GrnController(GrnService service, ICurrentUserService currentUser)
|
||||
[HttpPost("api/goods-receipts")]
|
||||
public async Task<IActionResult> CreateFlat(CreateFlatGrnRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
// (testbug.md #28) — fall back to the explicitly supplied user until that's fixed.
|
||||
var userId = currentUser.UserId ?? request.ReceivedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "ReceivedByUserId is required while auth is unavailable" });
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var inner = new CreateGrnRequest(request.WarehouseId, request.Items);
|
||||
var result = await service.CreateAsync(request.PurchaseOrderId, inner, userId.Value, ct);
|
||||
var result = await service.CreateAsync(request.PurchaseOrderId, inner, userId, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
@@ -46,11 +43,10 @@ public class GrnController(GrnService service, ICurrentUserService currentUser)
|
||||
[HttpPost("api/purchase-orders/{id:guid}/goods-receipts")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateGrnRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.ReceivedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "ReceivedByUserId is required while auth is unavailable" });
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(id, request, userId.Value, ct);
|
||||
var result = await service.CreateAsync(id, request, userId, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
|
||||
@@ -2,18 +2,9 @@ namespace Distribution.Features.Grn;
|
||||
|
||||
public record GrnItemRequest(Guid PurchaseOrderItemId, int AcceptedQuantity, int RejectedQuantity);
|
||||
|
||||
public record CreateGrnRequest(
|
||||
Guid WarehouseId,
|
||||
List<GrnItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? ReceivedByUserId = null);
|
||||
public record CreateGrnRequest(Guid WarehouseId, List<GrnItemRequest> Items);
|
||||
|
||||
public record CreateFlatGrnRequest(
|
||||
Guid PurchaseOrderId,
|
||||
Guid WarehouseId,
|
||||
List<GrnItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? ReceivedByUserId = null);
|
||||
public record CreateFlatGrnRequest(Guid PurchaseOrderId, Guid WarehouseId, List<GrnItemRequest> Items);
|
||||
|
||||
public record GrnItemResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -124,8 +124,6 @@ public class GrnService(
|
||||
await stockRepo.AddMovementAsync(new StockMovement
|
||||
{
|
||||
FK_ItemId = poItem.FK_ItemId,
|
||||
FromLocationId = StockService.ExternalLocationId,
|
||||
FromLocationType = LocationType.External,
|
||||
ToLocationId = request.WarehouseId,
|
||||
ToLocationType = LocationType.Warehouse,
|
||||
Quantity = line.AcceptedQuantity,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Invoice;
|
||||
|
||||
public interface IInvoiceRepository
|
||||
{
|
||||
Task<Models.Invoice?> GetByIdAsync(Guid id);
|
||||
Task<Models.Invoice?> GetByShopPOIdAsync(Guid shopPOId);
|
||||
Task<List<Models.Invoice>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, int page, int pageSize);
|
||||
Task<int> GetCountAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status);
|
||||
Task AddAsync(Models.Invoice invoice);
|
||||
void Update(Models.Invoice invoice);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Invoice;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/invoices")]
|
||||
public class InvoiceController(InvoiceService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] Guid? agentId,
|
||||
[FromQuery] Guid? routeId,
|
||||
[FromQuery] Guid? shopId,
|
||||
[FromQuery] InvoiceStatus? status,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetAllAsync(agentId, routeId, shopId, status, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Invoice;
|
||||
|
||||
public record CreateInvoiceRequest(Guid ShopPOId);
|
||||
public record InvoiceItemAdjustment(Guid ShopPOItemId, int AdjustedQuantity, string? Note = null);
|
||||
public record FinalizeInvoiceRequest(List<InvoiceItemAdjustment> Adjustments);
|
||||
|
||||
public record InvoiceResponse(
|
||||
Guid Id,
|
||||
Guid ShopPOId,
|
||||
string StoreName,
|
||||
InvoiceStatus Status,
|
||||
PaymentStatus PaymentStatus,
|
||||
Guid CreatedBy,
|
||||
DateTime? PrintedAt,
|
||||
Guid? PrintedBy,
|
||||
DateTime? FinalizedAt,
|
||||
Guid? FinalizedBy,
|
||||
decimal TotalAmount,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
@@ -1,47 +0,0 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Invoice;
|
||||
|
||||
public class InvoiceRepository(AppDBContext context) : IInvoiceRepository
|
||||
{
|
||||
public async Task<Models.Invoice?> GetByIdAsync(Guid id) =>
|
||||
await context.Invoices
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Store)
|
||||
.Include(i => i.Payments.Where(p => p.IsActive))
|
||||
.FirstOrDefaultAsync(i => i.Id == id && i.IsActive);
|
||||
|
||||
public async Task<Models.Invoice?> GetByShopPOIdAsync(Guid shopPOId) =>
|
||||
await context.Invoices
|
||||
.Include(i => i.ShopPO)
|
||||
.FirstOrDefaultAsync(i => i.FK_ShopPOId == shopPOId && i.IsActive);
|
||||
|
||||
public async Task<List<Models.Invoice>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, int page, int pageSize) =>
|
||||
await context.Invoices
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Store)
|
||||
.Where(i => i.IsActive
|
||||
&& (agentId == null || i.ShopPO.RaisedById == agentId)
|
||||
&& (routeId == null || i.ShopPO.Store.FK_RouteId == routeId)
|
||||
&& (shopId == null || i.ShopPO.FK_StoreId == shopId)
|
||||
&& (status == null || i.Status == status))
|
||||
.OrderByDescending(i => i.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<int> GetCountAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status) =>
|
||||
await context.Invoices
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Store)
|
||||
.CountAsync(i => i.IsActive
|
||||
&& (agentId == null || i.ShopPO.RaisedById == agentId)
|
||||
&& (routeId == null || i.ShopPO.Store.FK_RouteId == routeId)
|
||||
&& (shopId == null || i.ShopPO.FK_StoreId == shopId)
|
||||
&& (status == null || i.Status == status));
|
||||
|
||||
public async Task AddAsync(Models.Invoice invoice) =>
|
||||
await context.Invoices.AddAsync(invoice);
|
||||
|
||||
public void Update(Models.Invoice invoice) =>
|
||||
context.Invoices.Update(invoice);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Invoice;
|
||||
|
||||
public class InvoiceService(
|
||||
IInvoiceRepository repo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
private static InvoiceResponse ToResponse(Models.Invoice i) =>
|
||||
new(i.Id, i.FK_ShopPOId,
|
||||
i.ShopPO?.Store?.StoreName ?? string.Empty,
|
||||
i.Status, i.PaymentStatus, i.CreatedBy,
|
||||
i.PrintedAt, i.PrintedBy,
|
||||
i.FinalizedAt, i.FinalizedBy,
|
||||
i.TotalAmount, i.IsActive, i.CreatedAt);
|
||||
|
||||
public async Task<InvoiceResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var invoice = await repo.GetByIdAsync(id);
|
||||
return invoice is null ? null : ToResponse(invoice);
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<InvoiceResponse>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, int page, int pageSize)
|
||||
{
|
||||
var total = await repo.GetCountAsync(agentId, routeId, shopId, status);
|
||||
var items = await repo.GetAllAsync(agentId, routeId, shopId, status, page, pageSize);
|
||||
return new PagedResponse<InvoiceResponse>(page, pageSize, total, items.Select(ToResponse).ToList());
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,7 @@ public record UpdateItemRequest(
|
||||
Guid? SubCategoryId,
|
||||
Guid? BrandId,
|
||||
Guid BaseUomId,
|
||||
decimal? SalePrice,
|
||||
bool IsActive = true);
|
||||
decimal? SalePrice);
|
||||
|
||||
public record ReplaceConversionsRequest(List<UomConversionRequest> Conversions);
|
||||
|
||||
|
||||
@@ -20,14 +20,12 @@ public class ItemRepository(AppDBContext context) : IItemRepository
|
||||
|
||||
public async Task<List<Models.Item>> GetAllAsync(Guid? categoryId = null, Guid? subCategoryId = null, Guid? brandId = null)
|
||||
{
|
||||
// Deleted items stay IsActive=false but remain visible with an Inactive status —
|
||||
// Delete never removes the row, only marks it (same rule as Orders/Stores).
|
||||
var query = context.Items
|
||||
.Include(i => i.Category)
|
||||
.Include(i => i.SubCategory)
|
||||
.Include(i => i.Brand)
|
||||
.Include(i => i.BaseUom)
|
||||
.AsQueryable();
|
||||
.Where(i => i.IsActive);
|
||||
|
||||
if (categoryId.HasValue) query = query.Where(i => i.FK_CategoryId == categoryId);
|
||||
if (subCategoryId.HasValue) query = query.Where(i => i.FK_SubCategoryId == subCategoryId);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ItemService(IItemRepository repo, IUnitOfWork uow)
|
||||
public async Task<ItemDetailResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var item = await repo.GetByIdAsync(id);
|
||||
if (item is null) return null;
|
||||
if (item is null || !item.IsActive) return null;
|
||||
return MapDetail(item);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class ItemService(IItemRepository repo, IUnitOfWork uow)
|
||||
public async Task<ItemDetailResponse?> UpdateAsync(Guid id, UpdateItemRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var item = await repo.GetByIdAsync(id);
|
||||
if (item is null) return null;
|
||||
if (item is null || !item.IsActive) return null;
|
||||
if (item.SyncSource == Models.SyncSource.Erp) return null;
|
||||
|
||||
item.Name = request.Name;
|
||||
@@ -84,7 +84,6 @@ public class ItemService(IItemRepository repo, IUnitOfWork uow)
|
||||
item.FK_BrandId = request.BrandId;
|
||||
item.FK_BaseUomId = request.BaseUomId;
|
||||
item.SalePrice = request.SalePrice;
|
||||
item.IsActive = request.IsActive;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(item); await uow.CommitAsync(ct); }
|
||||
|
||||
@@ -8,6 +8,7 @@ public interface ILoadingSheetRepository
|
||||
Task<LoadingSheet?> GetActiveByRideIdAsync(Guid rideId);
|
||||
Task<List<LoadingSheet>> GetAllAsync(Guid? rideId, LoadingSheetStatus? status, int page, int pageSize);
|
||||
Task<int> GetCountAsync(Guid? rideId, LoadingSheetStatus? status);
|
||||
Task<List<(Guid ItemId, decimal TotalQty)>> GetAggregatedOrderItemsByRouteIdAsync(Guid routeId);
|
||||
Task AddAsync(LoadingSheet sheet);
|
||||
void Update(LoadingSheet sheet);
|
||||
void RemoveItems(IEnumerable<LoadingSheetItem> items);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Models;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -29,10 +29,9 @@ public class LoadingSheetController(LoadingSheetService service, ICurrentUserSer
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateLoadingSheetRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(request, userId, ct);
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
@@ -51,13 +50,90 @@ public class LoadingSheetController(LoadingSheetService service, ICurrentUserSer
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/confirm")]
|
||||
public async Task<IActionResult> Confirm(Guid id, CancellationToken ct)
|
||||
[HttpPost("{id:guid}/confirm-dispatch")]
|
||||
public async Task<IActionResult> ConfirmDispatch(Guid id, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.ConfirmAsync(id, userId, ct);
|
||||
var result = await service.ConfirmDispatchAsync(id, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/receipt-items")]
|
||||
public async Task<IActionResult> SetReceiptItems(Guid id, SetReceiptItemsRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.SetReceiptItemsAsync(id, request, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/confirm-receipt")]
|
||||
public async Task<IActionResult> ConfirmReceipt(Guid id, ConfirmReceiptRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.ConfirmReceiptAsync(id, request, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/resolve")]
|
||||
public async Task<IActionResult> Resolve(Guid id, ResolveDiscrepancyRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.ResolveAsync(id, request, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/dispatch")]
|
||||
public async Task<IActionResult> PatchDispatch(Guid id, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.ConfirmDispatchAsync(id, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/receipt")]
|
||||
public async Task<IActionResult> PatchReceipt(Guid id, PatchReceiptRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.PatchReceiptAsync(id, request.Items, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/resolve")]
|
||||
public async Task<IActionResult> PatchResolve(Guid id, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.PatchResolveAsync(id, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
|
||||
@@ -5,27 +5,37 @@ namespace Distribution.Features.LoadingEvent;
|
||||
public record LoadingSheetResponse(
|
||||
Guid Id,
|
||||
Guid FK_RideId,
|
||||
Guid FK_SourceWarehouseId,
|
||||
string WarehouseName,
|
||||
LoadingSheetStatus Status,
|
||||
Guid PopulatedBy,
|
||||
DateTime PopulatedAt,
|
||||
DateTime? ConfirmedAt,
|
||||
Guid? ConfirmedBy,
|
||||
DateTime? DispatchConfirmedAt,
|
||||
Guid? DispatchConfirmedBy,
|
||||
DateTime? ReceiptConfirmedAt,
|
||||
Guid? ReceiptConfirmedBy,
|
||||
DateTime CreatedAt,
|
||||
List<LoadingSheetItemResponse> Items,
|
||||
List<LoadingSheetOrderResponse> Orders);
|
||||
List<LoadingSheetItemResponse> Items);
|
||||
|
||||
public record LoadingSheetItemResponse(
|
||||
Guid Id,
|
||||
Guid FK_ItemId,
|
||||
string ItemName,
|
||||
Guid FK_WarehouseId,
|
||||
string WarehouseName,
|
||||
decimal QuantityDispatched,
|
||||
Guid? FK_ShopPOItemId);
|
||||
|
||||
public record LoadingSheetOrderResponse(Guid ShopPOId);
|
||||
decimal? QuantityReceived,
|
||||
decimal? QuantityResolved);
|
||||
|
||||
public record CreateLoadingSheetRequest(Guid FK_RideId);
|
||||
|
||||
public record SetItemsRequest(List<SetItemInput> Items);
|
||||
public record SetItemInput(Guid FK_ItemId, Guid FK_WarehouseId, decimal Quantity, Guid? FK_ShopPOItemId = null);
|
||||
public record SetItemInput(Guid FK_ItemId, decimal Quantity);
|
||||
|
||||
public record SetReceiptItemsRequest(List<ReceiptItemInput> Items);
|
||||
public record ReceiptItemInput(Guid FK_ItemId, decimal QuantityReceived);
|
||||
|
||||
public record ConfirmReceiptRequest(int Meterstart);
|
||||
|
||||
public record ResolveDiscrepancyRequest(List<ResolveItemInput> Items, int Meterstart);
|
||||
public record ResolveItemInput(Guid FK_ItemId, decimal QuantityResolved);
|
||||
|
||||
// Combined PATCH endpoints used by the frontend
|
||||
public record PatchReceiptItemInput(Guid ItemId, decimal QuantityReceived);
|
||||
public record PatchReceiptRequest(List<PatchReceiptItemInput> Items);
|
||||
|
||||
@@ -8,20 +8,20 @@ public class LoadingSheetRepository(AppDBContext context) : ILoadingSheetReposit
|
||||
{
|
||||
public async Task<LoadingSheet?> GetByIdAsync(Guid id) =>
|
||||
await context.LoadingSheets
|
||||
.Include(s => s.SourceWarehouse)
|
||||
.Include(s => s.Items).ThenInclude(i => i.Item)
|
||||
.Include(s => s.Items).ThenInclude(i => i.Warehouse)
|
||||
.Include(s => s.Orders)
|
||||
.FirstOrDefaultAsync(s => s.Id == id);
|
||||
|
||||
public async Task<LoadingSheet?> GetActiveByRideIdAsync(Guid rideId) =>
|
||||
await context.LoadingSheets
|
||||
.FirstOrDefaultAsync(s => s.FK_RideId == rideId && s.Status == LoadingSheetStatus.Planned);
|
||||
.FirstOrDefaultAsync(s =>
|
||||
s.FK_RideId == rideId &&
|
||||
s.Status != LoadingSheetStatus.ReceiptConfirmed &&
|
||||
s.Status != LoadingSheetStatus.Resolved);
|
||||
|
||||
public async Task<List<LoadingSheet>> GetAllAsync(Guid? rideId, LoadingSheetStatus? status, int page, int pageSize) =>
|
||||
await context.LoadingSheets
|
||||
.Include(s => s.Items).ThenInclude(i => i.Item)
|
||||
.Include(s => s.Items).ThenInclude(i => i.Warehouse)
|
||||
.Include(s => s.Orders)
|
||||
.Include(s => s.SourceWarehouse)
|
||||
.Where(s =>
|
||||
(rideId == null || s.FK_RideId == rideId) &&
|
||||
(status == null || s.Status == status))
|
||||
@@ -36,6 +36,19 @@ public class LoadingSheetRepository(AppDBContext context) : ILoadingSheetReposit
|
||||
(rideId == null || s.FK_RideId == rideId) &&
|
||||
(status == null || s.Status == status));
|
||||
|
||||
public async Task<List<(Guid ItemId, decimal TotalQty)>> GetAggregatedOrderItemsByRouteIdAsync(Guid routeId)
|
||||
{
|
||||
var groups = await context.OrderItems
|
||||
.Where(oi => oi.IsActive
|
||||
&& oi.Order.IsActive
|
||||
&& oi.Order.Store.FK_RouteId == routeId
|
||||
&& (oi.Order.Status == OrderStatus.Pending || oi.Order.Status == OrderStatus.PartiallyFulfilled))
|
||||
.GroupBy(oi => oi.FK_ItemId)
|
||||
.Select(g => new { ItemId = g.Key, TotalQty = g.Sum(oi => (decimal)(oi.Quantity - oi.QuantityFulfilled)) })
|
||||
.ToListAsync();
|
||||
return groups.Select(x => (x.ItemId, x.TotalQty)).ToList();
|
||||
}
|
||||
|
||||
public async Task AddAsync(LoadingSheet sheet) =>
|
||||
await context.LoadingSheets.AddAsync(sheet);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class LoadingSheetService(
|
||||
return sheet is null ? null : ToResponse(sheet);
|
||||
}
|
||||
|
||||
public async Task<LoadingSheetResponse> CreateAsync(CreateLoadingSheetRequest request, Guid userId, CancellationToken ct = default)
|
||||
public async Task<LoadingSheetResponse> CreateAsync(CreateLoadingSheetRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var ride = await rideRepo.GetByIdAsync(request.FK_RideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
@@ -34,16 +34,22 @@ public class LoadingSheetService(
|
||||
|
||||
var existing = await repo.GetActiveByRideIdAsync(request.FK_RideId);
|
||||
if (existing is not null)
|
||||
throw new InvalidOperationException("This ride already has a Planned loading sheet");
|
||||
throw new InvalidOperationException("This ride already has an active loading sheet");
|
||||
|
||||
var sheet = new LoadingSheet
|
||||
{
|
||||
FK_RideId = ride.Id,
|
||||
Status = LoadingSheetStatus.Planned,
|
||||
PopulatedBy = userId,
|
||||
PopulatedAt = DateTime.Now
|
||||
FK_SourceWarehouseId = ride.FK_SourceWarehouseId,
|
||||
Status = LoadingSheetStatus.Pending
|
||||
};
|
||||
|
||||
if (ride.RideType == RideType.Delivery)
|
||||
{
|
||||
var orderItems = await repo.GetAggregatedOrderItemsByRouteIdAsync(ride.FK_RouteId);
|
||||
foreach (var (itemId, qty) in orderItems)
|
||||
sheet.Items.Add(new LoadingSheetItem { FK_ItemId = itemId, QuantityDispatched = qty });
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
@@ -61,8 +67,8 @@ public class LoadingSheetService(
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.Planned)
|
||||
throw new InvalidOperationException("Items can only be changed while the sheet is Planned");
|
||||
if (sheet.Status != LoadingSheetStatus.Pending)
|
||||
throw new InvalidOperationException("Items can only be changed while the sheet is Pending");
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
@@ -72,8 +78,6 @@ public class LoadingSheetService(
|
||||
{
|
||||
FK_LoadingSheetId = sheet.Id,
|
||||
FK_ItemId = i.FK_ItemId,
|
||||
FK_WarehouseId = i.FK_WarehouseId,
|
||||
FK_ShopPOItemId = i.FK_ShopPOItemId,
|
||||
QuantityDispatched = i.Quantity
|
||||
}));
|
||||
await uow.CommitAsync(ct);
|
||||
@@ -84,91 +88,289 @@ public class LoadingSheetService(
|
||||
return ToResponse(saved!);
|
||||
}
|
||||
|
||||
public async Task<LoadingSheetResponse> ConfirmAsync(Guid id, Guid userId, CancellationToken ct = default)
|
||||
public async Task<LoadingSheetResponse> ConfirmDispatchAsync(Guid id, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.Planned)
|
||||
throw new InvalidOperationException($"Cannot confirm a sheet in {sheet.Status} status");
|
||||
if (sheet.Status != LoadingSheetStatus.Pending)
|
||||
throw new InvalidOperationException($"Cannot confirm dispatch for a sheet in {sheet.Status} status");
|
||||
|
||||
if (!sheet.Items.Any(i => i.QuantityDispatched > 0))
|
||||
throw new InvalidOperationException("Sheet must have at least one item with quantity greater than zero");
|
||||
|
||||
sheet.Status = LoadingSheetStatus.DispatchConfirmed;
|
||||
sheet.DispatchConfirmedAt = DateTime.Now;
|
||||
sheet.DispatchConfirmedBy = userId;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(sheet); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(sheet);
|
||||
}
|
||||
|
||||
public async Task<LoadingSheetResponse> SetReceiptItemsAsync(Guid id, SetReceiptItemsRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.DispatchConfirmed)
|
||||
throw new InvalidOperationException("Receipt quantities can only be set after dispatch is confirmed");
|
||||
|
||||
foreach (var input in request.Items)
|
||||
{
|
||||
var item = sheet.Items.FirstOrDefault(i => i.FK_ItemId == input.FK_ItemId)
|
||||
?? throw new InvalidOperationException($"Item {input.FK_ItemId} is not on this loading sheet");
|
||||
item.QuantityReceived = input.QuantityReceived;
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(sheet); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(sheet);
|
||||
}
|
||||
|
||||
public async Task<LoadingSheetResponse> ConfirmReceiptAsync(Guid id, ConfirmReceiptRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.DispatchConfirmed)
|
||||
throw new InvalidOperationException($"Cannot confirm receipt for a sheet in {sheet.Status} status");
|
||||
|
||||
if (sheet.Items.Any(i => !i.QuantityReceived.HasValue))
|
||||
throw new InvalidOperationException("All items must have a received quantity before confirming receipt");
|
||||
|
||||
var ride = await rideRepo.GetByIdAsync(sheet.FK_RideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
|
||||
var hasDiscrepancy = sheet.Items.Any(i => i.QuantityReceived != i.QuantityDispatched);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await ApplyLoadMovementsAsync(sheet.Id, ride.FK_VehicleId, sheet.Items, userId);
|
||||
if (hasDiscrepancy)
|
||||
{
|
||||
sheet.Status = LoadingSheetStatus.DiscrepancyFlagged;
|
||||
}
|
||||
else
|
||||
{
|
||||
sheet.Status = LoadingSheetStatus.ReceiptConfirmed;
|
||||
sheet.ReceiptConfirmedAt = DateTime.Now;
|
||||
sheet.ReceiptConfirmedBy = userId;
|
||||
|
||||
sheet.Status = LoadingSheetStatus.Confirmed;
|
||||
sheet.ConfirmedAt = DateTime.Now;
|
||||
sheet.ConfirmedBy = userId;
|
||||
var lines = sheet.Items.Select(i => (i.FK_ItemId, i.QuantityReceived!.Value)).ToList();
|
||||
await ApplyLoadMovementsAsync(sheet, ride.FK_VehicleId, lines, userId);
|
||||
|
||||
ride.Status = RideStatus.Active;
|
||||
ride.ActivatedAt = DateTime.Now;
|
||||
ride.Meterstart = request.Meterstart;
|
||||
rideRepo.Update(ride);
|
||||
}
|
||||
|
||||
repo.Update(sheet);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
var saved = await repo.GetByIdAsync(id);
|
||||
return ToResponse(saved!);
|
||||
return ToResponse(sheet);
|
||||
}
|
||||
|
||||
private async Task ApplyLoadMovementsAsync(Guid sheetId, Guid vehicleId, List<LoadingSheetItem> items, Guid userId)
|
||||
public async Task<LoadingSheetResponse> ResolveAsync(Guid id, ResolveDiscrepancyRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
foreach (var item in items.Where(i => i.QuantityDispatched > 0))
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.DiscrepancyFlagged)
|
||||
throw new InvalidOperationException($"Cannot resolve a sheet in {sheet.Status} status");
|
||||
|
||||
foreach (var input in request.Items)
|
||||
{
|
||||
var warehouseBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, item.FK_WarehouseId)
|
||||
?? throw new InvalidOperationException($"No stock found in warehouse for item {item.FK_ItemId}");
|
||||
var item = sheet.Items.FirstOrDefault(i => i.FK_ItemId == input.FK_ItemId)
|
||||
?? throw new InvalidOperationException($"Item {input.FK_ItemId} is not on this loading sheet");
|
||||
if (input.QuantityResolved < 0)
|
||||
throw new InvalidOperationException("Resolved quantity cannot be negative");
|
||||
item.QuantityResolved = input.QuantityResolved;
|
||||
}
|
||||
|
||||
if (warehouseBalance.Quantity < item.QuantityDispatched)
|
||||
throw new InvalidOperationException($"Insufficient warehouse stock for item {item.FK_ItemId}");
|
||||
if (sheet.Items.Any(i => !i.QuantityResolved.HasValue))
|
||||
throw new InvalidOperationException("All items must have a resolved quantity");
|
||||
|
||||
warehouseBalance.Quantity -= item.QuantityDispatched;
|
||||
var ride = await rideRepo.GetByIdAsync(sheet.FK_RideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
|
||||
sheet.Status = LoadingSheetStatus.Resolved;
|
||||
sheet.ReceiptConfirmedAt = DateTime.Now;
|
||||
sheet.ReceiptConfirmedBy = userId;
|
||||
|
||||
var lines = sheet.Items.Select(i => (i.FK_ItemId, i.QuantityResolved!.Value)).ToList();
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await ApplyLoadMovementsAsync(sheet, ride.FK_VehicleId, lines, userId);
|
||||
|
||||
ride.Status = RideStatus.Active;
|
||||
ride.ActivatedAt = DateTime.Now;
|
||||
ride.Meterstart = request.Meterstart;
|
||||
rideRepo.Update(ride);
|
||||
|
||||
repo.Update(sheet);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(sheet);
|
||||
}
|
||||
|
||||
public async Task<LoadingSheetResponse> PatchReceiptAsync(Guid id, List<PatchReceiptItemInput> items, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.DispatchConfirmed)
|
||||
throw new InvalidOperationException($"Cannot set receipt for a sheet in {sheet.Status} status");
|
||||
|
||||
foreach (var input in items)
|
||||
{
|
||||
var sheetItem = sheet.Items.FirstOrDefault(i => i.FK_ItemId == input.ItemId)
|
||||
?? throw new InvalidOperationException($"Item {input.ItemId} is not on this loading sheet");
|
||||
sheetItem.QuantityReceived = input.QuantityReceived;
|
||||
}
|
||||
|
||||
var ride = await rideRepo.GetByIdAsync(sheet.FK_RideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
|
||||
var hasDiscrepancy = sheet.Items.Any(i => i.QuantityReceived != i.QuantityDispatched);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
if (hasDiscrepancy)
|
||||
{
|
||||
sheet.Status = LoadingSheetStatus.DiscrepancyFlagged;
|
||||
}
|
||||
else
|
||||
{
|
||||
sheet.Status = LoadingSheetStatus.ReceiptConfirmed;
|
||||
sheet.ReceiptConfirmedAt = DateTime.Now;
|
||||
sheet.ReceiptConfirmedBy = userId;
|
||||
|
||||
var lines = sheet.Items.Select(i => (i.FK_ItemId, i.QuantityReceived!.Value)).ToList();
|
||||
await ApplyLoadMovementsAsync(sheet, ride.FK_VehicleId, lines, userId);
|
||||
|
||||
ride.Status = RideStatus.Active;
|
||||
ride.ActivatedAt = DateTime.Now;
|
||||
ride.Meterstart = 0;
|
||||
rideRepo.Update(ride);
|
||||
}
|
||||
|
||||
repo.Update(sheet);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(sheet);
|
||||
}
|
||||
|
||||
public async Task<LoadingSheetResponse> PatchResolveAsync(Guid id, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var sheet = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Loading sheet not found");
|
||||
|
||||
if (sheet.Status != LoadingSheetStatus.DiscrepancyFlagged)
|
||||
throw new InvalidOperationException($"Cannot resolve a sheet in {sheet.Status} status");
|
||||
|
||||
foreach (var item in sheet.Items)
|
||||
{
|
||||
if (!item.QuantityReceived.HasValue)
|
||||
throw new InvalidOperationException($"Item {item.FK_ItemId} has no received quantity to resolve from");
|
||||
item.QuantityResolved = item.QuantityReceived.Value;
|
||||
}
|
||||
|
||||
var ride = await rideRepo.GetByIdAsync(sheet.FK_RideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
|
||||
sheet.Status = LoadingSheetStatus.Resolved;
|
||||
sheet.ReceiptConfirmedAt = DateTime.Now;
|
||||
sheet.ReceiptConfirmedBy = userId;
|
||||
|
||||
var lines = sheet.Items.Select(i => (i.FK_ItemId, i.QuantityResolved!.Value)).ToList();
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await ApplyLoadMovementsAsync(sheet, ride.FK_VehicleId, lines, userId);
|
||||
|
||||
ride.Status = RideStatus.Active;
|
||||
ride.ActivatedAt = DateTime.Now;
|
||||
ride.Meterstart = 0;
|
||||
rideRepo.Update(ride);
|
||||
|
||||
repo.Update(sheet);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(sheet);
|
||||
}
|
||||
|
||||
private async Task ApplyLoadMovementsAsync(LoadingSheet sheet, Guid vehicleId, List<(Guid ItemId, decimal Qty)> lines, Guid userId)
|
||||
{
|
||||
foreach (var (itemId, qty) in lines.Where(l => l.Qty > 0))
|
||||
{
|
||||
var warehouseBalance = await stockRepo.GetBalanceAsync(itemId, sheet.FK_SourceWarehouseId)
|
||||
?? throw new InvalidOperationException($"No stock found in warehouse for item {itemId}");
|
||||
if (warehouseBalance.Quantity < qty)
|
||||
throw new InvalidOperationException($"Insufficient warehouse stock for item {itemId}");
|
||||
|
||||
warehouseBalance.Quantity -= qty;
|
||||
warehouseBalance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(warehouseBalance);
|
||||
|
||||
var vehicleBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId);
|
||||
var vehicleBalance = await stockRepo.GetBalanceAsync(itemId, vehicleId);
|
||||
if (vehicleBalance is null)
|
||||
{
|
||||
await stockRepo.AddBalanceAsync(new StockBalance
|
||||
{
|
||||
FK_ItemId = item.FK_ItemId,
|
||||
FK_ItemId = itemId,
|
||||
LocationId = vehicleId,
|
||||
LocationType = LocationType.Vehicle,
|
||||
Quantity = item.QuantityDispatched
|
||||
Quantity = qty
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
vehicleBalance.Quantity += item.QuantityDispatched;
|
||||
vehicleBalance.Quantity += qty;
|
||||
vehicleBalance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(vehicleBalance);
|
||||
}
|
||||
|
||||
await stockRepo.AddMovementAsync(new StockMovement
|
||||
{
|
||||
FK_ItemId = item.FK_ItemId,
|
||||
FromLocationId = item.FK_WarehouseId,
|
||||
FK_ItemId = itemId,
|
||||
FromLocationId = sheet.FK_SourceWarehouseId,
|
||||
FromLocationType = LocationType.Warehouse,
|
||||
ToLocationId = vehicleId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
Quantity = item.QuantityDispatched,
|
||||
Quantity = qty,
|
||||
Type = MovementType.Load,
|
||||
ReferenceId = sheetId,
|
||||
ReferenceId = sheet.Id,
|
||||
CreatedBy = userId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static LoadingSheetResponse ToResponse(LoadingSheet s) =>
|
||||
new(s.Id, s.FK_RideId, s.Status, s.PopulatedBy, s.PopulatedAt,
|
||||
s.ConfirmedAt, s.ConfirmedBy, s.CreatedAt,
|
||||
new(s.Id, s.FK_RideId, s.FK_SourceWarehouseId,
|
||||
s.SourceWarehouse?.Name ?? string.Empty,
|
||||
s.Status,
|
||||
s.DispatchConfirmedAt, s.DispatchConfirmedBy,
|
||||
s.ReceiptConfirmedAt, s.ReceiptConfirmedBy,
|
||||
s.CreatedAt,
|
||||
s.Items.Select(i => new LoadingSheetItemResponse(
|
||||
i.Id, i.FK_ItemId, i.Item?.Name ?? string.Empty,
|
||||
i.FK_WarehouseId, i.Warehouse?.Name ?? string.Empty,
|
||||
i.QuantityDispatched, i.FK_ShopPOItemId)).ToList(),
|
||||
s.Orders.Select(o => new LoadingSheetOrderResponse(o.FK_ShopPOId)).ToList());
|
||||
i.QuantityDispatched, i.QuantityReceived, i.QuantityResolved)).ToList());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public interface IOrderRepository
|
||||
{
|
||||
Task<Models.Order?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Order>> GetAllAsync();
|
||||
Task<Models.Order?> GetBySaleIdAsync(Guid saleId);
|
||||
Task AddAsync(Models.Order entity);
|
||||
void Update(Models.Order entity);
|
||||
void Remove(Models.Order entity);
|
||||
Task<List<Models.OrderItem>> GetItemsByOrderIdAsync(Guid orderId);
|
||||
Task<List<Models.Order>> GetByStoreIdAsync(Guid storeId);
|
||||
Task AddItemsAsync(IEnumerable<Models.OrderItem> items);
|
||||
Task<List<Models.Order>> GetPendingByRouteIdAsync(Guid routeId);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/orders")]
|
||||
public class OrderController(OrderService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("store/{storeId:guid}")]
|
||||
public async Task<IActionResult> GetByStore(Guid storeId)
|
||||
{
|
||||
var result = await service.GetByStoreIdAsync(storeId);
|
||||
return result is null ? Forbid() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return result is null ? Forbid() : CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateOrderRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public record CreateOrderRequest(Guid SaleId);
|
||||
|
||||
public record UpdateOrderRequest(OrderStatus Status);
|
||||
|
||||
public record OrderItemResponse(Guid ItemId, int Quantity, int QuantityFulfilled, decimal UnitPrice);
|
||||
|
||||
public record OrderResponse(
|
||||
Guid Id,
|
||||
Guid SaleId,
|
||||
DateTime OrderDate,
|
||||
Guid StoreId,
|
||||
decimal TotalAmount,
|
||||
OrderStatus Status,
|
||||
DateTime? FulfilledAt,
|
||||
List<OrderItemResponse> Items,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
@@ -0,0 +1,49 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public class OrderRepository(AppDBContext context) : IOrderRepository
|
||||
{
|
||||
public async Task<Models.Order?> GetByIdAsync(Guid id) =>
|
||||
await context.Orders.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Order>> GetAllAsync() =>
|
||||
await context.Orders.ToListAsync();
|
||||
|
||||
public async Task<Models.Order?> GetBySaleIdAsync(Guid saleId) =>
|
||||
await context.Orders.FirstOrDefaultAsync(o => o.FK_SaleId == saleId && o.IsActive);
|
||||
|
||||
public async Task AddAsync(Models.Order entity) =>
|
||||
await context.Orders.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Order entity) =>
|
||||
context.Orders.Update(entity);
|
||||
|
||||
public void Remove(Models.Order entity)
|
||||
{
|
||||
entity.Status = Models.OrderStatus.Cancelled;
|
||||
entity.IsActive = false;
|
||||
context.Orders.Update(entity);
|
||||
}
|
||||
|
||||
public async Task<List<Models.OrderItem>> GetItemsByOrderIdAsync(Guid orderId) =>
|
||||
await context.OrderItems.Where(oi => oi.FK_OrderId == orderId).ToListAsync();
|
||||
|
||||
public async Task<List<Models.Order>> GetByStoreIdAsync(Guid storeId) =>
|
||||
await context.Orders.Where(o => o.FK_StoreId == storeId && o.IsActive).ToListAsync();
|
||||
|
||||
public async Task AddItemsAsync(IEnumerable<Models.OrderItem> items) =>
|
||||
await context.OrderItems.AddRangeAsync(items);
|
||||
|
||||
public async Task<List<Models.Order>> GetPendingByRouteIdAsync(Guid routeId) =>
|
||||
await context.Orders
|
||||
.Include(o => o.Store)
|
||||
.Include(o => o.OrderItems)
|
||||
.ThenInclude(i => i.Item)
|
||||
.Where(o => o.IsActive
|
||||
&& o.Store.FK_RouteId == routeId
|
||||
&& (o.Status == OrderStatus.Pending || o.Status == OrderStatus.PartiallyFulfilled))
|
||||
.ToListAsync();
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using Distribution.Features.Sale;
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.PriceList;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public class OrderService(
|
||||
IOrderRepository repo,
|
||||
IStoreRepository storeRepo,
|
||||
IItemRepository itemRepo,
|
||||
IStockRepository stockRepo,
|
||||
PriceListService priceListService,
|
||||
ISaleRepository saleRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser,
|
||||
IScopeAccessService scopeAccess)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
|
||||
private static OrderResponse ToResponse(Models.Order o, List<Models.OrderItem> items) =>
|
||||
new(o.Id, o.FK_SaleId, o.OrderDate, o.FK_StoreId, o.TotalAmount, o.Status, o.FulfilledAt,
|
||||
items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity, i.QuantityFulfilled, i.UnitPrice)).ToList(),
|
||||
o.IsActive, o.CreatedAt);
|
||||
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
|
||||
private async Task<bool> HasStoreScopeAsync(Guid storeId)
|
||||
{
|
||||
if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope;
|
||||
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
||||
}
|
||||
|
||||
public async Task<List<OrderResponse>> GetAllAsync()
|
||||
{
|
||||
var orders = (await repo.GetAllAsync()).Where(o => o.IsActive).ToList();
|
||||
if (!HasGlobalScope && currentUser.UserId is not null)
|
||||
{
|
||||
var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet();
|
||||
orders = orders.Where(o => accessibleStoreIds.Contains(o.FK_StoreId)).ToList();
|
||||
}
|
||||
var result = new List<OrderResponse>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var items = await repo.GetItemsByOrderIdAsync(order.Id);
|
||||
result.Add(ToResponse(order, items));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OrderResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(order.FK_StoreId)) return null;
|
||||
var items = await repo.GetItemsByOrderIdAsync(id);
|
||||
return ToResponse(order, items);
|
||||
}
|
||||
|
||||
public async Task<List<OrderResponse>?> GetByStoreIdAsync(Guid storeId)
|
||||
{
|
||||
if (!await HasStoreScopeAsync(storeId)) return null;
|
||||
var orders = await repo.GetByStoreIdAsync(storeId);
|
||||
var result = new List<OrderResponse>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var items = await repo.GetItemsByOrderIdAsync(order.Id);
|
||||
result.Add(ToResponse(order, items));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OrderResponse?> CreateAsync(CreateOrderRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var sale = await saleRepo.GetByIdAsync(request.SaleId);
|
||||
if (sale is null || !sale.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(sale.FK_StoreId)) return null;
|
||||
|
||||
var existingOrder = await repo.GetBySaleIdAsync(request.SaleId)
|
||||
?? throw new InvalidOperationException("Orders are created automatically from sales");
|
||||
|
||||
var items = await repo.GetItemsByOrderIdAsync(existingOrder.Id);
|
||||
return ToResponse(existingOrder, items);
|
||||
}
|
||||
|
||||
public async Task<OrderResponse?> UpdateAsync(Guid id, UpdateOrderRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(order.FK_StoreId)) return null;
|
||||
|
||||
var sale = await saleRepo.GetByIdAsync(order.FK_SaleId)
|
||||
?? throw new InvalidOperationException("Sale not found for this order");
|
||||
|
||||
order.Status = request.Status;
|
||||
order.FulfilledAt = request.Status == OrderStatus.Fulfilled ? DateTime.Now : null;
|
||||
|
||||
sale.Status = request.Status switch
|
||||
{
|
||||
OrderStatus.Fulfilled => SaleStatus.Completed,
|
||||
OrderStatus.Cancelled => SaleStatus.Cancelled,
|
||||
_ => SaleStatus.Pending
|
||||
};
|
||||
sale.CompletedAt = request.Status == OrderStatus.Fulfilled ? DateTime.Now : null;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(order);
|
||||
saleRepo.Update(sale);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
|
||||
var items = await repo.GetItemsByOrderIdAsync(id);
|
||||
return ToResponse(order, items);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return false;
|
||||
if (!await HasStoreScopeAsync(order.FK_StoreId)) return false;
|
||||
|
||||
var sale = await saleRepo.GetByIdAsync(order.FK_SaleId);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(order);
|
||||
if (sale is not null)
|
||||
{
|
||||
sale.Status = SaleStatus.Cancelled;
|
||||
sale.CompletedAt = null;
|
||||
saleRepo.Update(sale);
|
||||
}
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Payment;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/payments")]
|
||||
public class FlatPaymentController(PaymentService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] Guid? storeId,
|
||||
[FromQuery] Guid? saleId,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetAllAsync(storeId, saleId, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateFlatPaymentRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inner = new CreatePaymentRequest(request.Amount, request.Method);
|
||||
var result = await service.CreateAsync(request.SaleId, inner, ct);
|
||||
return result is null ? NotFound(new { error = "Sale not found or access denied" }) : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Payment;
|
||||
|
||||
public interface IPaymentRepository
|
||||
{
|
||||
Task<Models.Payment?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Payment>> GetAllAsync(Guid? invoiceId, int page, int pageSize);
|
||||
Task<int> GetCountAsync(Guid? invoiceId);
|
||||
Task<List<Models.Payment>> GetByInvoiceIdAsync(Guid invoiceId);
|
||||
Task<List<Models.Payment>> GetAllAsync(Guid? storeId, Guid? saleId, int page, int pageSize);
|
||||
Task<int> GetCountAsync(Guid? storeId, Guid? saleId);
|
||||
Task<List<Models.Payment>> GetBySaleIdAsync(Guid saleId);
|
||||
Task AddAsync(Models.Payment payment);
|
||||
void Update(Models.Payment payment);
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
namespace Distribution.Features.Payment;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/invoices/{invoiceId:guid}/payments")]
|
||||
[Route("api/sales/{saleId:guid}/payments")]
|
||||
public class PaymentController(PaymentService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetByInvoice(Guid invoiceId)
|
||||
public async Task<IActionResult> GetBySale(Guid saleId)
|
||||
{
|
||||
var result = await service.GetByInvoiceIdAsync(invoiceId);
|
||||
var result = await service.GetBySaleIdAsync(saleId);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(Guid invoiceId, CreatePaymentRequest request, CancellationToken ct)
|
||||
public async Task<IActionResult> Create(Guid saleId, CreatePaymentRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(invoiceId, request, ct);
|
||||
var result = await service.CreateAsync(saleId, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Payment;
|
||||
|
||||
public record CreatePaymentRequest(
|
||||
decimal Amount,
|
||||
PaymentMethod Method,
|
||||
EntryMode EntryMode = EntryMode.RealTime,
|
||||
string? ChequeNumber = null,
|
||||
DateTime? ChequeDate = null,
|
||||
Guid? CollectedByUserId = null);
|
||||
public record CreatePaymentRequest(decimal Amount, PaymentMethod Method);
|
||||
|
||||
public record CreateFlatPaymentRequest(Guid SaleId, decimal Amount, PaymentMethod Method);
|
||||
|
||||
public record PaymentResponse(
|
||||
Guid Id,
|
||||
Guid InvoiceId,
|
||||
Guid SaleId,
|
||||
Guid StoreId,
|
||||
decimal Amount,
|
||||
PaymentMethod Method,
|
||||
EntryMode EntryMode,
|
||||
string? ChequeNumber,
|
||||
DateTime? ChequeDate,
|
||||
DateTime CollectedAt,
|
||||
Guid CollectedBy,
|
||||
Guid? RecordedBy,
|
||||
DateTime CreatedAt);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -9,27 +9,29 @@ public class PaymentRepository(AppDBContext context) : IPaymentRepository
|
||||
public async Task<Models.Payment?> GetByIdAsync(Guid id) =>
|
||||
await context.Payments.FirstOrDefaultAsync(p => p.Id == id && p.IsActive);
|
||||
|
||||
public async Task<List<Models.Payment>> GetAllAsync(Guid? invoiceId, int page, int pageSize) =>
|
||||
public async Task<List<Models.Payment>> GetAllAsync(Guid? storeId, Guid? saleId, int page, int pageSize) =>
|
||||
await context.Payments
|
||||
.Where(p => p.IsActive && (invoiceId == null || p.FK_InvoiceId == invoiceId))
|
||||
.Where(p => p.IsActive
|
||||
&& (storeId == null || p.FK_StoreId == storeId)
|
||||
&& (saleId == null || p.FK_SaleId == saleId))
|
||||
.OrderByDescending(p => p.CollectedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<int> GetCountAsync(Guid? invoiceId) =>
|
||||
public async Task<int> GetCountAsync(Guid? storeId, Guid? saleId) =>
|
||||
await context.Payments
|
||||
.CountAsync(p => p.IsActive && (invoiceId == null || p.FK_InvoiceId == invoiceId));
|
||||
.Where(p => p.IsActive
|
||||
&& (storeId == null || p.FK_StoreId == storeId)
|
||||
&& (saleId == null || p.FK_SaleId == saleId))
|
||||
.CountAsync();
|
||||
|
||||
public async Task<List<Models.Payment>> GetByInvoiceIdAsync(Guid invoiceId) =>
|
||||
public async Task<List<Models.Payment>> GetBySaleIdAsync(Guid saleId) =>
|
||||
await context.Payments
|
||||
.Where(p => p.FK_InvoiceId == invoiceId && p.IsActive)
|
||||
.Where(p => p.FK_SaleId == saleId && p.IsActive)
|
||||
.OrderBy(p => p.CollectedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Payment payment) =>
|
||||
await context.Payments.AddAsync(payment);
|
||||
|
||||
public void Update(Models.Payment payment) =>
|
||||
context.Payments.Update(payment);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Features.Invoice;
|
||||
using Distribution.Features.Sale;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -7,52 +8,52 @@ namespace Distribution.Features.Payment;
|
||||
|
||||
public class PaymentService(
|
||||
IPaymentRepository repo,
|
||||
IInvoiceRepository invoiceRepo,
|
||||
ISaleRepository saleRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
private static PaymentResponse ToResponse(Models.Payment p) =>
|
||||
new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.EntryMode, p.ChequeNumber, p.ChequeDate,
|
||||
p.CollectedAt, p.CollectedBy, p.RecordedBy, p.CreatedAt);
|
||||
new(p.Id, p.FK_SaleId, p.FK_StoreId, p.Amount, p.Method, p.CollectedAt, p.CollectedBy, p.CreatedAt);
|
||||
|
||||
public async Task<List<PaymentResponse>?> GetByInvoiceIdAsync(Guid invoiceId)
|
||||
public async Task<PagedResponse<PaymentResponse>> GetAllAsync(Guid? storeId, Guid? saleId, int page, int pageSize)
|
||||
{
|
||||
var invoice = await invoiceRepo.GetByIdAsync(invoiceId);
|
||||
if (invoice is null || !invoice.IsActive) return null;
|
||||
var total = await repo.GetCountAsync(storeId, saleId);
|
||||
var items = await repo.GetAllAsync(storeId, saleId, page, pageSize);
|
||||
return new PagedResponse<PaymentResponse>(page, pageSize, total, items.Select(ToResponse).ToList());
|
||||
}
|
||||
|
||||
var payments = await repo.GetByInvoiceIdAsync(invoiceId);
|
||||
public async Task<PaymentResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var payment = await repo.GetByIdAsync(id);
|
||||
return payment is null ? null : ToResponse(payment);
|
||||
}
|
||||
|
||||
public async Task<List<PaymentResponse>?> GetBySaleIdAsync(Guid saleId)
|
||||
{
|
||||
var sale = await saleRepo.GetByIdAsync(saleId);
|
||||
if (sale is null || !sale.IsActive) return null;
|
||||
|
||||
var payments = await repo.GetBySaleIdAsync(saleId);
|
||||
return payments.Select(ToResponse).ToList();
|
||||
}
|
||||
|
||||
public async Task<PaymentResponse?> CreateAsync(Guid invoiceId, CreatePaymentRequest request, CancellationToken ct = default)
|
||||
public async Task<PaymentResponse?> CreateAsync(Guid saleId, CreatePaymentRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var invoice = await invoiceRepo.GetByIdAsync(invoiceId);
|
||||
if (invoice is null || !invoice.IsActive) return null;
|
||||
if (invoice.Status == InvoiceStatus.Finalized)
|
||||
throw new InvalidOperationException("Cannot record a payment against a finalized invoice");
|
||||
var sale = await saleRepo.GetByIdAsync(saleId);
|
||||
if (sale is null || !sale.IsActive) return null;
|
||||
if (sale.Status == SaleStatus.Cancelled)
|
||||
throw new InvalidOperationException("Cannot record a payment against a cancelled sale");
|
||||
|
||||
if (request.Method == PaymentMethod.Cheque &&
|
||||
(string.IsNullOrWhiteSpace(request.ChequeNumber) || request.ChequeDate is null))
|
||||
throw new InvalidOperationException("ChequeNumber and ChequeDate are required when Method is Cheque");
|
||||
|
||||
var recorderId = currentUser.UserId ?? request.CollectedByUserId
|
||||
?? throw new InvalidOperationException("CollectedByUserId is required while auth is unavailable");
|
||||
var collectedBy = request.CollectedByUserId ?? recorderId;
|
||||
var recordedBy = request.EntryMode == EntryMode.DayEndManual && collectedBy != recorderId
|
||||
? recorderId
|
||||
: (Guid?)null;
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
|
||||
var payment = new Models.Payment
|
||||
{
|
||||
FK_InvoiceId = invoiceId,
|
||||
FK_SaleId = saleId,
|
||||
FK_StoreId = sale.FK_StoreId,
|
||||
Amount = request.Amount,
|
||||
Method = request.Method,
|
||||
EntryMode = request.EntryMode,
|
||||
ChequeNumber = request.Method == PaymentMethod.Cheque ? request.ChequeNumber : null,
|
||||
ChequeDate = request.Method == PaymentMethod.Cheque ? request.ChequeDate : null,
|
||||
CollectedAt = DateTime.Now,
|
||||
CollectedBy = collectedBy,
|
||||
RecordedBy = recordedBy
|
||||
CollectedBy = userId
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
@@ -60,16 +61,17 @@ public class PaymentService(
|
||||
{
|
||||
await repo.AddAsync(payment);
|
||||
|
||||
var allPayments = await repo.GetByInvoiceIdAsync(invoiceId);
|
||||
var allPayments = await repo.GetBySaleIdAsync(saleId);
|
||||
var total = allPayments.Sum(p => p.Amount) + request.Amount;
|
||||
|
||||
invoice.PaymentStatus = total == 0
|
||||
sale.PaymentStatus = total == 0
|
||||
? PaymentStatus.Pending
|
||||
: total < invoice.TotalAmount
|
||||
: total < sale.TotalAmount
|
||||
? PaymentStatus.PartiallyPaid
|
||||
: PaymentStatus.Paid;
|
||||
|
||||
invoiceRepo.Update(invoice);
|
||||
saleRepo.Update(sale);
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
@@ -10,13 +10,6 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? regionId) =>
|
||||
Ok(await service.GetAllAsync(regionId));
|
||||
|
||||
[HttpGet("resolve")]
|
||||
public async Task<IActionResult> Resolve([FromQuery] Guid itemId, [FromQuery] Guid storeId, [FromQuery] DateTime saleDate)
|
||||
{
|
||||
var result = await service.ResolveByStoreAsync(itemId, storeId, saleDate);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
@@ -69,14 +62,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
[HttpDelete("{id:guid}/items/{sku}")]
|
||||
public async Task<IActionResult> RemoveItem(Guid id, string sku, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.RemoveItemAsync(id, sku, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Conflict(new { error = ex.Message });
|
||||
}
|
||||
var result = await service.RemoveItemAsync(id, sku, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,16 @@ namespace Distribution.Features.PriceList;
|
||||
|
||||
public record CreatePriceListRequest(string Name, Guid? RegionId, DateTime EffectiveFrom, DateTime EffectiveTo);
|
||||
|
||||
public record UpdatePriceListRequest(string Name, Guid? RegionId, DateTime EffectiveFrom, DateTime EffectiveTo, bool IsActive);
|
||||
public record UpdatePriceListRequest(string Name, Guid? RegionId, DateTime EffectiveFrom, DateTime EffectiveTo);
|
||||
|
||||
public record UpsertPriceListItemRequest(
|
||||
decimal Price,
|
||||
decimal? MinPrice = null,
|
||||
decimal? RetailPrice = null,
|
||||
decimal? AgencyMarginPercent = null);
|
||||
public record UpsertPriceListItemRequest(decimal Price, decimal? MinPrice = null);
|
||||
|
||||
public record PriceListItemResponse(
|
||||
Guid Id,
|
||||
Guid ItemId,
|
||||
string Sku,
|
||||
string ItemName,
|
||||
decimal Price,
|
||||
decimal? MinPrice,
|
||||
decimal? RetailPrice,
|
||||
decimal? AgencyMarginPercent);
|
||||
public record PriceListItemResponse(Guid Id, Guid ItemId, string Sku, string ItemName, decimal Price, decimal? MinPrice);
|
||||
|
||||
/// <summary>
|
||||
/// A resolved price with its negotiable floor, plus the informational retail price and agency
|
||||
/// margin when the resolution came from an actual PriceListItem (both null on the Item.SalePrice
|
||||
/// fallback, since neither is tracked at the item level).
|
||||
/// A resolved price with its negotiable floor. MinPrice null = no negotiation, Price is fixed.
|
||||
/// </summary>
|
||||
public record PriceResolution(decimal Price, decimal? MinPrice, decimal? RetailPrice, decimal? AgencyMarginPercent);
|
||||
public record PriceResolution(decimal Price, decimal? MinPrice);
|
||||
|
||||
public record PriceListResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -13,7 +13,9 @@ public class PriceListRepository(AppDBContext context) : IPriceListRepository
|
||||
|
||||
public async Task<List<Models.PriceList>> GetAllAsync(Guid? regionId = null)
|
||||
{
|
||||
var query = context.PriceLists.Include(pl => pl.Region).AsQueryable();
|
||||
var query = context.PriceLists
|
||||
.Include(pl => pl.Region)
|
||||
.Where(pl => pl.IsActive);
|
||||
|
||||
if (regionId.HasValue) query = query.Where(pl => pl.FK_RegionId == regionId);
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
public class PriceListService(IPriceListRepository repo, IItemRepository itemRepo, IStoreRepository storeRepo, IUnitOfWork uow)
|
||||
public class PriceListService(IPriceListRepository repo, IItemRepository itemRepo, IUnitOfWork uow)
|
||||
{
|
||||
private static PriceListItemResponse MapItem(Models.PriceListItem i) =>
|
||||
new(i.Id, i.FK_ItemId, i.Item.Sku, i.Item.Name, i.Price, i.MinPrice, i.RetailPrice, i.AgencyMarginPercent);
|
||||
new(i.Id, i.FK_ItemId, i.Item.Sku, i.Item.Name, i.Price, i.MinPrice);
|
||||
|
||||
private static PriceListResponse MapDetail(Models.PriceList pl) =>
|
||||
new(pl.Id, pl.Name, pl.FK_RegionId, pl.Region?.Name,
|
||||
@@ -25,7 +24,7 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
public async Task<PriceListResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var pl = await repo.GetByIdAsync(id);
|
||||
if (pl is null) return null;
|
||||
if (pl is null || !pl.IsActive) return null;
|
||||
return MapDetail(pl);
|
||||
}
|
||||
|
||||
@@ -49,7 +48,7 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
public async Task<PriceListResponse?> UpdateAsync(Guid id, UpdatePriceListRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = await repo.GetByIdAsync(id);
|
||||
if (priceList is null) return null;
|
||||
if (priceList is null || !priceList.IsActive) return null;
|
||||
|
||||
foreach (var item in priceList.Items.Where(i => i.IsActive))
|
||||
{
|
||||
@@ -62,7 +61,6 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
priceList.FK_RegionId = request.RegionId;
|
||||
priceList.EffectiveFrom = request.EffectiveFrom;
|
||||
priceList.EffectiveTo = request.EffectiveTo;
|
||||
priceList.IsActive = request.IsActive;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(priceList); await uow.CommitAsync(ct); }
|
||||
@@ -92,9 +90,7 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
public async Task<PriceListResponse?> UpsertItemAsync(Guid priceListId, string sku, UpsertPriceListItemRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = await repo.GetByIdAsync(priceListId);
|
||||
if (priceList is null) return null;
|
||||
if (!priceList.IsActive)
|
||||
throw new InvalidOperationException("Cannot price items on an inactive price list. Reactivate it first.");
|
||||
if (priceList is null || !priceList.IsActive) return null;
|
||||
|
||||
var item = await itemRepo.GetBySkuAsync(sku);
|
||||
if (item is null) return null;
|
||||
@@ -103,13 +99,6 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
throw new InvalidOperationException(
|
||||
$"MinPrice ({request.MinPrice}) cannot exceed Price ({request.Price}) for item '{sku}'.");
|
||||
|
||||
if (request.RetailPrice.HasValue && request.RetailPrice.Value < request.Price)
|
||||
throw new InvalidOperationException(
|
||||
$"RetailPrice ({request.RetailPrice}) cannot be less than Price ({request.Price}) for item '{sku}' — the store would sell at a loss.");
|
||||
|
||||
if (request.AgencyMarginPercent is < 0 or > 100)
|
||||
throw new InvalidOperationException("AgencyMarginPercent must be between 0 and 100.");
|
||||
|
||||
if (await repo.HasOverlapAsync(item.Id, priceList.FK_RegionId, priceList.EffectiveFrom, priceList.EffectiveTo, priceListId))
|
||||
throw new InvalidOperationException(
|
||||
$"Overlapping price list already exists for item '{sku}' in the same region for this date range.");
|
||||
@@ -126,17 +115,13 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
FK_PriceListId = priceListId,
|
||||
FK_ItemId = item.Id,
|
||||
Price = request.Price,
|
||||
MinPrice = request.MinPrice,
|
||||
RetailPrice = request.RetailPrice,
|
||||
AgencyMarginPercent = request.AgencyMarginPercent
|
||||
MinPrice = request.MinPrice
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.Price = request.Price;
|
||||
existing.MinPrice = request.MinPrice;
|
||||
existing.RetailPrice = request.RetailPrice;
|
||||
existing.AgencyMarginPercent = request.AgencyMarginPercent;
|
||||
repo.UpdateItem(existing);
|
||||
}
|
||||
await uow.CommitAsync(ct);
|
||||
@@ -149,9 +134,7 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
public async Task<PriceListResponse?> RemoveItemAsync(Guid priceListId, string sku, CancellationToken ct = default)
|
||||
{
|
||||
var priceList = await repo.GetByIdAsync(priceListId);
|
||||
if (priceList is null) return null;
|
||||
if (!priceList.IsActive)
|
||||
throw new InvalidOperationException("Cannot remove items from an inactive price list. Reactivate it first.");
|
||||
if (priceList is null || !priceList.IsActive) return null;
|
||||
|
||||
var item = await itemRepo.GetBySkuAsync(sku);
|
||||
if (item is null) return null;
|
||||
@@ -177,36 +160,12 @@ public class PriceListService(IPriceListRepository repo, IItemRepository itemRep
|
||||
if (regionId.HasValue)
|
||||
{
|
||||
var regionMatches = await repo.GetActiveItemsAsync(itemId, regionId, orderDate);
|
||||
if (regionMatches.Count > 0)
|
||||
{
|
||||
var m = regionMatches[0];
|
||||
return new PriceResolution(m.Price, m.MinPrice, m.RetailPrice, m.AgencyMarginPercent);
|
||||
}
|
||||
if (regionMatches.Count > 0) return new PriceResolution(regionMatches[0].Price, regionMatches[0].MinPrice);
|
||||
}
|
||||
|
||||
var globalMatches = await repo.GetActiveItemsAsync(itemId, null, orderDate);
|
||||
if (globalMatches.Count > 0)
|
||||
{
|
||||
var m = globalMatches[0];
|
||||
return new PriceResolution(m.Price, m.MinPrice, m.RetailPrice, m.AgencyMarginPercent);
|
||||
}
|
||||
if (globalMatches.Count > 0) return new PriceResolution(globalMatches[0].Price, globalMatches[0].MinPrice);
|
||||
|
||||
return itemSalePrice.HasValue ? new PriceResolution(itemSalePrice.Value, null, null, null) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same resolution `SaleService.ResolveUnitPriceAsync` uses internally, exposed read-only so
|
||||
/// the frontend can show the real price (region/date-scoped price list, else Item.SalePrice)
|
||||
/// before a sale is ever submitted, instead of guessing from the flat catalogue price.
|
||||
/// </summary>
|
||||
public async Task<PriceResolution?> ResolveByStoreAsync(Guid itemId, Guid storeId, DateTime saleDate)
|
||||
{
|
||||
var item = await itemRepo.GetByIdAsync(itemId);
|
||||
if (item is null) return null;
|
||||
|
||||
var store = await storeRepo.GetByIdWithRegionAsync(storeId);
|
||||
if (store is null) return null;
|
||||
|
||||
return await ResolvePriceAsync(itemId, store.Route?.FK_RegionId, item.SalePrice, saleDate);
|
||||
return itemSalePrice.HasValue ? new PriceResolution(itemSalePrice.Value, null) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,10 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreatePoRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
// (testbug.md #28) — fall back to the explicitly supplied user until that's fixed.
|
||||
var userId = currentUser.UserId ?? request.CreatedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "CreatedByUserId is required while auth is unavailable" });
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(request, userId.Value, ct);
|
||||
var result = await service.CreateAsync(request, userId, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
|
||||
@@ -4,11 +4,7 @@ namespace Distribution.Features.Po;
|
||||
|
||||
public record PoItemRequest(Guid ItemId, int Quantity, decimal UnitCost);
|
||||
|
||||
public record CreatePoRequest(
|
||||
Guid SupplierId,
|
||||
List<PoItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? CreatedByUserId = null);
|
||||
public record CreatePoRequest(Guid SupplierId, List<PoItemRequest> Items);
|
||||
|
||||
/// <summary>
|
||||
/// Update line: PurchaseOrderItemId set = update an existing unreceived line (Quantity/UnitCost),
|
||||
|
||||
@@ -20,13 +20,10 @@ public class PurchaseReturnController(PurchaseReturnService service, ICurrentUse
|
||||
[HttpPost("api/purchase-orders/{id:guid}/returns")]
|
||||
public async Task<IActionResult> Create(Guid id, CreatePurchaseReturnRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
// (testbug.md #28) — fall back to the explicitly supplied user until that's fixed.
|
||||
var userId = currentUser.UserId ?? request.CreatedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "CreatedByUserId is required while auth is unavailable" });
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(id, request, userId.Value, ct);
|
||||
var result = await service.CreateAsync(id, request, userId, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
|
||||
@@ -2,11 +2,7 @@ namespace Distribution.Features.PurchaseReturn;
|
||||
|
||||
public record PurchaseReturnItemRequest(Guid ItemId, int Quantity);
|
||||
|
||||
public record CreatePurchaseReturnRequest(
|
||||
string Reason,
|
||||
List<PurchaseReturnItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? CreatedByUserId = null);
|
||||
public record CreatePurchaseReturnRequest(string Reason, List<PurchaseReturnItemRequest> Items);
|
||||
|
||||
public record PurchaseReturnItemResponse(Guid Id, Guid ItemId, string Sku, string ItemName, int Quantity);
|
||||
|
||||
|
||||
@@ -104,8 +104,6 @@ public class PurchaseReturnService(
|
||||
FK_ItemId = line.FK_ItemId,
|
||||
FromLocationId = warehouseId,
|
||||
FromLocationType = LocationType.Warehouse,
|
||||
ToLocationId = StockService.ExternalLocationId,
|
||||
ToLocationType = LocationType.External,
|
||||
Quantity = line.Quantity,
|
||||
Type = MovementType.PurchaseReturn,
|
||||
ReasonCode = request.Reason,
|
||||
|
||||
@@ -42,5 +42,5 @@ public interface IRegionRepository
|
||||
Task<List<Models.Route>> GetRoutesAsync(Guid regionId);
|
||||
Task<List<Models.Store>> GetStoresByRouteIdsAsync(List<Guid> routeIds);
|
||||
|
||||
Task<List<Models.ShopPO>> GetOrdersForDashboardAsync(Guid regionId, DateTime periodStart, DateTime periodEndExclusive);
|
||||
Task<List<Models.Order>> GetOrdersForDashboardAsync(Guid regionId, DateTime periodStart, DateTime periodEndExclusive);
|
||||
}
|
||||
|
||||
@@ -129,11 +129,11 @@ public class RegionRepository(AppDBContext context) : IRegionRepository
|
||||
public async Task<List<Models.Store>> GetStoresByRouteIdsAsync(List<Guid> routeIds) =>
|
||||
await context.Stores.Where(s => routeIds.Contains(s.FK_RouteId) && s.IsActive).ToListAsync();
|
||||
|
||||
public async Task<List<Models.ShopPO>> GetOrdersForDashboardAsync(Guid regionId, DateTime periodStart, DateTime periodEndExclusive) =>
|
||||
await (from o in context.ShopPOs
|
||||
public async Task<List<Models.Order>> GetOrdersForDashboardAsync(Guid regionId, DateTime periodStart, DateTime periodEndExclusive) =>
|
||||
await (from o in context.Orders
|
||||
join s in context.Stores on o.FK_StoreId equals s.Id
|
||||
join r in context.Routes on s.FK_RouteId equals r.Id
|
||||
where r.FK_RegionId == regionId && o.IsActive && s.IsActive && r.IsActive
|
||||
&& o.CreatedAt >= periodStart && o.CreatedAt < periodEndExclusive
|
||||
&& o.OrderDate >= periodStart && o.OrderDate < periodEndExclusive
|
||||
select o).ToListAsync();
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public class RegionService(IRegionRepository repo, IUnitOfWork uow, ICurrentUser
|
||||
budget?.BudgetAmount,
|
||||
orders.Count,
|
||||
orders.Sum(o => o.TotalAmount),
|
||||
orders.Count == 0 ? null : orders.Max(o => o.CreatedAt),
|
||||
orders.Count == 0 ? null : orders.Max(o => o.OrderDate),
|
||||
taxRule?.RuleConfig,
|
||||
agencyMaps.Select(m => m.FK_AgencyUserId).ToList());
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@ namespace Distribution.Features.Returns;
|
||||
public interface IReturnRepository
|
||||
{
|
||||
Task<Models.SaleReturn?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.SaleReturn>> GetAllAsync();
|
||||
Task<List<Models.SaleReturn>> GetByShopPOIdAsync(Guid shopPOId);
|
||||
Task<List<Models.SaleReturn>> GetByOrderIdAsync(Guid orderId);
|
||||
Task AddAsync(Models.SaleReturn entity);
|
||||
void Update(Models.SaleReturn entity);
|
||||
Task AddCreditNoteAsync(Models.CreditNote entity);
|
||||
|
||||
/// <summary>Whether this store has ever had this item on any of its (active) ShopPOs.</summary>
|
||||
/// <summary>Whether this store has ever had this item on any of its (active) orders.</summary>
|
||||
Task<bool> StoreHasPriorSaleAsync(Guid storeId, Guid itemId);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Returns;
|
||||
@@ -6,10 +6,6 @@ namespace Distribution.Features.Returns;
|
||||
[ApiController]
|
||||
public class ReturnController(ReturnService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/returns")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/returns/{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
@@ -17,32 +13,19 @@ public class ReturnController(ReturnService service, ICurrentUserService current
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPatch("api/returns/{id:guid}/reasons")]
|
||||
public async Task<IActionResult> UpdateReasons(Guid id, UpdateSaleReturnReasonsRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateReasonsAsync(id, request, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
[HttpGet("api/orders/{id:guid}/returns")]
|
||||
public async Task<IActionResult> GetByOrder(Guid id) =>
|
||||
Ok(await service.GetByOrderIdAsync(id));
|
||||
|
||||
[HttpGet("api/shop-pos/{id:guid}/returns")]
|
||||
public async Task<IActionResult> GetByShopPO(Guid id) =>
|
||||
Ok(await service.GetByShopPOIdAsync(id));
|
||||
|
||||
[HttpPost("api/shop-pos/{id:guid}/returns")]
|
||||
[HttpPost("api/orders/{id:guid}/returns")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateSaleReturnRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.CreatedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "CreatedByUserId is required while auth is unavailable" });
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(id, request, userId.Value, ct);
|
||||
var result = await service.CreateAsync(id, request, userId, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (NotImplementedException ex) { return StatusCode(501, new { error = ex.Message }); }
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,7 @@ namespace Distribution.Features.Returns;
|
||||
|
||||
public record ReturnItemRequest(Guid ItemId, decimal Quantity, ReturnReasonCode ReasonCode);
|
||||
|
||||
public record CreateSaleReturnRequest(
|
||||
ReturnMode Mode,
|
||||
List<ReturnItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? CreatedByUserId = null);
|
||||
|
||||
// The return's financial/stock effect (credit, order total, vehicle stock credit) is applied
|
||||
// immediately on create with no separate approval step, so quantities/mode can never be edited
|
||||
// after the fact without desyncing stock and order totals. Only each line's reason code is editable.
|
||||
public record UpdateReturnReasonItem(Guid ItemId, ReturnReasonCode ReasonCode);
|
||||
public record UpdateSaleReturnReasonsRequest(List<UpdateReturnReasonItem> Items);
|
||||
public record CreateSaleReturnRequest(ReturnMode Mode, List<ReturnItemRequest> Items);
|
||||
|
||||
public record SaleReturnItemResponse(
|
||||
Guid Id,
|
||||
@@ -27,7 +17,7 @@ public record SaleReturnItemResponse(
|
||||
|
||||
public record SaleReturnResponse(
|
||||
Guid Id,
|
||||
Guid ShopPOId,
|
||||
Guid OrderId,
|
||||
ReturnMode Mode,
|
||||
decimal TotalCreditAmount,
|
||||
decimal OrderTotalAmountAfter,
|
||||
|
||||
@@ -7,37 +7,26 @@ public class ReturnRepository(AppDBContext context) : IReturnRepository
|
||||
{
|
||||
public async Task<Models.SaleReturn?> GetByIdAsync(Guid id) =>
|
||||
await context.SaleReturns
|
||||
.Include(r => r.ShopPO)
|
||||
.Include(r => r.Order)
|
||||
.Include(r => r.Items).ThenInclude(i => i.Item)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
public async Task<List<Models.SaleReturn>> GetAllAsync() =>
|
||||
public async Task<List<Models.SaleReturn>> GetByOrderIdAsync(Guid orderId) =>
|
||||
await context.SaleReturns
|
||||
.Include(r => r.ShopPO)
|
||||
.Include(r => r.Order)
|
||||
.Include(r => r.Items).ThenInclude(i => i.Item)
|
||||
.Where(r => r.IsActive)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Models.SaleReturn>> GetByShopPOIdAsync(Guid shopPOId) =>
|
||||
await context.SaleReturns
|
||||
.Include(r => r.ShopPO)
|
||||
.Include(r => r.Items).ThenInclude(i => i.Item)
|
||||
.Where(r => r.FK_ShopPOId == shopPOId && r.IsActive)
|
||||
.Where(r => r.FK_OrderId == orderId && r.IsActive)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.SaleReturn entity) =>
|
||||
await context.SaleReturns.AddAsync(entity);
|
||||
|
||||
public void Update(Models.SaleReturn entity) =>
|
||||
context.SaleReturns.Update(entity);
|
||||
|
||||
public async Task AddCreditNoteAsync(Models.CreditNote entity) =>
|
||||
await context.CreditNotes.AddAsync(entity);
|
||||
|
||||
public async Task<bool> StoreHasPriorSaleAsync(Guid storeId, Guid itemId) =>
|
||||
await context.ShopPOItems
|
||||
.Include(oi => oi.ShopPO)
|
||||
.AnyAsync(oi => oi.ShopPO.FK_StoreId == storeId && oi.ShopPO.IsActive && oi.FK_ItemId == itemId);
|
||||
await context.OrderItems
|
||||
.Include(oi => oi.Order)
|
||||
.AnyAsync(oi => oi.Order.FK_StoreId == storeId && oi.Order.IsActive && oi.FK_ItemId == itemId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Order;
|
||||
using Distribution.Features.Ride;
|
||||
using Distribution.Features.Sale;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -8,7 +10,9 @@ namespace Distribution.Features.Returns;
|
||||
|
||||
public class ReturnService(
|
||||
IReturnRepository repo,
|
||||
IShopPORepository shopPORepo,
|
||||
IOrderRepository orderRepo,
|
||||
ISaleRepository saleRepo,
|
||||
IRideRepository rideRepo,
|
||||
IItemRepository itemRepo,
|
||||
IStockRepository stockRepo,
|
||||
IUnitOfWork uow)
|
||||
@@ -17,7 +21,7 @@ public class ReturnService(
|
||||
new(i.Id, i.FK_ItemId, i.Item.Sku, i.Item.Name, i.Quantity, i.ReasonCode, i.UnitPrice);
|
||||
|
||||
private static SaleReturnResponse Map(SaleReturn r) =>
|
||||
new(r.Id, r.FK_ShopPOId, r.Mode, r.TotalCreditAmount, r.ShopPO.TotalAmount, r.IsActive, r.CreatedAt,
|
||||
new(r.Id, r.FK_OrderId, r.Mode, r.TotalCreditAmount, r.Order.TotalAmount, r.IsActive, r.CreatedAt,
|
||||
r.Items.Where(i => i.IsActive).Select(MapItem).ToList());
|
||||
|
||||
public async Task<SaleReturnResponse?> GetByIdAsync(Guid id)
|
||||
@@ -27,36 +31,139 @@ public class ReturnService(
|
||||
return Map(r);
|
||||
}
|
||||
|
||||
public async Task<List<SaleReturnResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync()).Select(Map).ToList();
|
||||
public async Task<List<SaleReturnResponse>> GetByOrderIdAsync(Guid orderId) =>
|
||||
(await repo.GetByOrderIdAsync(orderId)).Select(Map).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Quantities/mode are immutable once created (the stock/order effect already happened) —
|
||||
/// only each line's reason code can be corrected after the fact.
|
||||
/// Records a return recognized during the same store visit as an open order and settles it
|
||||
/// immediately: stock is credited back to the rep's vehicle, and either the order total is
|
||||
/// reduced (CreditDeduction) or the matching order line quantities are reduced (ItemExchange).
|
||||
/// A return is only allowed for an item the store has previously ordered.
|
||||
/// </summary>
|
||||
public async Task<SaleReturnResponse> UpdateReasonsAsync(Guid id, UpdateSaleReturnReasonsRequest request, CancellationToken ct = default)
|
||||
public async Task<SaleReturnResponse> CreateAsync(Guid orderId, CreateSaleReturnRequest request, Guid createdByUserId, CancellationToken ct = default)
|
||||
{
|
||||
var r = await repo.GetByIdAsync(id);
|
||||
if (r is null || !r.IsActive)
|
||||
throw new InvalidOperationException("Return not found.");
|
||||
var order = await orderRepo.GetByIdAsync(orderId);
|
||||
if (order is null || !order.IsActive)
|
||||
throw new InvalidOperationException("Order not found.");
|
||||
if (order.Status is OrderStatus.Cancelled or OrderStatus.Fulfilled)
|
||||
throw new InvalidOperationException("Cannot record a return against a closed order.");
|
||||
|
||||
if (request.Items is null || request.Items.Count == 0)
|
||||
throw new InvalidOperationException("A return must have at least one line item.");
|
||||
|
||||
var sale = await saleRepo.GetByIdAsync(order.FK_SaleId)
|
||||
?? throw new InvalidOperationException("Sale not found for this order.");
|
||||
if (sale.FK_RideId is null)
|
||||
throw new InvalidOperationException("A return can only be recorded for an order created during an active ride.");
|
||||
|
||||
var ride = await rideRepo.GetByIdAsync(sale.FK_RideId.Value)
|
||||
?? throw new InvalidOperationException("Ride not found.");
|
||||
var vehicleId = ride.FK_VehicleId;
|
||||
|
||||
var orderItems = await orderRepo.GetItemsByOrderIdAsync(orderId);
|
||||
var orderItemsByItemId = orderItems.ToDictionary(oi => oi.FK_ItemId);
|
||||
|
||||
var saleReturn = new SaleReturn
|
||||
{
|
||||
FK_OrderId = orderId,
|
||||
Mode = request.Mode,
|
||||
CreatedByUserId = createdByUserId
|
||||
};
|
||||
|
||||
foreach (var line in request.Items)
|
||||
{
|
||||
var item = r.Items.FirstOrDefault(i => i.FK_ItemId == line.ItemId && i.IsActive)
|
||||
?? throw new InvalidOperationException($"Item {line.ItemId} not found on this return.");
|
||||
item.ReasonCode = line.ReasonCode;
|
||||
if (line.Quantity <= 0)
|
||||
throw new InvalidOperationException("Return quantity must be greater than zero.");
|
||||
|
||||
var item = await itemRepo.GetByIdAsync(line.ItemId)
|
||||
?? throw new InvalidOperationException($"Item {line.ItemId} not found.");
|
||||
|
||||
if (!await repo.StoreHasPriorSaleAsync(order.FK_StoreId, line.ItemId))
|
||||
throw new InvalidOperationException($"No prior sale of item '{item.Name}' found for this store.");
|
||||
|
||||
var unitPrice = orderItemsByItemId.TryGetValue(line.ItemId, out var matchingOrderItem)
|
||||
? matchingOrderItem.UnitPrice
|
||||
: item.SalePrice ?? 0;
|
||||
|
||||
saleReturn.Items.Add(new SaleReturnItem
|
||||
{
|
||||
FK_ItemId = line.ItemId,
|
||||
Quantity = line.Quantity,
|
||||
ReasonCode = line.ReasonCode,
|
||||
UnitPrice = unitPrice
|
||||
});
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(r); await uow.CommitAsync(ct); }
|
||||
try
|
||||
{
|
||||
foreach (var line in saleReturn.Items)
|
||||
{
|
||||
var balance = await stockRepo.GetBalanceAsync(line.FK_ItemId, vehicleId);
|
||||
if (balance is null)
|
||||
{
|
||||
await stockRepo.AddBalanceAsync(new StockBalance
|
||||
{
|
||||
FK_ItemId = line.FK_ItemId,
|
||||
LocationId = vehicleId,
|
||||
LocationType = LocationType.Vehicle,
|
||||
Quantity = line.Quantity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
balance.Quantity += line.Quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(balance);
|
||||
}
|
||||
|
||||
await stockRepo.AddMovementAsync(new StockMovement
|
||||
{
|
||||
FK_ItemId = line.FK_ItemId,
|
||||
ToLocationId = vehicleId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
Quantity = line.Quantity,
|
||||
Type = MovementType.CustomerReturn,
|
||||
ReasonCode = line.ReasonCode.ToString(),
|
||||
ReferenceId = saleReturn.Id,
|
||||
CreatedBy = createdByUserId
|
||||
});
|
||||
}
|
||||
|
||||
await repo.AddAsync(saleReturn);
|
||||
|
||||
if (request.Mode == ReturnMode.CreditDeduction)
|
||||
{
|
||||
var total = saleReturn.Items.Sum(i => i.Quantity * i.UnitPrice);
|
||||
saleReturn.TotalCreditAmount = total;
|
||||
order.TotalAmount = Math.Max(0, order.TotalAmount - total);
|
||||
orderRepo.Update(order);
|
||||
|
||||
await repo.AddCreditNoteAsync(new CreditNote
|
||||
{
|
||||
FK_SaleReturnId = saleReturn.Id,
|
||||
Amount = total
|
||||
});
|
||||
}
|
||||
else // ItemExchange
|
||||
{
|
||||
foreach (var line in saleReturn.Items)
|
||||
{
|
||||
if (!orderItemsByItemId.TryGetValue(line.FK_ItemId, out var orderItem))
|
||||
continue; // no matching line on this order — nothing to offset
|
||||
|
||||
var offsetQty = Math.Min(line.Quantity, orderItem.Quantity);
|
||||
orderItem.Quantity -= (int)offsetQty;
|
||||
order.TotalAmount = Math.Max(0, order.TotalAmount - offsetQty * orderItem.UnitPrice);
|
||||
}
|
||||
orderRepo.Update(order);
|
||||
}
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(r);
|
||||
var saved = await repo.GetByIdAsync(saleReturn.Id);
|
||||
return Map(saved!);
|
||||
}
|
||||
|
||||
public async Task<List<SaleReturnResponse>> GetByShopPOIdAsync(Guid shopPOId) =>
|
||||
(await repo.GetByShopPOIdAsync(shopPOId)).Select(Map).ToList();
|
||||
|
||||
public Task<SaleReturnResponse> CreateAsync(Guid shopPOId, CreateSaleReturnRequest request, Guid createdByUserId, CancellationToken ct = default) =>
|
||||
throw new NotImplementedException("Return flow is being redesigned — not yet implemented.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Ride;
|
||||
|
||||
public interface IRideCancelRequestRepository
|
||||
{
|
||||
Task<RideCancelRequest?> GetByIdAsync(Guid id);
|
||||
Task<RideCancelRequest?> GetByRideIdAsync(Guid rideId);
|
||||
Task<RideCancelRequest?> GetPendingByRideIdAsync(Guid rideId);
|
||||
Task<List<RideCancelRequest>> GetAllAsync(CancelRequestStatus? status, int page, int pageSize);
|
||||
Task<int> GetCountAsync(CancelRequestStatus? status);
|
||||
Task AddAsync(RideCancelRequest request);
|
||||
void Update(RideCancelRequest request);
|
||||
}
|
||||
@@ -1,13 +1,22 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Ride;
|
||||
|
||||
public interface IRideRepository
|
||||
{
|
||||
Task<DeliveryRun?> GetByIdAsync(Guid id);
|
||||
Task<List<DeliveryRun>> GetAllAsync(RideStatus? status, Guid? driverProfileId, Guid? vehicleId, int page, int pageSize);
|
||||
Task<int> GetCountAsync(RideStatus? status, Guid? driverProfileId, Guid? vehicleId);
|
||||
Task<bool> HasOpenRideForVehicleAsync(Guid vehicleId, Guid? excludeRideId = null);
|
||||
Task<List<DeliveryRun>> GetAllAsync(RideStatus? status, RideType? type, Guid? driverProfileId, Guid? vehicleId, int page, int pageSize);
|
||||
Task<int> GetCountAsync(RideStatus? status, RideType? type, Guid? driverProfileId, Guid? vehicleId);
|
||||
Task<bool> HasOpenOrActiveRideForVehicleAsync(Guid vehicleId, Guid? excludeRideId = null);
|
||||
Task<List<DeliveryRun>> GetActiveRidesAsync(Guid? excludeRideId = null);
|
||||
Task<List<OrderSummaryModel>> GetActiveOrdersByRouteIdAsync(Guid routeId);
|
||||
Task AddAsync(DeliveryRun ride);
|
||||
void Update(DeliveryRun ride);
|
||||
}
|
||||
|
||||
public record OrderSummaryModel(
|
||||
Guid Id,
|
||||
Guid StoreId,
|
||||
string StoreName,
|
||||
DateTime OrderDate,
|
||||
decimal TotalAmount);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Ride;
|
||||
|
||||
public class RideCancelRequestRepository(AppDBContext context) : IRideCancelRequestRepository
|
||||
{
|
||||
public async Task<RideCancelRequest?> GetByIdAsync(Guid id) =>
|
||||
await context.RideCancelRequests.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
public async Task<RideCancelRequest?> GetByRideIdAsync(Guid rideId) =>
|
||||
await context.RideCancelRequests
|
||||
.Where(r => r.FK_RideId == rideId)
|
||||
.OrderByDescending(r => r.RequestedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public async Task<RideCancelRequest?> GetPendingByRideIdAsync(Guid rideId) =>
|
||||
await context.RideCancelRequests
|
||||
.FirstOrDefaultAsync(r => r.FK_RideId == rideId && r.Status == CancelRequestStatus.Pending);
|
||||
|
||||
public async Task<List<RideCancelRequest>> GetAllAsync(CancelRequestStatus? status, int page, int pageSize) =>
|
||||
await context.RideCancelRequests
|
||||
.Where(r => status == null || r.Status == status)
|
||||
.OrderByDescending(r => r.RequestedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<int> GetCountAsync(CancelRequestStatus? status) =>
|
||||
await context.RideCancelRequests
|
||||
.CountAsync(r => status == null || r.Status == status);
|
||||
|
||||
public async Task AddAsync(RideCancelRequest request) =>
|
||||
await context.RideCancelRequests.AddAsync(request);
|
||||
|
||||
public void Update(RideCancelRequest request) =>
|
||||
context.RideCancelRequests.Update(request);
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Ride;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/rides")]
|
||||
public class RideController(RideService service) : ControllerBase
|
||||
public class RideController(RideService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] RideStatus? status,
|
||||
[FromQuery] RideType? type,
|
||||
[FromQuery] Guid? driverProfileId,
|
||||
[FromQuery] Guid? vehicleId,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetAllAsync(status, driverProfileId, vehicleId, page, pageSize);
|
||||
var result = await service.GetAllAsync(status, type, driverProfileId, vehicleId, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
@@ -29,12 +31,8 @@ public class RideController(RideService service) : ControllerBase
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateRideRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/driver")]
|
||||
@@ -42,13 +40,39 @@ public class RideController(RideService service) : ControllerBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.AssignDriverAsync(id, request.DriverProfileId, ct);
|
||||
var result = await service.AssignDriverAsync(id, request.FK_DriverProfileId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/open")]
|
||||
public async Task<IActionResult> Open(Guid id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.OpenAsync(id, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/activate")]
|
||||
[HttpPost("{id:guid}/activate")]
|
||||
public async Task<IActionResult> Activate(Guid id, ActivateRideRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.ActivateAsync(id, request, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/close")]
|
||||
[HttpPost("{id:guid}/close")]
|
||||
public async Task<IActionResult> Close(Guid id, CloseRideRequest request, CancellationToken ct)
|
||||
{
|
||||
@@ -61,15 +85,70 @@ public class RideController(RideService service) : ControllerBase
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/cancel")]
|
||||
public async Task<IActionResult> Cancel(Guid id, CancelRideRequest request, CancellationToken ct)
|
||||
[HttpPost("{id:guid}/cancel-request")]
|
||||
public async Task<IActionResult> RaiseCancelRequest(Guid id, RaiseCancelRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.CancelAsync(id, request, ct);
|
||||
var result = await service.RaiseCancelRequestAsync(id, request, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("cancel-requests")]
|
||||
public async Task<IActionResult> GetCancelRequests(
|
||||
[FromQuery] CancelRequestStatus? status,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetCancelRequestsAsync(status, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("cancel-requests/{id:guid}/approve")]
|
||||
public async Task<IActionResult> ApproveCancelRequest(Guid id, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.ApproveCancelAsync(id, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("cancel-requests/{id:guid}/reject")]
|
||||
public async Task<IActionResult> RejectCancelRequest(Guid id, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
try
|
||||
{
|
||||
var result = await service.RejectCancelAsync(id, userId, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/cancel-request")]
|
||||
public async Task<IActionResult> GetCancelRequest(Guid id)
|
||||
{
|
||||
var result = await service.GetCancelRequestAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("breakdown-assist/{rideId:guid}")]
|
||||
public async Task<IActionResult> GetBreakdownAssist(Guid rideId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.GetBreakdownAssistAsync(rideId);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user