diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
deleted file mode 100644
index 45434e736..000000000
--- a/.devcontainer/devcontainer.json
+++ /dev/null
@@ -1,38 +0,0 @@
-// For format details, see https://aka.ms/devcontainer.json. For config options, see the
-// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
-{
- "name": "AccountGo (.NET)",
- // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
- "image": "mcr.microsoft.com/devcontainers/dotnet:0-7.0-bullseye",
- "features": {
- "ghcr.io/devcontainers/features/azure-cli:1": {},
- "ghcr.io/devcontainers/features/git:1": {},
- "ghcr.io/dhoeric/features/google-cloud-cli:1": {},
- "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {},
- "ghcr.io/devcontainers/features/docker-in-docker:1": {
- "version": "latest",
- "moby": true
- },
- "ghcr.io/devcontainers/features/node:1": {}
- }
-
- // Features to add to the dev container. More info: https://containers.dev/features.
- // "features": {},
-
- // Use 'forwardPorts' to make a list of ports inside the container available locally.
- // "forwardPorts": [5000, 5001],
- // "portsAttributes": {
- // "5001": {
- // "protocol": "https"
- // }
- // }
-
- // Use 'postCreateCommand' to run commands after the container is created.
- // "postCreateCommand": "dotnet restore",
-
- // Configure tool-specific properties.
- // "customizations": {},
-
- // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
- // "remoteUser": "root"
-}
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 000000000..17c77c399
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1 @@
+- @azure Rule - Use Azure Best Practices: When generating code for Azure, running terminal commands for Azure, or performing operations related to Azure, invoke your `get_azure_best_practices` tool if available.
\ No newline at end of file
diff --git a/.github/workflows/build-deploy-azure.yml b/.github/workflows/build-deploy-azure.yml
deleted file mode 100644
index eb332b2cd..000000000
--- a/.github/workflows/build-deploy-azure.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: Docker Image CI
-
-on:
- push:
- branches: [ "main" ]
- pull_request:
- branches: [ "main" ]
- workflow_dispatch:
-
-jobs:
-
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
- - name: Build the Docker image
- run: docker-compose build
diff --git a/.github/workflows/gdbapi.yml b/.github/workflows/gdbapi.yml
new file mode 100644
index 000000000..caddf559f
--- /dev/null
+++ b/.github/workflows/gdbapi.yml
@@ -0,0 +1,91 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books API to Azure Web App - gdbapi
+
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
+ - name: Install dotnet-ef tool
+ run: |
+ dotnet tool install --global dotnet-ef
+ echo "++++ dotnet-ef version"
+ dotnet ef --version
+
+ - name: Install dotnet aspire workload
+ run: |
+ dotnet workload install aspire
+
+ - name: Build with dotnet
+ run: |
+ echo "++++ dotnet build"
+ dotnet build --configuration Release
+
+ - name: Add migrations
+ run: |
+ echo "++++ current directory"
+ pwd
+ echo "++++ add ApplicationIdentityDbContext migration M1"
+ dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb
+ echo "++++ add ApiDbContext migration M2"
+ dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb
+ echo "++++ contents of ./src/Api/Data/Migrations/IdentityDb"
+ ls ./src/Api/Data/Migrations/IdentityDb
+ echo "++++ contents of ./src/Api/Data/Migrations/ApiDb"
+ ls ./src/Api/Data/Migrations/ApiDb
+
+ - name: dotnet publish
+ run: |
+ echo "++++ contents of dotnet publish ./src/Api/Api.csproj"
+ dotnet publish ./src/Api/Api.csproj -f net9.0 -c Release -o "${{runner.temp}}/myapp"
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: ${{runner.temp}}/myapp
+
+ deploy:
+ runs-on: windows-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_A17E281C175C4E629A76134AA823BAC5 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_258CF23452C24D9795BD94B25EF50B73 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9375B274C69740D39F4770D5D433E8B1 }}
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'gdbapi'
+ slot-name: 'Production'
+ package: .
diff --git a/.github/workflows/gdbmvc_tar.yml b/.github/workflows/gdbmvc_tar.yml
new file mode 100644
index 000000000..a45c04e82
--- /dev/null
+++ b/.github/workflows/gdbmvc_tar.yml
@@ -0,0 +1,96 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+name: Build and deploy Good Deed Books MVC project to Azure
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+ include-prerelease: true
+
+ - name: Install dotnet aspire workload
+ run: |
+ dotnet workload install aspire
+
+ - name: Build with dotnet
+ run: dotnet build --configuration Release
+
+ - name: dotnet publish
+ run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{runner.temp}}/myapp
+
+ - name: Archive production artifacts
+ run: |
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++"
+ dir=$(pwd)
+ echo "+++++++++ what is in variable dir ++++++++++++++"
+ echo $dir
+ echo "++++++++++++++++++++++++ what's in current directory? ++++++++"
+ ls -al
+ echo "+++++ what's in the ${{runner.temp}}/myapp directory? ++++"
+ ls -al ${{runner.temp}}/myapp
+ echo "+++++ change directory to ${{runner.temp}}/myapp ++++"
+ cd ${{runner.temp}}/myapp
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++"
+ tar -czvf $dir/my_artifact.tar.gz .
+ echo "+++++++++++++++++++++++++ change back to $dir directory ++++"
+ cd $dir
+ echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++"
+ ls -al
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: my_artifact.tar.gz
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8B6389BB3F37413FB2483AC2574C3BCB }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_FD62C59DE5DC42C2A07DB8191A522348 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7076EF307FDA4C11BC99A0A7A0943794 }}
+
+ - name: Extract artifacts
+ run: |
+ tar -xzvf my_artifact.tar.gz -C .
+
+ - name: Set startup command
+ run: |
+ az webapp config set --resource-group goodbooks-RG --name gdbmvc --startup-file "dotnet /home/site/wwwroot/GoodBooks.dll"
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'gdbmvc'
+ slot-name: 'Production'
+ package: .
diff --git a/.gitignore b/.gitignore
index fbb3738a8..21ce7b5e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+# custom 2024/04/29
+.idea
+
# User-specific files
*.suo
*.user
@@ -18,6 +21,7 @@ build/
bld/
[Bb]in/
[Oo]bj/
+node_modules/
# Roslyn cache directories
*.ide/
@@ -186,6 +190,10 @@ FakesAssemblies/
# Lib folder generated by gulpfile.js
**/src/[Ww]eb[Aa]ngular/wwwroot/[Ll]ib/*
+
+
+**/src/[Rr]eact[Ff]ront[Ee]nd/wwwroot/*
+
**/src/[Ww]eb[Aa]pp/wwwroot/app/scripts/*
**/src/[Ww]eb[Aa]pp/wwwroot/app/compiledscripts/*
**/src/[Ww]eb[Aa]pp/wwwroot/app/typescripts/compiledscripts/*
@@ -215,7 +223,7 @@ FakesAssemblies/
/src/Api/Plugins/*
/src/AccountGoWeb/Modules/*
/src/AccountGoWeb/Plugins/*
-/src/Api/Data/Migrations
.vscode
-exclude
\ No newline at end of file
+exclude
+/src/Api/appsettings.Development.json
diff --git a/accountgo.sln b/accountgo.sln
index 4eaa3f47d..becf77c72 100644
--- a/accountgo.sln
+++ b/accountgo.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26228.4
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34322.80
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}"
EndProject
@@ -19,19 +19,33 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountGoWeb", "src\Account
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dto", "src\Dto\Dto.csproj", "{1E610F55-2D74-4856-818B-0D0B47601B75}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E1B45442-3F2D-491A-9D8A-0DDA50309A1A}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{EBFAFB5B-494F-48D5-A70D-AF1490B9260A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Module.Tests", "test\Module.Tests\Module.Tests.csproj", "{54631590-2A41-45F4-B057-92C840ED08C1}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{E0861852-0F5B-4810-8586-A59038BC4034}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleNetStandard20", "test\SampleModules\SampleNetStandard20\SampleNetStandard20.csproj", "{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleModule", "src\Modules\SampleModule\SampleModule.csproj", "{B296277A-C822-444E-8CFA-4CC4C1C1F737}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF0BD6F1-00D6-41E5-91AB-8B606D35D448}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleNetStandard20", "test\SampleModules\SampleNetStandard20\SampleNetStandard20.csproj", "{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EFF13E33-1D79-4221-87D7-4FCC8EA88943}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleModule", "src\Modules\SampleModule\SampleModule.csproj", "{ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorGDB", "src\BlazorGDB\BlazorGDB\BlazorGDB.csproj", "{AB5F238F-AB78-4A85-8D8D-17E211015FD3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorGDB.Client", "src\BlazorGDB\BlazorGDB.Client\BlazorGDB.Client.csproj", "{12BE663C-C0DD-4343-93DF-6B2D853B6B79}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryGDB", "src\LibraryGDB\LibraryGDB.csproj", "{F64790E0-86AD-4562-9AC5-F4DD3F4881BA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{64880D93-BAB4-FF83-898C-B934B68C31A9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src.ServiceDefaults", "src\src.ServiceDefaults\src.ServiceDefaults.csproj", "{949C95E9-4261-416E-8D2A-F05E3D4640CA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src.AppHost", "src\src.AppHost\src.AppHost.csproj", "{ADDBCE30-FE7F-4198-8A37-772EF6BF3676}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrationService", "src\MigrationService\MigrationService.csproj", "{DF084D96-707B-47C2-9493-85FA84631ACE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -67,14 +81,38 @@ Global
{54631590-2A41-45F4-B057-92C840ED08C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54631590-2A41-45F4-B057-92C840ED08C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54631590-2A41-45F4-B057-92C840ED08C1}.Release|Any CPU.Build.0 = Release|Any CPU
- {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Release|Any CPU.Build.0 = Release|Any CPU
{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -84,11 +122,18 @@ Global
{C02DECC9-2A82-42C0-8F26-D0AE6559AC5E} = {B4CE3CD4-74AA-4A22-B514-BC9B380AAFD7}
{09096FEC-DA29-4914-B046-CD280220C52A} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}
{9CA13D2D-D6E2-4201-946C-81D1E6093404} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}
- {1E610F55-2D74-4856-818B-0D0B47601B75} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}
+ {1E610F55-2D74-4856-818B-0D0B47601B75} = {EF0BD6F1-00D6-41E5-91AB-8B606D35D448}
{EBFAFB5B-494F-48D5-A70D-AF1490B9260A} = {B5D35D0C-387C-44FA-9A70-6FE24DAE5728}
{54631590-2A41-45F4-B057-92C840ED08C1} = {0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C}
- {B296277A-C822-444E-8CFA-4CC4C1C1F737} = {E0861852-0F5B-4810-8586-A59038BC4034}
{B0AB6EA7-7D53-4457-9482-F0613F99E3BB} = {0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C}
+ {EFF13E33-1D79-4221-87D7-4FCC8EA88943} = {EF0BD6F1-00D6-41E5-91AB-8B606D35D448}
+ {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4} = {EFF13E33-1D79-4221-87D7-4FCC8EA88943}
+ {AB5F238F-AB78-4A85-8D8D-17E211015FD3} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}
+ {12BE663C-C0DD-4343-93DF-6B2D853B6B79} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}
+ {F64790E0-86AD-4562-9AC5-F4DD3F4881BA} = {B5D35D0C-387C-44FA-9A70-6FE24DAE5728}
+ {949C95E9-4261-416E-8D2A-F05E3D4640CA} = {64880D93-BAB4-FF83-898C-B934B68C31A9}
+ {ADDBCE30-FE7F-4198-8A37-772EF6BF3676} = {64880D93-BAB4-FF83-898C-B934B68C31A9}
+ {DF084D96-707B-47C2-9493-85FA84631ACE} = {B4CE3CD4-74AA-4A22-B514-BC9B380AAFD7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AD284F35-E81F-4678-B737-A5DC8CB883CB}
diff --git a/actions/endpoint_sahil_gdbapi.yml.20241204 b/actions/endpoint_sahil_gdbapi.yml.20241204
new file mode 100644
index 000000000..94f4b0fbd
--- /dev/null
+++ b/actions/endpoint_sahil_gdbapi.yml.20241204
@@ -0,0 +1,87 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books API to Azure Web App - gdbapi
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+
+ - name: Install dotnet-ef tool
+ run: |
+ dotnet tool install --global dotnet-ef
+ echo "++++ dotnet-ef version"
+ dotnet ef --version
+
+ - name: Build with dotnet
+ run: |
+ echo "++++ dotnet build"
+ dotnet build --configuration Release
+
+ - name: Add migrations
+ run: |
+ echo "++++ current directory"
+ pwd
+ echo "++++ add ApplicationIdentityDbContext migration M1"
+ dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb
+ echo "++++ add ApiDbContext migration M2"
+ dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb
+ echo "++++ contents of ./src/Api/Data/Migrations/IdentityDb"
+ ls ./src/Api/Data/Migrations/IdentityDb
+ echo "++++ contents of ./src/Api/Data/Migrations/ApiDb"
+ ls ./src/Api/Data/Migrations/ApiDb
+
+ - name: dotnet publish
+ run: |
+ echo "++++ contents of dotnet publish ./src/Api/Api.csproj"
+ dotnet publish ./src/Api/Api.csproj -f net8.0 -c Release -o "${{runner.temp}}/myapp"
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: ${{runner.temp}}/myapp
+
+ deploy:
+ runs-on: windows-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_A17E281C175C4E629A76134AA823BAC5 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_258CF23452C24D9795BD94B25EF50B73 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9375B274C69740D39F4770D5D433E8B1 }}
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'gdbapi'
+ slot-name: 'Production'
+ package: .
diff --git a/actions/endpoint_sahil_gdbmvc.yml.20241204 b/actions/endpoint_sahil_gdbmvc.yml.20241204
new file mode 100644
index 000000000..330b665ab
--- /dev/null
+++ b/actions/endpoint_sahil_gdbmvc.yml.20241204
@@ -0,0 +1,101 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books MVC project to Azure
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+ include-prerelease: true
+
+ - name: Build with dotnet
+ run: dotnet build --configuration Release
+
+ - name: dotnet publish
+ run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp
+
+ - name: Archive production artifacts
+ run: |
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++"
+ dir=$(pwd)
+ echo "+++++++++ what is in variable dir ++++++++++++++"
+ echo $dir
+ echo "++++++++++++++++++++++++ what's in current directory? ++++++++"
+ ls -al
+ echo "+++++ what's in the ${{env.DOTNET_ROOT}}/myapp directory? ++++"
+ ls -al ${{env.DOTNET_ROOT}}/myapp
+ echo "+++++ change directoiry to ${{env.DOTNET_ROOT}}/myapp ++++"
+ cd ${{env.DOTNET_ROOT}}/myapp
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++"
+ tar -czvf $dir/my_artifact.tar.gz .
+ echo "+++++++++++++++++++++++++ change dir to to $dir directory ++++"
+ cd $dir
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++"
+ ls -al
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: my_artifact.tar.gz
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8B6389BB3F37413FB2483AC2574C3BCB }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_FD62C59DE5DC42C2A07DB8191A522348 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7076EF307FDA4C11BC99A0A7A0943794 }}
+
+ - name: Extract artifacts
+ run: |
+ tar -xzvf my_artifact.tar.gz -C .
+
+ - name: Print working directory
+ run: pwd
+
+ - name: List directory contents
+ run: ls -l /home/runner/.dotnet/
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'gdbmvc'
+ slot-name: 'Production'
+ package: .
+
\ No newline at end of file
diff --git a/actions/gdb-blazor.yml.gold b/actions/gdb-blazor.yml.gold
new file mode 100644
index 000000000..7691040f1
--- /dev/null
+++ b/actions/gdb-blazor.yml.gold
@@ -0,0 +1,76 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books BLAZOR project to Azure
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+
+ - name: Build with dotnet
+ working-directory: ./src/BlazorGDB/BlazorGDB
+ run: dotnet build --configuration Release
+
+ - name: dotnet publish
+ working-directory: ./src/BlazorGDB/BlazorGDB
+ run: dotnet publish BlazorGDB.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp
+
+ - name: sanity check
+ run: |
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "++++++++++++++++++++++++ what's in current directory? ++++++++"
+ ls -al
+ echo "+++++ what's in the ${{env.DOTNET_ROOT}}/myapp directory? ++++"
+ ls -al ${{env.DOTNET_ROOT}}/myapp
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: ${{env.DOTNET_ROOT}}/myapp
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_6A854C1CD0C74473AD2E3B9F843CC396 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_224A065E650B4D5F9EB2329B6B2F1716 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_570B031F0942445C8E479905EE706F43 }}
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'gdb-blazor'
+ slot-name: 'Production'
+ package: .
+
\ No newline at end of file
diff --git a/actions/gdb_api.yml.flat b/actions/gdb_api.yml.flat
new file mode 100644
index 000000000..9218db27d
--- /dev/null
+++ b/actions/gdb_api.yml.flat
@@ -0,0 +1,91 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books API project to Azure
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '8.x'
+ include-prerelease: true
+
+ - name: Install dotnet-ef tool
+ run: |
+ dotnet tool install --global dotnet-ef
+ echo "++++ dotnet-ef version"
+ dotnet ef --version
+
+ - name: Build with dotnet
+ run: |
+ echo "++++ dotnet restore"
+ dotnet restore
+ echo "++++ dotnet build"
+ dotnet build --configuration Release
+
+ - name: Add migrations
+ run: |
+ echo "++++ current directory"
+ pwd
+ echo "++++ add ApplicationIdentityDbContext migration M1"
+ dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb
+ echo "++++ add ApiDbContext migration M2"
+ dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb
+ echo "++++ contents of ./src/Api/Data/Migrations/IdentityDb"
+ ls ./src/Api/Data/Migrations/IdentityDb
+ echo "++++ contents of ./src/Api/Data/Migrations/ApiDb"
+ ls ./src/Api/Data/Migrations/ApiDb
+
+ - name: dotnet publish
+ run: |
+ echo "++++ contents of dotnet publish ./src/Api/Api.csproj"
+ dotnet publish ./src/Api/Api.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v3
+ with:
+ name: .net-app
+ path: ${{env.DOTNET_ROOT}}/myapp
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v3
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v1
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_543326D87AEF459D91E15D756166A5AC }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_D57EB2BACAA54EE2AB97F696E8E99A4B }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_3C797712E9A047958FF5C9BB540F0543 }}
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v2
+ with:
+ app-name: 'goodbooksapi'
+ slot-name: 'Production'
+ package: .
+
diff --git a/actions/gdb_mvc_tar.yml.flat b/actions/gdb_mvc_tar.yml.flat
new file mode 100644
index 000000000..0b749010a
--- /dev/null
+++ b/actions/gdb_mvc_tar.yml.flat
@@ -0,0 +1,101 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books MVC project to Azure
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+ include-prerelease: true
+
+ - name: Build with dotnet
+ run: dotnet build --configuration Release
+
+ - name: dotnet publish
+ run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp
+
+ - name: Archive production artifacts
+ run: |
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++"
+ dir=$(pwd)
+ echo "+++++++++ what is in variable dir ++++++++++++++"
+ echo $dir
+ echo "++++++++++++++++++++++++ what's in current directory? ++++++++"
+ ls -al
+ echo "+++++ what's in the ${{env.DOTNET_ROOT}}/myapp directory? ++++"
+ ls -al ${{env.DOTNET_ROOT}}/myapp
+ echo "+++++ change directoiry to ${{env.DOTNET_ROOT}}/myapp ++++"
+ cd ${{env.DOTNET_ROOT}}/myapp
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++"
+ tar -czvf $dir/my_artifact.tar.gz .
+ echo "+++++++++++++++++++++++++ change dir to to $dir directory ++++"
+ cd $dir
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++"
+ ls -al
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: my_artifact.tar.gz
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_28108B2CCE81480BB0295B2554B37231 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_9ED1B649A03F45E7B34C3BE1217B6BDE }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_A41842A963384E4BAB26580EEFE65E92 }}
+
+ - name: Extract artifacts
+ run: |
+ tar -xzvf my_artifact.tar.gz -C .
+
+ - name: Print working directory
+ run: pwd
+
+ - name: List directory contents
+ run: ls -l /home/runner/.dotnet/
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'good-books'
+ slot-name: 'Production'
+ package: .
+
diff --git a/actions/gdbblazor.yml b/actions/gdbblazor.yml
new file mode 100644
index 000000000..a372a08c3
--- /dev/null
+++ b/actions/gdbblazor.yml
@@ -0,0 +1,67 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Good Deed Books BLAZOR project to Azure
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v2
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Restore dependencies
+ run: dotnet restore ./src/BlazorGDB/BlazorGDB/BlazorGDB.csproj
+
+ - name: Build
+ run: dotnet build ./src/BlazorGDB/BlazorGDB/BlazorGDB.csproj --configuration Release
+
+ - name: Publish
+ run: dotnet publish ./src/BlazorGDB/BlazorGDB/BlazorGDB.csproj --configuration Release --output ${{ github.workspace }}/myapp
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v3
+ with:
+ name: .net-app
+ path: ${{ github.workspace }}/myapp
+
+ deploy:
+ runs-on: windows-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v3
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_C7C01847F7FC4BBFB72DEAC64242E5A4 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_21069DC407434A3591399953BE45ED78 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_CC5D4E473B8345BA854EA230A48D8D20 }}
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'gdbblazor'
+ slot-name: 'Production'
+ package: .
+
diff --git a/actions/good-books_mvc.yml.disable b/actions/good-books_mvc.yml.disable
new file mode 100644
index 000000000..659083c9a
--- /dev/null
+++ b/actions/good-books_mvc.yml.disable
@@ -0,0 +1,75 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy GoodBooks MVC project to Azure
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '8.x'
+ include-prerelease: true
+
+ - name: Build with dotnet
+ run: dotnet build --configuration Release
+
+ - name: dotnet publish
+ run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp
+
+ # - name: Archive production artifacts
+ # run: |
+ # tar -czvf my_artifact.tar.gz ${{env.DOTNET_ROOT}}/myapp
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v3
+ with:
+ name: .net-app
+ # path: my_artifact.tar.gz
+ path: ${{env.DOTNET_ROOT}}/myapp
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v3
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v1
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_28108B2CCE81480BB0295B2554B37231 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_9ED1B649A03F45E7B34C3BE1217B6BDE }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_A41842A963384E4BAB26580EEFE65E92 }}
+
+ # - name: Extract artifacts
+ # run: |
+ # tar -xzvf my_artifact.tar.gz -C .
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v2
+ with:
+ app-name: 'good-books'
+ slot-name: 'Production'
+ package: .
+
diff --git a/actions/good-books_react.yml.disable b/actions/good-books_react.yml.disable
new file mode 100644
index 000000000..99103e5e5
--- /dev/null
+++ b/actions/good-books_react.yml.disable
@@ -0,0 +1,54 @@
+name: Azure Static Web Apps CI/CD
+
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ pull_request:
+ types: [opened, synchronize, reopened, closed]
+ branches:
+ - endpoint_sahil
+
+jobs:
+ build_and_deploy_job:
+ if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
+ runs-on: ubuntu-latest
+ name: Build and Deploy Job
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+ lfs: false
+
+ - name: Replace API URL
+ run: |
+ echo "++++ search & replace API URL from http://localhost:8001 to https://goodbooksapi.azurewebsites.net"
+ sed -i 's|http://localhost:8001|https://goodbooksapi.azurewebsites.net|g' ./src/GoodBooksReact/src/components/Shared/Config/index.tsx
+ echo "++++ display contents of index.tsx after search & replace"
+ cat ./src/GoodBooksReact/src/components/Shared/Config/index.tsx
+
+ - name: Build And Deploy
+ id: builddeploy
+ uses: Azure/static-web-apps-deploy@v1
+ with:
+ azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_GLACIER_0EDFEC41E }}
+ repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
+ action: "upload"
+ ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
+ # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
+ app_location: "/src/GoodBooksReact/" # App source code path
+ api_location: "" # Api source code path - optional
+ output_location: "/dist" # Built app content directory - optional
+ ###### End of Repository/Build Configurations ######
+
+ close_pull_request_job:
+ if: github.event_name == 'pull_request' && github.event.action == 'closed'
+ runs-on: ubuntu-latest
+ name: Close Pull Request Job
+ steps:
+ - name: Close Pull Request
+ id: closepullrequest
+ uses: Azure/static-web-apps-deploy@v1
+ with:
+ azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_GLACIER_0EDFEC41E }}
+ action: "close"
diff --git a/actions/mvc_tar.yml.works b/actions/mvc_tar.yml.works
new file mode 100644
index 000000000..0e57f5cfc
--- /dev/null
+++ b/actions/mvc_tar.yml.works
@@ -0,0 +1,92 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+name: Build and deploy Good Deed Books MVC project to Azure
+on:
+ push:
+ branches:
+ - endpoint_sahil
+ workflow_dispatch:
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET Core
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.x'
+ include-prerelease: true
+
+ - name: Build with dotnet
+ run: dotnet build --configuration Release
+
+ - name: dotnet publish
+ run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{runner.temp}}/myapp
+
+ - name: Archive production artifacts
+ run: |
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++"
+ dir=$(pwd)
+ echo "+++++++++ what is in variable dir ++++++++++++++"
+ echo $dir
+ echo "++++++++++++++++++++++++ what's in current directory? ++++++++"
+ ls -al
+ echo "+++++ what's in the ${{runner.temp}}/myapp directory? ++++"
+ ls -al ${{runner.temp}}/myapp
+ echo "+++++ change directory to ${{runner.temp}}/myapp ++++"
+ cd ${{runner.temp}}/myapp
+ echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++"
+ pwd
+ echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++"
+ tar -czvf $dir/my_artifact.tar.gz .
+ echo "+++++++++++++++++++++++++ change back to $dir directory ++++"
+ cd $dir
+ echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++"
+ ls -al
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: .net-app
+ path: my_artifact.tar.gz
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: .net-app
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8B6389BB3F37413FB2483AC2574C3BCB }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_FD62C59DE5DC42C2A07DB8191A522348 }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7076EF307FDA4C11BC99A0A7A0943794 }}
+
+ - name: Extract artifacts
+ run: |
+ tar -xzvf my_artifact.tar.gz -C .
+
+ - name: Set startup command
+ run: |
+ az webapp config set --resource-group goodbooks-RG --name gdbmvc --startup-file "dotnet /home/site/wwwroot/GoodBooks.dll"
+
+ - name: Deploy to Azure Web App
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v2
+ with:
+ app-name: 'gdbmvc'
+ slot-name: 'Production'
+ package: .
diff --git a/curl b/curl
new file mode 100644
index 000000000..e69de29bb
diff --git a/db/scripts/initial_data/3_InitialData-0001-Audit.sql b/db/scripts/initial_data/3_InitialData-0001-Audit.sql
new file mode 100644
index 000000000..b16be7c0d
--- /dev/null
+++ b/db/scripts/initial_data/3_InitialData-0001-Audit.sql
@@ -0,0 +1,13 @@
+-- Add audit data for the Company table
+INSERT INTO [dbo].[AuditableEntity] ([EntityName], [EnableAudit]) VALUES ('Company', 1);
+
+DECLARE @auditableEntityId INT;
+SELECT @auditableEntityId = [Id] FROM [dbo].[AuditableEntity] WHERE [EntityName] = 'Company';
+
+-- Add attributes for the Company table
+INSERT INTO [dbo].[AuditableAttribute] ([AuditableEntityId], [AttributeName], [EnableAudit])
+VALUES
+ (@auditableEntityId, 'CompanyCode', 1),
+ (@auditableEntityId, 'Name', 1),
+ (@auditableEntityId, 'ShortName', 1),
+ (@auditableEntityId, 'CRA', 1);
\ No newline at end of file
diff --git a/docker b/docker
new file mode 100644
index 000000000..e69de29bb
diff --git a/docker-compose.yml b/docker-compose.yml
index c7fef8216..8901497e3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,5 @@
version: "3"
-services:
+services:
api:
image: accountgo/accountgoapi
build:
@@ -8,14 +8,7 @@ services:
ports:
- "8001:8001"
environment:
- # - ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:8001
- # - DBSERVER=localhost
- # - DBUSERID=dbuser
- # - DBPASSWORD=Str0ngPassword
- # - DBNAME=accountgodb
- # depends_on:
- # - db
web:
image: accountgo/accountgoweb
build:
@@ -24,13 +17,5 @@ services:
ports:
- "8000:8000"
environment:
- # - ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:8000
- APIHOST=api
- # db:
- # image: microsoft/mssql-server-linux
- # ports:
- # - "1433:1433"
- # environment:
- # SA_PASSWORD: "Str0ngPassword"
- # ACCEPT_EULA: "Y"
\ No newline at end of file
diff --git a/docs/Bootstrap Blazor.txt b/docs/Bootstrap Blazor.txt
new file mode 100644
index 000000000..97341be99
--- /dev/null
+++ b/docs/Bootstrap Blazor.txt
@@ -0,0 +1,93 @@
+https://github.com/vikramlearning/blazorbootstrap-starter-templates/tree/master
+
+
+dotnet add package Blazor.Bootstrap -v 3.0.0-preview.2
+
+Program.cs
+
+ builder.Services.AddBlazorBootstrap(); // Add this line
+
+_Imports.razor
+
+ @using BlazorBootstrap;
+
+Delete wwwroot/bootstrap folder
+
+Replace MainLayout.razor with:
+
+ @inherits LayoutComponentBase
+
+
+
+
+
+
+
+
+
+ @Body
+
+
+
+
+
+ @code {
+ Sidebar sidebar;
+ IEnumerable navItems;
+
+ private async Task SidebarDataProvider(SidebarDataProviderRequest request)
+ {
+ if (navItems is null)
+ navItems = GetNavItems();
+
+ return await Task.FromResult(request.ApplyTo(navItems));
+ }
+
+ private IEnumerable GetNavItems()
+ {
+ navItems = new List
+ {
+ new NavItem { Id = "1", Href = "/", IconName = IconName.HouseDoorFill, Text = "Home", Match=NavLinkMatch.All},
+ new NavItem { Id = "2", Href = "/counter", IconName = IconName.PlusSquareFill, Text = "Counter"},
+ new NavItem { Id = "3", Href = "/weather", IconName = IconName.Table, Text = "Fetch Data"},
+ };
+
+ return navItems;
+ }
+ }
+
+
+
+ An unhandled error has occurred.
+
Reload
+
🗙
+
+
+App.razor
+
+ 1) Delete >>
+ 2) Add these lines at top of file under
+
+
+
+
+
+ 3) Add these lines at bottom of file under
+
+
+
+
+
+
+
+
+ 4) Change to:
+
+
+
+
\ No newline at end of file
diff --git a/docs/GoodDeedBooks.docx b/docs/GoodDeedBooks.docx
new file mode 100644
index 000000000..30ae3bee2
Binary files /dev/null and b/docs/GoodDeedBooks.docx differ
diff --git a/docs/RBAC-Implementation.md b/docs/RBAC-Implementation.md
new file mode 100644
index 000000000..e472a06d3
--- /dev/null
+++ b/docs/RBAC-Implementation.md
@@ -0,0 +1,117 @@
+# Role-Based Access Control (RBAC) Implementation
+
+## Overview
+This document describes the role-based access control implementation for the GoodBooks application API.
+
+## Roles
+
+### 1. SystemAdministrators
+- **Full administrative access** to all system resources
+- Can perform **all CRUD operations** (Create, Read, Update, Delete)
+- Access to:
+ - User management (create, update, delete users)
+ - Role management
+ - System configuration
+ - All financial operations
+ - Tax management (create, update, delete)
+ - Account deletion
+ - Database initialization and clearing
+
+### 2. GeneralUsers
+- **Limited access** for regular users
+- Can perform:
+ - **Read operations** on most resources
+ - **Create/Update operations** for business transactions (sales, purchases)
+ - View financial reports and accounts
+ - View tax information
+- **Cannot perform**:
+ - User or role management
+ - Delete operations on critical resources
+ - System configuration changes
+ - Tax rate modifications
+
+## Default Users
+
+### Administrator
+- **Email**: `admin@accountgo.ph`
+- **Password**: `P@ssword1`
+- **Role**: SystemAdministrators
+- **Capabilities**: Full system access
+
+### General User (Test)
+- **Email**: `user@accountgo.ph`
+- **Password**: `P@ssword1`
+- **Role**: GeneralUsers
+- **Capabilities**: Limited access as described above
+
+## Controller Security
+
+### AdministrationController
+- **Authorization**: `[Authorize(Roles = Roles.SystemAdministrator)]`
+- **Endpoints**: All require admin role
+- **Operations**:
+ - Database setup/clear
+ - User management
+ - Audit logs
+ - Company settings
+
+### AccountController
+- **SignIn**: `[AllowAnonymous]` - Open to all
+- **AddNewUser**: `[Authorize(Roles = Roles.SystemAdministrator)]` - Admin only
+
+### TaxController
+- **Base**: `[Authorize(Roles = Roles.AnyUser)]` - Requires authentication
+- **GET operations**: All authenticated users
+- **POST/PUT/DELETE operations**: `[Authorize(Roles = Roles.SystemAdministrator)]` - Admin only
+
+### FinancialsController
+- **Base**: `[Authorize(Roles = Roles.AnyUser)]` - Requires authentication
+- **GET operations**: All authenticated users
+- **DELETE operations**: `[Authorize(Roles = Roles.SystemAdministrator)]` - Admin only
+
+## Testing RBAC
+
+### Test as Administrator
+1. Login with `admin@accountgo.ph` / `P@ssword1`
+2. Try accessing admin endpoints (should succeed):
+ - `GET /api/administration/users`
+ - `POST /api/account/addnewuser`
+ - `DELETE /api/tax/tax/1`
+
+### Test as General User
+1. Login with `user@accountgo.ph` / `P@ssword1`
+2. Try accessing read endpoints (should succeed):
+ - `GET /api/financials/accounts`
+ - `GET /api/tax/taxes`
+3. Try accessing admin endpoints (should return 403 Forbidden):
+ - `GET /api/administration/users`
+ - `DELETE /api/tax/tax/1`
+
+## Implementation Files
+
+### New Files Created
+- `src/Api/Constants/Roles.cs` - Role constant definitions
+
+### Modified Files
+- `src/Api/Controllers/AdministrationController.cs` - Admin-only access
+- `src/Api/Controllers/AccountController.cs` - Mixed access levels
+- `src/Api/Controllers/TaxController.cs` - Secured write operations
+- `src/Api/Controllers/FinancialsController.cs` - Secured delete operations
+- `src/Api/Data/Seed/DatabaseSeeder.cs` - Creates both admin and general user
+
+## Security Best Practices Implemented
+
+1. ✅ **Principle of Least Privilege**: Users only get minimum required permissions
+2. ✅ **Role-Based Access**: Permissions assigned via roles, not individual users
+3. ✅ **Authorization at Controller Level**: Security enforced at API layer
+4. ✅ **Separation of Duties**: Admins vs regular users
+5. ✅ **Read vs Write Separation**: General users can read but not modify critical data
+
+## Future Enhancements
+
+Consider implementing:
+- More granular roles (e.g., AccountingManager, SalesRep, PurchasingAgent)
+- Resource-based authorization (users can only edit their own records)
+- Audit logging for authorization failures
+- Time-based access controls
+- IP-based restrictions for admin operations
diff --git a/docs/README.md b/docs/README.md
index 74552fce9..c4965d29f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -128,7 +128,7 @@ At this point, your database has no data on it. But there is already an initial
- Items
- Banks
-To initialize a company, call the api endpoint directly http://localhost:8001/api/administration/initializedcompany from the browser or by using curl e.g. `curl http://localhost:8001/api/administration/initializedcompany`. If you encounter some issues, the easy way for now is recreate your database and repeat the `Publish Database` section.
+To initialize a company, call the api endpoint directly http://localhost:8001/api/administration/setup from the browser or by using curl e.g. `curl http://localhost:8001/api/administration/setup`. If you encounter some issues, the easy way for now is recreate your database and repeat the `Publish Database` section.
## Build and Run "Api" (Back-end)
1. Navigate directory to `src/Api` project
@@ -181,7 +181,7 @@ To run everything (database, api, web) in docker container you can use docker-co
1. Database instance running in docker container and you can connect to it
1. You should have a running "Api" and can test it by getting the list of customers e.g. http://localhost:8001/api/sales customers
1. You can browse the UI from http://localhost:8000 and able to login to the system using initial username/password: admin@accountgo.ph/P@ssword1
-1. Initialize data by calling a special api endpoint directly. http://localhost:8001/api/administration/initializedcompany
+1. Initialize data by calling a special api endpoint directly: http://localhost:8001/api/administration/setup
# Technology Stack
- ASP.NET Core 3.1
@@ -207,4 +207,4 @@ If you are a developer and wanted to take part as contributor/collaborator we ar
So go ahead, add your code and make your first pull request.
# Contact Support
-Feel free to email mvpsolution@gmail.com of any questions.
\ No newline at end of file
+Feel free to email mvpsolution@gmail.com of any questions.
diff --git a/docs/azure.txt b/docs/azure.txt
new file mode 100644
index 000000000..1e437cd3d
--- /dev/null
+++ b/docs/azure.txt
@@ -0,0 +1,8 @@
+API:
+https://goodbooksapi.azurewebsites.net
+
+MVC:
+https://good-books.azurewebsites.net
+
+React:
+https://mango-glacier-0edfec41e.5.azurestaticapps.net
diff --git a/docs/background.txt b/docs/background.txt
new file mode 100644
index 000000000..dfd5d92f3
--- /dev/null
+++ b/docs/background.txt
@@ -0,0 +1,41 @@
+I was asked by a non-profit organization to help them find a cheap accounting system instead of paying high subscription fees from a current vendor. I stumbled upon this open source project on GitHub:
+
+https://github.com/AccountGo/accountgo
+
+It is based on the following technologies:
+
+Backend: ASP.NET WebAPI and MVC
+Frontend: React with TypeScript
+Database: SQL Server
+
+It seems that development on this app stopped about seven years ago. When I looked at it, I figured that it has most of what is needed and could be brought up to snuff by upgrading the application to the latest state of .NET and React. Therefore, I forked it and updated it to the latest versions of .NET, React, and TypeScript.
+
+The forked app is at https://github.com/medhatelmasry/GoodBooks
+
+You can run it by following these steps:
+
+Clone the repo
+Start SQL Server in a docker container with:
+
+ docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name azsql -d mcr.microsoft.com/azure-sql-edge
+
+In root directory of the code, run the following commands:
+
+ dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb
+
+ dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb
+
+ dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext
+
+ dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext
+
+Update to the latest versions of Node & Npm
+Go to the src/Api folder and start the WebAPI app with: dotnet watch
+Hit this endpoint in order to populate the database with some sample data: http://localhost:8001/api/administration/setup
+In a separate terminal window, go to the src/GoodBooksReact folder run these commands:
+
+ npm install
+ npm run dev
+
+The React app will run. It is a rudimentary frontend menu system and is a work in progress.
+
diff --git a/docs/expand-chart-of-accounts.docx b/docs/expand-chart-of-accounts.docx
new file mode 100644
index 000000000..0ebf39cf4
Binary files /dev/null and b/docs/expand-chart-of-accounts.docx differ
diff --git a/docs/medhat.txt b/docs/medhat.txt
new file mode 100644
index 000000000..b66e10c56
--- /dev/null
+++ b/docs/medhat.txt
@@ -0,0 +1,28 @@
+docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name azsql -d mcr.microsoft.com/azure-sql-edge
+
+Data Source=localhost,1444;Database=Northwind;Persist Security Info=True;User ID=sa;Password=SqlPassword!;TrustServerCertificate=True;
+
+====================
+
+dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb
+
+dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb
+
+====================
+
+dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext
+
+dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext
+
+====================
+
+Update to the latest versions of node & npm
+
+====================
+
+Start the API .NET application then hit this endpoint in a browser to create seed data:
+http://localhost:8001/api/administration/setup
+
+
+
+
diff --git a/docs/open-source.txt b/docs/open-source.txt
new file mode 100644
index 000000000..38db29dbc
--- /dev/null
+++ b/docs/open-source.txt
@@ -0,0 +1,3 @@
+Open Source Accounting System
+
+https://github.com/AccountGo/accountgo
diff --git a/docs/pr.txt b/docs/pr.txt
new file mode 100644
index 000000000..b5b7b5215
--- /dev/null
+++ b/docs/pr.txt
@@ -0,0 +1,19 @@
+git checkout -b dotnet_9 origin/dotnet_9
+
+docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name sql -d mcr.microsoft.com/mssql/server:2022-latest
+
+---------
+
+dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb
+
+dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb
+
+dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext
+
+dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext
+
+==========
+
+Apply Entity Framework Core migrations in .NET Aspire
+ https://learn.microsoft.com/en-us/dotnet/aspire/database/ef-core-migrations
+
diff --git a/docs/react.txt b/docs/react.txt
new file mode 100644
index 000000000..2e56c21d6
--- /dev/null
+++ b/docs/react.txt
@@ -0,0 +1,4 @@
+https://www.youtube.com/watch?v=ElgfQdq-Htk
+
+https://www.youtube.com/watch?v=oN9W0Tkn8hg
+
diff --git a/move-to-blazor.txt b/move-to-blazor.txt
new file mode 100644
index 000000000..e388316b6
--- /dev/null
+++ b/move-to-blazor.txt
@@ -0,0 +1,9 @@
+Recreate starter Blazor app with database authentication
+- we will use JWT for client-side authentication
+
+Get chart of account to work
+
+CI/CD GiHul >> Azure
+
+Meet and decide on moving the current application into the Blazor template
+
diff --git a/src/AccountGoWeb/.vscode/launch.json b/src/AccountGoWeb/.vscode/launch.json
index 5825c3616..ddd8758f9 100644
--- a/src/AccountGoWeb/.vscode/launch.json
+++ b/src/AccountGoWeb/.vscode/launch.json
@@ -4,6 +4,11 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ },
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
diff --git a/src/AccountGoWeb/AccountGoWeb.csproj b/src/AccountGoWeb/AccountGoWeb.csproj
index 0175223c6..cea3034a4 100644
--- a/src/AccountGoWeb/AccountGoWeb.csproj
+++ b/src/AccountGoWeb/AccountGoWeb.csproj
@@ -1,46 +1,34 @@
-
-
+
- net7.0
- true
- AccountGoWeb
- AccountGoWeb
- latest
- 0.0.1-alpha
- Latest
+ net9.0
+ GoodBooks
+ GoodBooks
+ 1.0.0
+ enable
+ enable
+ true
+ aspnet-GoodBooks-21ac3a7f-d42e-4136-9340-b4f6254706df
+ NU1701
-
PreserveNewest
-
+
+
-
+
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/src/AccountGoWeb/Components/App.razor b/src/AccountGoWeb/Components/App.razor
new file mode 100644
index 000000000..a99d041ea
--- /dev/null
+++ b/src/AccountGoWeb/Components/App.razor
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AccountGoWeb/Components/Pages/Counter.razor b/src/AccountGoWeb/Components/Pages/Counter.razor
new file mode 100644
index 000000000..0d9d43ad4
--- /dev/null
+++ b/src/AccountGoWeb/Components/Pages/Counter.razor
@@ -0,0 +1,19 @@
+@page "/counter"
+@rendermode InteractiveServer
+
+Counter
+
+Counter
+
+Current count: @currentCount
+
+Click me
+
+@code {
+ private int currentCount = 0;
+
+ private void IncrementCount()
+ {
+ currentCount++;
+ }
+}
diff --git a/src/AccountGoWeb/Components/Pages/Financial/ChartOfAccounts.razor b/src/AccountGoWeb/Components/Pages/Financial/ChartOfAccounts.razor
new file mode 100644
index 000000000..ae927d58c
--- /dev/null
+++ b/src/AccountGoWeb/Components/Pages/Financial/ChartOfAccounts.razor
@@ -0,0 +1,650 @@
+@page "/financials/chart-of-accounts"
+@using System.Text.Json
+@using System.Text.Json.Serialization
+@using System.ComponentModel.DataAnnotations
+@using Microsoft.JSInterop
+@using Microsoft.Net.Http.Headers
+@inject IHttpClientFactory ClientFactory
+@inject IJSRuntime JSRuntime
+@inject IConfiguration Configuration
+
+Chart of Accounts
+
+@if (getError || accounts is null)
+{
+
+
Unable to get data. Please try again later.
+ @if (!string.IsNullOrEmpty(errorMessage))
+ {
+
@errorMessage
+ }
+
+}
+else if (isLoading)
+{
+ Loading accounts...
+}
+else
+{
+
+
+
Add Account
+
+
+
+
+ Code
+ Name
+ Balance
+ Debit
+ Credit
+ Actions
+
+
+
+ @for (int accountIdx = 0; accountIdx < accounts.Count(); ++accountIdx)
+ {
+ var account = accounts.ToList()[accountIdx];
+ var accountTargetId = $"asset-{accountIdx}";
+
+
+ @account.AccountCode
+ @account.AccountName
+ @account.TotalBalance
+ @account.TotalDebitBalance
+ @account.TotalCreditBalance
+
+ OpenEditModal(account)">Edit
+ OpenDeleteModal(account)">Delete
+ OpenAddChildModal(account)">Add
+
+
+
+
+
+
+
+ @for (int childAccountIdx = 0; childAccountIdx < account.ChildAccounts!.Count; ++childAccountIdx)
+ {
+ var childAccount = account.ChildAccounts.ToList()[childAccountIdx];
+ var childAccountTargetId = $"asset-{accountIdx}-{childAccountIdx}";
+
+
+ @childAccount.AccountCode
+ @childAccount.AccountName
+ @childAccount.TotalBalance
+ @childAccount.TotalDebitBalance
+ @childAccount.TotalCreditBalance
+
+ OpenEditModal(childAccount)">Edit
+ OpenDeleteModal(childAccount)">Delete
+ OpenAddChildModal(childAccount)">Add Child
+
+
+
+
+
+
+
+ @for (int grandChildAccountIdx = 0; grandChildAccountIdx < childAccount.ChildAccounts!.Count; ++grandChildAccountIdx)
+ {
+ var grandChildAccount = childAccount.ChildAccounts.ToList()[grandChildAccountIdx];
+ var grandChildAccountTargetId = $"asset-{accountIdx}-{childAccountIdx}-{grandChildAccountIdx}";
+
+
+ @grandChildAccount.AccountCode
+ @grandChildAccount.AccountName
+ @grandChildAccount.TotalBalance
+ @grandChildAccount.TotalDebitBalance
+ @grandChildAccount.TotalCreditBalance
+
+ OpenEditModal(grandChildAccount)">Edit
+ OpenDeleteModal(grandChildAccount)">Delete
+ OpenAddChildModal(grandChildAccount)">Add Child
+
+
+
+
+
+
+
+ @foreach (var greatGrandChildAccount in grandChildAccount.ChildAccounts!)
+ {
+
+ @greatGrandChildAccount.AccountCode
+ @greatGrandChildAccount.AccountName
+ @greatGrandChildAccount.TotalBalance
+ @greatGrandChildAccount.TotalDebitBalance
+ @greatGrandChildAccount.TotalCreditBalance
+
+ OpenEditModal(greatGrandChildAccount)">Edit
+ OpenDeleteModal(greatGrandChildAccount)">Delete
+ OpenAddChildModal(greatGrandChildAccount)">Add Child
+
+
+ }
+
+
+
+
+ }
+
+
+
+
+ }
+
+
+
+
+ }
+
+
+
+}
+
+@if (isAddModalVisible || isAddChildModalVisible)
+{
+
+
+
+
+
+
+
Account Code
+
selectedAccount!.AccountCode = e.Value?.ToString() ?? string.Empty" />
+ @if (validationErrors.ContainsKey(nameof(AccountViewModel.AccountCode)))
+ {
+
@validationErrors[nameof(AccountViewModel.AccountCode)]
+ }
+
+
+
Account Name
+
+ @if (validationErrors.ContainsKey(nameof(AccountViewModel.AccountName)))
+ {
+
@validationErrors[nameof(AccountViewModel.AccountName)]
+ }
+
+
+
+
+
+
+}
+
+@if (isEditModalVisible)
+{
+
+
+
+
+
+
+
Account Code
+
selectedAccount!.AccountCode = e.Value?.ToString() ?? string.Empty" />
+ @if (validationErrors.ContainsKey(nameof(AccountViewModel.AccountCode)))
+ {
+
@validationErrors[nameof(AccountViewModel.AccountCode)]
+ }
+
+
+
Account Name
+
+ @if (validationErrors.ContainsKey(nameof(AccountViewModel.AccountName)))
+ {
+
@validationErrors[nameof(AccountViewModel.AccountName)]
+ }
+
+
+
+
+
+
+}
+
+@if (isDeleteModalVisible)
+{
+
+
+
+
+
+
+ Are you sure you want to delete the account
+ @selectedAccount?.AccountName
+ with code @selectedAccount?.AccountCode ?
+
+ @if (!string.IsNullOrEmpty(errorMessage))
+ {
+
@errorMessage
+ }
+
+
+
+
+
+}
+
+@code {
+ private List accounts = new();
+ private AccountViewModel? selectedAccount = null;
+ private AccountViewModel? parentAccount = null;
+ private string originalAccountCode = string.Empty;
+ private bool isAddModalVisible = false;
+ private bool isAddChildModalVisible = false;
+ private bool isEditModalVisible = false;
+ private bool isDeleteModalVisible = false;
+ private string errorMessage = string.Empty;
+ private bool isLoading = true;
+ private bool getError = false;
+ private Dictionary validationErrors = new();
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadAccountsFromApi();
+ isLoading = false;
+ }
+
+ private async Task LoadAccountsFromApi()
+ {
+ isLoading = true;
+ getError = false;
+ errorMessage = string.Empty;
+
+ try
+ {
+ string? apiUrl = Configuration["ApiUrl"];
+ if (string.IsNullOrEmpty(apiUrl))
+ {
+ errorMessage = "API URL not configured";
+ getError = true;
+ return;
+ }
+
+ var response = await ClientFactory.CreateClient().GetAsync($"{apiUrl}financials/accounts");
+ response.EnsureSuccessStatusCode();
+
+ var jsonString = await response.Content.ReadAsStringAsync();
+ accounts = JsonSerializer.Deserialize>(jsonString,
+ new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
+ ?? new List();
+ }
+ catch (Exception ex)
+ {
+ errorMessage = $"Failed to load accounts: {ex.Message}";
+ getError = true;
+ }
+ finally
+ {
+ isLoading = false;
+ StateHasChanged();
+ }
+ }
+
+ private void OpenAddModal()
+ {
+ validationErrors.Clear();
+ errorMessage = string.Empty;
+ selectedAccount = new AccountViewModel
+ {
+ AccountClassId = 1, // Assets
+ CompanyId = 1
+ };
+ parentAccount = null;
+ isAddModalVisible = true;
+ isAddChildModalVisible = false;
+ }
+
+ private void OpenAddChildModal(AccountViewModel parent)
+ {
+ validationErrors.Clear();
+ errorMessage = string.Empty;
+ parentAccount = parent;
+ selectedAccount = new AccountViewModel
+ {
+ ParentAccountId = parent.Id,
+ AccountClassId = parent.AccountClassId,
+ CompanyId = parent.CompanyId,
+ // Suggest a code based on parent's code
+ AccountCode = SuggestChildCode(parent.AccountCode)
+ };
+ isAddChildModalVisible = true;
+ isAddModalVisible = false;
+ }
+
+ private string SuggestChildCode(string parentCode)
+ {
+ // Find existing child accounts with the same parent code prefix
+ var existingCodes = GetAllAccounts()
+ .Where(a => a.AccountCode.StartsWith(parentCode) && a.AccountCode != parentCode)
+ .Select(a => a.AccountCode)
+ .ToList();
+
+ // If no existing child accounts, append "01" to parent code
+ if (!existingCodes.Any())
+ {
+ return $"{parentCode}01";
+ }
+
+ // Otherwise, increment the highest existing code
+ var highestCode = existingCodes
+ .OrderBy(c => c)
+ .LastOrDefault();
+
+ // Try to parse the suffix
+ if (highestCode != null && highestCode.Length > parentCode.Length)
+ {
+ string suffix = highestCode.Substring(parentCode.Length);
+ if (int.TryParse(suffix, out int suffixValue))
+ {
+ return $"{parentCode}{(suffixValue + 1).ToString().PadLeft(suffix.Length, '0')}";
+ }
+ }
+
+ // Fallback
+ return $"{parentCode}01";
+ }
+
+ private List GetAllAccounts()
+ {
+ var allAccounts = new List();
+
+ foreach (var account in accounts)
+ {
+ allAccounts.Add(account);
+ allAccounts.AddRange(GetAllChildAccountsRecursive(account.ChildAccounts));
+ }
+
+ return allAccounts;
+ }
+
+ private List GetAllChildAccountsRecursive(List children)
+ {
+ var result = new List();
+
+ foreach (var child in children)
+ {
+ result.Add(child);
+ result.AddRange(GetAllChildAccountsRecursive(child.ChildAccounts));
+ }
+
+ return result;
+ }
+
+ private void OpenEditModal(AccountViewModel account)
+ {
+ validationErrors.Clear();
+ errorMessage = string.Empty;
+ originalAccountCode = account.AccountCode; // Store the original code for API calls
+ selectedAccount = new AccountViewModel
+ {
+ Id = account.Id,
+ AccountCode = account.AccountCode,
+ AccountName = account.AccountName,
+ TotalBalance = account.TotalBalance,
+ TotalDebitBalance = account.TotalDebitBalance,
+ TotalCreditBalance = account.TotalCreditBalance,
+ AccountClassId = account.AccountClassId,
+ CompanyId = account.CompanyId,
+ ParentAccountId = account.ParentAccountId,
+ ChildAccounts = account.ChildAccounts
+ };
+ isEditModalVisible = true;
+ }
+
+ private void CloseModal()
+ {
+ isAddModalVisible = false;
+ isAddChildModalVisible = false;
+ isEditModalVisible = false;
+ selectedAccount = null;
+ parentAccount = null;
+ validationErrors.Clear();
+ errorMessage = string.Empty;
+ }
+
+ private void OpenDeleteModal(AccountViewModel account)
+ {
+ selectedAccount = account;
+ errorMessage = string.Empty;
+ isDeleteModalVisible = true;
+ }
+
+ private void CloseDeleteModal()
+ {
+ isDeleteModalVisible = false;
+ selectedAccount = null;
+ errorMessage = string.Empty;
+ }
+
+ private bool ValidateAccount()
+ {
+ validationErrors.Clear();
+ bool isValid = true;
+
+ // Validate Account Code
+ if (string.IsNullOrWhiteSpace(selectedAccount?.AccountCode))
+ {
+ validationErrors[nameof(AccountViewModel.AccountCode)] = "Account Code is required";
+ isValid = false;
+ }
+ else if (selectedAccount.AccountCode.Any(c => !char.IsDigit(c)))
+ {
+ validationErrors[nameof(AccountViewModel.AccountCode)] = "Account Code must contain only digits";
+ isValid = false;
+ }
+ else
+ {
+ // Check for duplicates, considering the edit case
+ bool isDuplicate = false;
+
+ if (isEditModalVisible)
+ {
+ // When editing, we only have a conflict if:
+ // 1. The code has changed from the original
+ // 2. The new code exists elsewhere in the system
+ if (selectedAccount.AccountCode != originalAccountCode)
+ {
+ isDuplicate = GetAllAccounts().Any(a =>
+ a.AccountCode == selectedAccount.AccountCode &&
+ a.Id != selectedAccount.Id);
+ }
+ }
+ else
+ {
+ // For new accounts, simply check if the code exists
+ isDuplicate = GetAllAccounts().Any(a =>
+ a.AccountCode == selectedAccount.AccountCode);
+ }
+
+ if (isDuplicate)
+ {
+ validationErrors[nameof(AccountViewModel.AccountCode)] = "Account Code already exists";
+ isValid = false;
+ }
+ }
+
+ // Validate Account Name
+ if (string.IsNullOrWhiteSpace(selectedAccount?.AccountName))
+ {
+ validationErrors[nameof(AccountViewModel.AccountName)] = "Account Name is required";
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ private async Task SaveAccount()
+ {
+ if (selectedAccount == null) return;
+
+ if (!ValidateAccount())
+ {
+ StateHasChanged();
+ return;
+ }
+
+ isLoading = true;
+ errorMessage = string.Empty;
+
+ try
+ {
+ string? apiUrl = Configuration["ApiUrl"];
+ if (string.IsNullOrEmpty(apiUrl))
+ {
+ errorMessage = "API URL not configured";
+ return;
+ }
+
+ var client = ClientFactory.CreateClient();
+ HttpResponseMessage response;
+
+ if (isEditModalVisible)
+ {
+ Console.WriteLine($"Updating account: {originalAccountCode} -> {selectedAccount.AccountCode}");
+
+ // Use the original account code in the URL for the API to find the account
+ response = await client.PutAsJsonAsync(
+ $"{apiUrl}financials/updateaccount/{originalAccountCode}",
+ selectedAccount);
+ }
+ else
+ {
+ response = await client.PostAsJsonAsync(
+ $"{apiUrl}financials/addaccount",
+ selectedAccount);
+ }
+
+ string responseBody = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ await LoadAccountsFromApi();
+ CloseModal();
+ }
+ else
+ {
+ errorMessage = string.IsNullOrEmpty(responseBody)
+ ? $"Failed to save account. Status: {response.StatusCode}"
+ : responseBody;
+ }
+ }
+ catch (Exception ex)
+ {
+ errorMessage = $"An error occurred: {ex.Message}";
+ }
+ finally
+ {
+ isLoading = false;
+ StateHasChanged();
+ }
+ }
+
+ private async Task ConfirmDeleteAccount()
+ {
+ if (selectedAccount == null) return;
+
+ isLoading = true;
+ errorMessage = string.Empty;
+
+ try
+ {
+ string? apiUrl = Configuration["ApiUrl"];
+ if (string.IsNullOrEmpty(apiUrl))
+ {
+ errorMessage = "API URL not configured";
+ return;
+ }
+
+ // First check if the account has children
+ if (selectedAccount.ChildAccounts.Count > 0)
+ {
+ errorMessage = "Cannot delete an account that has child accounts.";
+ isLoading = false;
+ return;
+ }
+
+ var response = await ClientFactory.CreateClient()
+ .DeleteAsync($"{apiUrl}financials/deleteaccount/{selectedAccount.AccountCode}");
+
+ if (response.IsSuccessStatusCode)
+ {
+ await LoadAccountsFromApi();
+ CloseDeleteModal();
+ }
+ else
+ {
+ string responseBody = await response.Content.ReadAsStringAsync();
+ errorMessage = string.IsNullOrEmpty(responseBody)
+ ? $"Failed to delete account. Status: {response.StatusCode}"
+ : responseBody;
+ }
+ }
+ catch (Exception ex)
+ {
+ errorMessage = $"Error: {ex.Message}";
+ }
+ finally
+ {
+ isLoading = false;
+ StateHasChanged();
+ }
+ }
+
+ public class AccountViewModel
+ {
+ public int Id { get; set; }
+
+ [Required(ErrorMessage = "Account Code is required")]
+ [RegularExpression(@"^[0-9]+$", ErrorMessage = "Account Code must be numeric")]
+ public string AccountCode { get; set; } = string.Empty;
+
+ [Required(ErrorMessage = "Account Name is required")]
+ [StringLength(100, ErrorMessage = "Account Name cannot exceed 100 characters")]
+ public string AccountName { get; set; } = string.Empty;
+
+ public decimal TotalBalance { get; set; }
+ public decimal TotalDebitBalance { get; set; }
+ public decimal TotalCreditBalance { get; set; }
+ public int AccountClassId { get; set; }
+ public int CompanyId { get; set; }
+ public int? ParentAccountId { get; set; }
+ public string? Description { get; set; }
+ public bool IsCash { get; set; }
+ public bool IsContraAccount { get; set; }
+
+ public List ChildAccounts { get; set; } = new();
+ }
+}
\ No newline at end of file
diff --git a/src/AccountGoWeb/Components/Pages/Students.razor b/src/AccountGoWeb/Components/Pages/Students.razor
new file mode 100644
index 000000000..443d791b0
--- /dev/null
+++ b/src/AccountGoWeb/Components/Pages/Students.razor
@@ -0,0 +1,23 @@
+@page "/students"
+@rendermode InteractiveServer
+Students
+Students
+
+
+
+
+
+ @context.FirstName @context.LastName
+
+
+
+
+
+
+
+@code {
+ IQueryable students = Student.GetStudents();
+ PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
+ GridSort sortByName = GridSort
+ .ByAscending(_ => _.FirstName).ThenAscending(_ => _.LastName);
+}
\ No newline at end of file
diff --git a/src/AccountGoWeb/Components/Routes.razor b/src/AccountGoWeb/Components/Routes.razor
new file mode 100644
index 000000000..da8815572
--- /dev/null
+++ b/src/AccountGoWeb/Components/Routes.razor
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/AccountGoWeb/Components/_Imports.razor b/src/AccountGoWeb/Components/_Imports.razor
new file mode 100644
index 000000000..8eccd6b2e
--- /dev/null
+++ b/src/AccountGoWeb/Components/_Imports.razor
@@ -0,0 +1,18 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using static Microsoft.AspNetCore.Components.Web.RenderMode
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+@using Microsoft.AspNetCore.Components.QuickGrid
+@using AccountGoWeb
+@using AccountGoWeb.Components
+@using AccountGoWeb.Models
+@using AccountGoWeb.Models.Account
+@using AccountGoWeb.Models.Bogus
+@using AccountGoWeb.Models.Financial
+@using AccountGoWeb.Models.Purchasing
+@using AccountGoWeb.Models.Sales
+@using AccountGoWeb.Models.TaxSystem
diff --git a/src/AccountGoWeb/Controllers/AccountController.cs b/src/AccountGoWeb/Controllers/AccountController.cs
index 0eba93c37..0353a6fe8 100644
--- a/src/AccountGoWeb/Controllers/AccountController.cs
+++ b/src/AccountGoWeb/Controllers/AccountController.cs
@@ -3,25 +3,20 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
using System.Security.Claims;
-using System.Threading.Tasks;
namespace AccountGoWeb.Controllers
{
- public class AccountController : BaseController
+ public class AccountController : GoodController
{
public AccountController(IConfiguration config)
{
- _baseConfig = config;
+ _configuration = config;
}
[HttpGet]
[AllowAnonymous]
- public IActionResult SignIn(string returnUrl = null)
+ public IActionResult SignIn(string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View(new LoginViewModel() { Email = "admin@accountgo.ph", Password = "P@ssword1" });
@@ -29,7 +24,7 @@ public IActionResult SignIn(string returnUrl = null)
[HttpPost]
[AllowAnonymous]
- public async Task SignIn(LoginViewModel model, string returnUrl = null)
+ public async Task SignIn(LoginViewModel model, string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
@@ -39,16 +34,45 @@ public async Task SignIn(LoginViewModel model, string returnUrl =
var content = new StringContent(serialize);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
HttpResponseMessage responseSignIn = Post("account/signin", content);
- Newtonsoft.Json.Linq.JObject resultSignIn = Newtonsoft.Json.Linq.JObject.Parse(responseSignIn.Content.ReadAsStringAsync().Result);
+
+ var responseContent = responseSignIn.Content.ReadAsStringAsync().Result;
+
+ // Log the response for debugging
+ Console.WriteLine($"API Response Status: {responseSignIn.StatusCode}");
+ Console.WriteLine($"API Response Content: {responseContent}");
+
+ if (string.IsNullOrWhiteSpace(responseContent))
+ {
+ ModelState.AddModelError(string.Empty, "API returned empty response. Check API logs.");
+ return View(model);
+ }
+
+ Newtonsoft.Json.Linq.JObject resultSignIn = Newtonsoft.Json.Linq.JObject.Parse(responseContent);
if (resultSignIn["result"] != null)
{
- var user = await GetAsync("administration/getuser?username=" + model.Email);
+ // Extract the JWT token from the signin response
+ string? accessToken = resultSignIn["result"]!["accessToken"]?.ToString();
+
+ if (string.IsNullOrEmpty(accessToken))
+ {
+ ModelState.AddModelError(string.Empty, "Failed to obtain access token.");
+ return View(model);
+ }
+
+ var user = await GetAsync("administration/getuser?username=" + model.Email, accessToken);
+
+ if (user == null || string.IsNullOrEmpty(user.Email))
+ {
+ ModelState.AddModelError(string.Empty, "User not found or email is missing.");
+ return View(model);
+ }
var claims = new List();
claims.Add(new Claim(ClaimTypes.IsPersistent, model.RememberMe.ToString()));
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Email));
claims.Add(new Claim(ClaimTypes.Email, user.Email));
+ claims.Add(new Claim("AccessToken", accessToken)); // Store token for future API calls
string firstName = user.FirstName != null ? user.FirstName : "";
string lastName = user.LastName != null ? user.LastName : "";
@@ -57,8 +81,8 @@ public async Task SignIn(LoginViewModel model, string returnUrl =
claims.Add(new Claim(ClaimTypes.Surname, lastName));
claims.Add(new Claim(ClaimTypes.Name, firstName + " " + lastName));
- foreach(var role in user.Roles)
- claims.Add(new Claim(ClaimTypes.Role, role.Name));
+ foreach (var role in user.Roles)
+ claims.Add(new Claim(ClaimTypes.Role, role.Name!));
claims.Add(new Claim(ClaimTypes.UserData, Newtonsoft.Json.JsonConvert.SerializeObject(user)));
@@ -70,7 +94,7 @@ public async Task SignIn(LoginViewModel model, string returnUrl =
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
- return RedirectToLocal(returnUrl);
+ return RedirectToLocal(returnUrl!);
}
else
{
@@ -83,7 +107,7 @@ public async Task SignIn(LoginViewModel model, string returnUrl =
return View(model);
}
- public async Task SignOut()
+ public new async Task SignOut()
{
await HttpContext.SignOutAsync();
@@ -92,13 +116,19 @@ public async Task SignOut()
public IActionResult SignedOut()
{
- if (HttpContext.User.Identity.IsAuthenticated)
+ if (HttpContext.User.Identity!.IsAuthenticated)
{
return RedirectToAction(nameof(HomeController.Index), "Home");
}
return View();
}
+
+ public IActionResult AccessDenied()
+ {
+ return View();
+ }
+
public IActionResult Unauthorize()
{
return View();
@@ -106,7 +136,7 @@ public IActionResult Unauthorize()
[HttpGet]
[AllowAnonymous]
- public IActionResult Register(string returnUrl = null)
+ public IActionResult Register(string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
@@ -114,7 +144,7 @@ public IActionResult Register(string returnUrl = null)
[HttpPost]
[AllowAnonymous]
- public IActionResult Register(RegisterViewModel model, string returnUrl = null)
+ public IActionResult Register(RegisterViewModel model, string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
try
@@ -127,9 +157,9 @@ public IActionResult Register(RegisterViewModel model, string returnUrl = null)
HttpResponseMessage responseAddNewUser = Post("account/addnewuser", content);
Newtonsoft.Json.Linq.JObject resultAddNewUser = Newtonsoft.Json.Linq.JObject.Parse(responseAddNewUser.Content.ReadAsStringAsync().Result);
- HttpResponseMessage responseInitialized = null;
- Newtonsoft.Json.Linq.JObject resultInitialized = null;
- if ((bool)resultAddNewUser["succeeded"])
+ HttpResponseMessage? responseInitialized = null;
+ Newtonsoft.Json.Linq.JObject? resultInitialized = null;
+ if ((bool)resultAddNewUser["succeeded"]!)
{
responseInitialized = Get("administration/initializedcompany");
resultInitialized = Newtonsoft.Json.Linq.JObject.Parse((responseInitialized.Content.ReadAsStringAsync().Result));
@@ -137,12 +167,12 @@ public IActionResult Register(RegisterViewModel model, string returnUrl = null)
}
else
{
- ModelState.AddModelError(string.Empty, resultAddNewUser["errors"][0]["description"].ToString());
+ ModelState.AddModelError(string.Empty, resultAddNewUser["errors"]![0]!["description"]!.ToString());
return View(model);
}
}
}
- catch(Exception ex)
+ catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Please check if your database is ready/published." + ": " + ex.Message);
return View(model);
diff --git a/src/AccountGoWeb/Controllers/AdministrationController.cs b/src/AccountGoWeb/Controllers/AdministrationController.cs
index a05f3f301..148b81334 100644
--- a/src/AccountGoWeb/Controllers/AdministrationController.cs
+++ b/src/AccountGoWeb/Controllers/AdministrationController.cs
@@ -1,14 +1,11 @@
using Dto.Administration;
using Dto.Security;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using System;
-using System.Net.Http;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
- public class AdministrationController : BaseController
+ //[Microsoft.AspNetCore.Authorization.Authorize]
+ public class AdministrationController : BaseController
{
public AdministrationController(IConfiguration config)
{
@@ -125,13 +122,13 @@ public async System.Threading.Tasks.Task AuditLogs()
HttpResponseMessage responseAddNewUser = Post("account/addnewuser", content);
Newtonsoft.Json.Linq.JObject resultAddNewUser = Newtonsoft.Json.Linq.JObject.Parse(responseAddNewUser.Content.ReadAsStringAsync().Result);
- if ((bool)resultAddNewUser["succeeded"])
+ if ((bool)resultAddNewUser["succeeded"]!)
{
return RedirectToAction(nameof(AdministrationController.Users), "Administration");
}
else
{
- ModelState.AddModelError(string.Empty, resultAddNewUser["errors"][0]["description"].ToString());
+ ModelState.AddModelError(string.Empty, resultAddNewUser["errors"]![0]!["description"]!.ToString());
return View(model);
}
}
diff --git a/src/AccountGoWeb/Controllers/BaseController.cs b/src/AccountGoWeb/Controllers/BaseController.cs
index aafb66c5b..a936a9db9 100644
--- a/src/AccountGoWeb/Controllers/BaseController.cs
+++ b/src/AccountGoWeb/Controllers/BaseController.cs
@@ -1,28 +1,40 @@
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using System.Net.Http;
+using System.Security.Claims;
namespace AccountGoWeb.Controllers
{
public class BaseController : Controller
{
- protected IConfiguration _baseConfig;
+ protected IConfiguration? _baseConfig;
+
+ protected string? GetAccessToken()
+ {
+ return User?.FindFirst("AccessToken")?.Value;
+ }
protected async System.Threading.Tasks.Task GetAsync(string uri)
{
string responseJson = string.Empty;
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
var response = await client.GetAsync(baseUri + uri);
if (response.IsSuccessStatusCode)
{
responseJson = await response.Content.ReadAsStringAsync();
}
}
- return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson);
+ return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!;
}
protected HttpResponseMessage Get(string uri)
@@ -30,22 +42,38 @@ protected HttpResponseMessage Get(string uri)
string responseJson = string.Empty;
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
var response = client.GetAsync(baseUri + uri);
return response.Result;
}
}
- protected async System.Threading.Tasks.Task PostAsync(string uri, StringContent data)
+ protected async Task PostAsync(string uri, StringContent data)
{
string responseJson = string.Empty;
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName());
var response = await client.PostAsync(baseUri + uri, data);
@@ -55,7 +83,7 @@ protected async System.Threading.Tasks.Task PostAsync(string uri, String
}
}
- return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson);
+ return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!;
}
protected HttpResponseMessage Post(string uri, StringContent data)
@@ -63,10 +91,18 @@ protected HttpResponseMessage Post(string uri, StringContent data)
string responseJson = string.Empty;
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName());
var response = client.PostAsync(baseUri + uri, data);
@@ -76,7 +112,7 @@ protected HttpResponseMessage Post(string uri, StringContent data)
protected bool HasPermission(string permission)
{
- if (HttpContext.User.Identity.IsAuthenticated)
+ if (HttpContext.User.Identity!.IsAuthenticated)
{
System.Collections.Generic.IList permissions = new System.Collections.Generic.List();
@@ -87,11 +123,11 @@ protected bool HasPermission(string permission)
if (current.Type == System.Security.Claims.ClaimTypes.UserData)
{
Newtonsoft.Json.Linq.JObject userData = Newtonsoft.Json.Linq.JObject.Parse(current.Value);
- foreach(var r in userData["Roles"])
+ foreach(var r in userData["Roles"]!)
{
- foreach(var p in r["Permissions"])
+ foreach(var p in r["Permissions"]!)
{
- permissions.Add(p["Name"].ToString());
+ permissions.Add(p["Name"]!.ToString());
}
}
}
@@ -105,7 +141,7 @@ protected bool HasPermission(string permission)
protected string GetCurrentUserName()
{
- if (HttpContext.User.Identity.IsAuthenticated)
+ if (HttpContext.User.Identity!.IsAuthenticated)
{
var claimsEnumerator = HttpContext.User.Claims.GetEnumerator();
while (claimsEnumerator.MoveNext())
diff --git a/src/AccountGoWeb/Controllers/ContactController.cs b/src/AccountGoWeb/Controllers/ContactController.cs
index fd3e1a60a..f22788253 100644
--- a/src/AccountGoWeb/Controllers/ContactController.cs
+++ b/src/AccountGoWeb/Controllers/ContactController.cs
@@ -1,11 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
+using Dto.Common;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using System.Net.Http;
-using Dto.Common;
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
namespace AccountGoWeb.Controllers
@@ -35,8 +29,8 @@ public async System.Threading.Tasks.Task Contacts(int partyId = 0
//return View(model: contacts);
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "contact/contacts?partyId=" + partyId + "&partyType=" + partyType);
if (response.IsSuccessStatusCode)
@@ -57,7 +51,7 @@ public async System.Threading.Tasks.Task Contacts(int partyId = 0
///
public IActionResult Contact(int id = 0, int partyId = 0, int partyType = 0)
{
- Contact contact = null;
+ Contact? contact = null;
if (id == 0) // creating new contact
{
diff --git a/src/AccountGoWeb/Controllers/DashboardController.cs b/src/AccountGoWeb/Controllers/DashboardController.cs
index c8791445a..982b6b73b 100644
--- a/src/AccountGoWeb/Controllers/DashboardController.cs
+++ b/src/AccountGoWeb/Controllers/DashboardController.cs
@@ -1,9 +1,8 @@
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
+ //[Microsoft.AspNetCore.Authorization.Authorize]
public class DashboardController : BaseController
{
public DashboardController(IConfiguration config)
@@ -19,7 +18,7 @@ public IActionResult Index()
public IActionResult MonthlySales()
{
- ViewBag.ApiMontlySales = _baseConfig["ApiUrl"] + "sales/getmonthlysales";
+ ViewBag.ApiMontlySales = _baseConfig!["ApiUrl"] + "Sales/GetMonthlySales";
return View();
}
}
diff --git a/src/AccountGoWeb/Controllers/ErrorController.cs b/src/AccountGoWeb/Controllers/ErrorController.cs
new file mode 100644
index 000000000..fe423d702
--- /dev/null
+++ b/src/AccountGoWeb/Controllers/ErrorController.cs
@@ -0,0 +1,60 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Diagnostics;
+
+namespace AccountGoWeb.Controllers
+{
+ // Controller responsible for handling various error scenarios
+ public class ErrorController : GoodController
+ {
+ private readonly ILogger _logger;
+
+ public ErrorController(IConfiguration config, ILogger logger)
+ {
+ _configuration = config;
+ _logger = logger;
+ }
+
+ // Handles HTTP status code errors like 404, 500, etc.
+ // Maps to route "/Error/{statusCode}" as configured in Program.cs
+ [Route("Error/{statusCode}")]
+ public IActionResult HttpStatusCodeHandler(int statusCode)
+ {
+ var statusCodeResult = HttpContext.Features.Get();
+
+ ViewBag.PageContentHeader = $"Error {statusCode}";
+
+ switch (statusCode)
+ {
+ case 404:
+ ViewBag.ErrorMessage = "Sorry, the page you requested could not be found";
+ _logger.LogWarning($"404 error occurred. Path = {statusCodeResult?.OriginalPath}");
+ break;
+ case 500:
+ ViewBag.ErrorMessage = "An internal server error occurred";
+ _logger.LogError($"500 error occurred. Path = {statusCodeResult?.OriginalPath}");
+ break;
+ default:
+ // Generic message for other status codes
+ ViewBag.ErrorMessage = "An error occurred";
+ break;
+ }
+
+ return View("Error");
+ }
+
+ // Handles uncaught exceptions throughout the application
+ // Maps to route "/Error" as configured in Program.cs via UseExceptionHandler
+ [Route("Error")]
+ public IActionResult Error()
+ {
+ var exceptionDetails = HttpContext.Features.Get();
+
+ ViewBag.PageContentHeader = "Error";
+ ViewBag.ErrorMessage = "An unexpected error occurred";
+
+ _logger.LogError($"Exception: {exceptionDetails?.Error?.Message} occurred at {exceptionDetails?.Path}");
+
+ return View();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AccountGoWeb/Controllers/FinancialsController.cs b/src/AccountGoWeb/Controllers/FinancialsController.cs
index bbe60f680..f33c1b61f 100644
--- a/src/AccountGoWeb/Controllers/FinancialsController.cs
+++ b/src/AccountGoWeb/Controllers/FinancialsController.cs
@@ -1,196 +1,215 @@
using Microsoft.AspNetCore.Mvc;
-using System.Collections.Generic;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
- public class FinancialsController : BaseController
+ //[Microsoft.AspNetCore.Authorization.Authorize]
+ public class FinancialsController : BaseController
+ {
+ private readonly ILogger _logger;
+
+ public FinancialsController(IConfiguration config, ILogger logger)
{
- public FinancialsController(Microsoft.Extensions.Configuration.IConfiguration config)
- {
- _baseConfig = config;
- }
+ _baseConfig = config;
+ _logger = logger;
+ }
- public IActionResult AddJournalEntry()
- {
- ViewBag.PageContentHeader = "Add Journal Entry";
- return View();
- }
+ public IActionResult AddJournalEntry()
+ {
+ ViewBag.PageContentHeader = "Add Journal Entry";
+ return View();
+ }
- public IActionResult JournalEntry(int id)
- {
- ViewBag.PageContentHeader = "Journal Entry";
- return View();
- }
+ public IActionResult JournalEntry(int id)
+ {
+ ViewBag.PageContentHeader = "Journal Entry";
+ return View();
+ }
- public async System.Threading.Tasks.Task Accounts()
+ public async Task Accounts()
+ {
+ ViewBag.PageContentHeader = "Chart of Accounts";
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ _logger.LogInformation($"+++++++++++++++ baseUri={baseUri} +++++++++++++++");
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.GetAsync(baseUri + "financials/accounts");
+ if (response.IsSuccessStatusCode)
{
- ViewBag.PageContentHeader = "Accounts";
-
- using (var client = new System.Net.Http.HttpClient())
- {
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "financials/accounts");
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- return View(model: responseJson);
- }
- }
-
- return View();
+ var responseJson = await response.Content.ReadAsStringAsync();
+ var accountModels = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
+ return View(accountModels);
}
+ }
+
+ return View();
+ }
+
+ public async Task Account(int? id = null)
+ {
+ Dto.Financial.Account? accountModel = null;
+ if (id == null)
+ {
+ accountModel = new Dto.Financial.Account();
+ }
+ else
+ {
+ accountModel = await GetAsync("financials/account?id=" + id);
+ }
+
+ ViewBag.PageContentHeader = "Account";
+ return View(accountModel);
+ }
- public async System.Threading.Tasks.Task Account(int? id = null)
+ public async System.Threading.Tasks.Task JournalEntries()
+ {
+ ViewBag.PageContentHeader = "Journal Entries";
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.GetAsync(baseUri + "financials/journalentries");
+ if (response.IsSuccessStatusCode)
{
- Dto.Financial.Account accountModel = null;
- if (id == null)
- {
- accountModel = new Dto.Financial.Account();
- }
- else
- {
- accountModel = await GetAsync("financials/account?id=" + id);
- }
-
- ViewBag.PageContentHeader = "Account";
- return View(accountModel);
+ var responseJson = await response.Content.ReadAsStringAsync();
+ return View(model: responseJson);
}
+ }
+
+ return View();
+ }
- public async System.Threading.Tasks.Task JournalEntries()
+ public async System.Threading.Tasks.Task GeneralLedger()
+ {
+ ViewBag.PageContentHeader = "General Ledger";
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.GetAsync(baseUri + "financials/generalledger");
+ if (response.IsSuccessStatusCode)
{
- ViewBag.PageContentHeader = "Journal Entries";
-
- using (var client = new System.Net.Http.HttpClient())
- {
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "financials/journalentries");
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- return View(model: responseJson);
- }
- }
-
- return View();
+ var responseJson = await response.Content.ReadAsStringAsync();
+ return View(model: responseJson);
}
+ }
+
+ return View();
+ }
- public async System.Threading.Tasks.Task GeneralLedger()
+ public async System.Threading.Tasks.Task TrialBalance()
+ {
+ ViewBag.PageContentHeader = "Trial Balance";
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.GetAsync(baseUri + "financials/trialbalance");
+ if (response.IsSuccessStatusCode)
{
- ViewBag.PageContentHeader = "General Ledger";
-
- using (var client = new System.Net.Http.HttpClient())
- {
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "financials/generalledger");
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- return View(model: responseJson);
- }
- }
-
- return View();
+ var responseJson = await response.Content.ReadAsStringAsync();
+ var trialBalanceModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
+ return View(trialBalanceModel);
}
+ }
+
+ return View();
+ }
- public async System.Threading.Tasks.Task TrialBalance()
+ public async System.Threading.Tasks.Task BalanceSheet()
+ {
+ ViewBag.PageContentHeader = "Balance Sheet";
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.GetAsync(baseUri + "financials/balancesheet");
+ if (response.IsSuccessStatusCode)
{
- ViewBag.PageContentHeader = "Trial Balance";
-
- using (var client = new System.Net.Http.HttpClient())
- {
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "financials/trialbalance");
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- var trialBalanceModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
- return View(trialBalanceModel);
- }
- }
-
- return View();
+ var responseJson = await response.Content.ReadAsStringAsync();
+ var balanceSheetModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
+ return View(balanceSheetModel);
}
+ }
+ return View();
+ // return View(new List()); // Use this statement to test the view with an empty balance sheet
+
+ //var Dto = _financialService.BalanceSheet().ToList();
+ //var dt = Helpers.CollectionHelper.ConvertTo(Dto);
+ //var incomestatement = _financialService.IncomeStatement();
+ //var netincome = incomestatement.Where(a => a.IsExpense == false).Sum(a => a.Amount) - incomestatement.Where(a => a.IsExpense == true).Sum(a => a.Amount);
+
+ // TODO: Add logic to get the correct account for accumulated profit/loss. Currently, the account code is hard-coded here.
+ // Solution 1: Add two columns in general ledger setting for the account id of accumulated profit and loss.
+ // Solution 2: Add column to Account table to flag if account is net income (profit and loss)
+ //if (netincome < 0)
+ //{
+ // var loss = Dto.Where(a => a.AccountCode == "30500").FirstOrDefault();
+ // loss.Amount = netincome;
+ //}
+ //else
+ //{
+ // var profit = Dto.Where(a => a.AccountCode == "30400").FirstOrDefault();
+ // profit.Amount = netincome;
+ //}
+
+ //return View(Dto);
+ }
+
+ public async Task IncomeStatement()
+ {
+ ViewBag.PageContentHeader = "Income Statement";
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
- public async System.Threading.Tasks.Task BalanceSheet()
+ try
{
- ViewBag.PageContentHeader = "Balance Sheet";
-
- using (var client = new System.Net.Http.HttpClient())
- {
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "financials/balancesheet");
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- var balanceSheetModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
- return View(balanceSheetModel);
- }
- }
-
- return View();
- //var Dto = _financialService.BalanceSheet().ToList();
- //var dt = Helpers.CollectionHelper.ConvertTo(Dto);
- //var incomestatement = _financialService.IncomeStatement();
- //var netincome = incomestatement.Where(a => a.IsExpense == false).Sum(a => a.Amount) - incomestatement.Where(a => a.IsExpense == true).Sum(a => a.Amount);
-
- // TODO: Add logic to get the correct account for accumulated profit/loss. Currently, the account code is hard-coded here.
- // Solution 1: Add two columns in general ledger setting for the account id of accumulated profit and loss.
- // Solution 2: Add column to Account table to flag if account is net income (profit and loss)
- //if (netincome < 0)
- //{
- // var loss = Dto.Where(a => a.AccountCode == "30500").FirstOrDefault();
- // loss.Amount = netincome;
- //}
- //else
- //{
- // var profit = Dto.Where(a => a.AccountCode == "30400").FirstOrDefault();
- // profit.Amount = netincome;
- //}
-
- //return View(Dto);
+ var response = await client.GetAsync(baseUri + "financials/incomestatement");
+ if (response.IsSuccessStatusCode)
+ {
+ var responseJson = await response.Content.ReadAsStringAsync();
+ var incomeStatementModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
+ return View(incomeStatementModel);
+ }
+ else
+ {
+ ViewBag.Error = "Failed to fetch income statement data.";
+ }
}
-
- public async System.Threading.Tasks.Task IncomeStatement()
+ catch (Exception ex)
{
- ViewBag.PageContentHeader = "Income Statement";
-
- using (var client = new System.Net.Http.HttpClient())
- {
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "financials/incomestatement");
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- var incomeStatementModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson);
- return View(incomeStatementModel);
- }
- }
-
- return View();
- //var Dto = _financialService.IncomeStatement();
- //return View(Dto);
+ ViewBag.Error = $"Error: {ex.Message}";
}
+ }
- public IActionResult Banks()
- {
- ViewBag.PageContentHeader = "Cash/Banks";
+ return View(new List());
+ }
- var banks = GetAsync>("financials/cashbanks").Result;
- return View(banks);
- }
+ public IActionResult Banks()
+ {
+ ViewBag.PageContentHeader = "Cash/Banks";
+
+ var banks = GetAsync>("financials/cashbanks").Result;
+
+ return View(banks);
}
+
+
+
+ }
}
diff --git a/src/AccountGoWeb/Controllers/GoodController.cs b/src/AccountGoWeb/Controllers/GoodController.cs
new file mode 100644
index 000000000..b2d2d754c
--- /dev/null
+++ b/src/AccountGoWeb/Controllers/GoodController.cs
@@ -0,0 +1,109 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Security.Claims;
+
+namespace AccountGoWeb.Controllers
+{
+ public class GoodController : Controller
+ {
+ protected IConfiguration? _configuration;
+
+ protected string? GetAccessToken()
+ {
+ return User?.FindFirst("AccessToken")?.Value;
+ }
+
+ protected HttpResponseMessage Get(string uri)
+ {
+ string responseJson = string.Empty;
+ using (var client = new HttpClient())
+ {
+ string? baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
+ var response = client.GetAsync(baseUri + uri);
+ return response.Result;
+ }
+ }
+
+ protected HttpResponseMessage Post(string uri, StringContent data)
+ {
+ string responseJson = string.Empty;
+ using (var client = new HttpClient())
+ {
+ string? baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
+ var response = client.PostAsync(baseUri + uri, data);
+ return response.Result;
+ }
+ }
+
+ protected async System.Threading.Tasks.Task GetAsync(string uri, string? accessToken = null)
+ {
+ string responseJson = string.Empty;
+ using (var client = new HttpClient())
+ {
+ string? baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+
+ // Use provided token or get from claims
+ var token = accessToken ?? GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
+ var response = await client.GetAsync(baseUri + uri);
+ if (response.IsSuccessStatusCode)
+ {
+ responseJson = await response.Content.ReadAsStringAsync();
+ }
+ }
+ return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!;
+ }
+
+ protected async System.Threading.Tasks.Task PostAsync(string uri, StringContent data)
+ {
+ string responseJson = string.Empty;
+ using (var client = new HttpClient())
+ {
+ string? baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+
+ var token = GetAccessToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
+ var response = await client.PostAsync(baseUri + uri, data);
+ if (response.IsSuccessStatusCode)
+ {
+ responseJson = await response.Content.ReadAsStringAsync();
+ }
+ }
+ return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!;
+ }
+ }
+}
diff --git a/src/AccountGoWeb/Controllers/HomeController.cs b/src/AccountGoWeb/Controllers/HomeController.cs
index d7b5a2def..b2b992dc8 100644
--- a/src/AccountGoWeb/Controllers/HomeController.cs
+++ b/src/AccountGoWeb/Controllers/HomeController.cs
@@ -1,9 +1,8 @@
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
+ //[Microsoft.AspNetCore.Authorization.Authorize]
public class HomeController : BaseController
{
public HomeController(IConfiguration config)
@@ -14,7 +13,7 @@ public HomeController(IConfiguration config)
public IActionResult Index()
{
ViewBag.PageContentHeader = "Dashboard";
- ViewBag.ApiMontlySales = _baseConfig["ApiUrl"] + "sales/getmonthlysales";
+ ViewBag.ApiMontlySales = _baseConfig!["ApiUrl"] + "Sales/GetMonthlySales";
return View();
}
}
diff --git a/src/AccountGoWeb/Controllers/InventoryController.cs b/src/AccountGoWeb/Controllers/InventoryController.cs
index e15c5645b..e357ecce3 100644
--- a/src/AccountGoWeb/Controllers/InventoryController.cs
+++ b/src/AccountGoWeb/Controllers/InventoryController.cs
@@ -1,26 +1,28 @@
using Dto.Inventory;
using Microsoft.AspNetCore.Mvc;
-using System.Net.Http;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
+ // [Microsoft.AspNetCore.Authorization.Authorize]
public class InventoryController : BaseController
{
- public InventoryController(Microsoft.Extensions.Configuration.IConfiguration config)
+ private readonly ILogger _logger;
+ public InventoryController(Microsoft.Extensions.Configuration.IConfiguration config,
+ ILogger logger)
{
_baseConfig = config;
Models.SelectListItemHelper._config = config;
+ _logger = logger;
}
- public async System.Threading.Tasks.Task Items()
+ public async Task Index()
{
ViewBag.PageContentHeader = "Items";
using (var client = new System.Net.Http.HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "inventory/items");
if (response.IsSuccessStatusCode)
@@ -33,14 +35,14 @@ public async System.Threading.Tasks.Task Items()
return View();
}
- public async System.Threading.Tasks.Task ICJ()
+ public async Task ICJ()
{
ViewBag.PageContentHeader = "Inventory Control Journal";
using (var client = new System.Net.Http.HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "inventory/icj");
if (response.IsSuccessStatusCode)
@@ -53,9 +55,10 @@ public async System.Threading.Tasks.Task ICJ()
return View();
}
- public IActionResult Item(int id = -1)
+ public IActionResult Item(int id)
{
- Item itemModel = null;
+ _logger.LogInformation("GetItem: " + id);
+ Item? itemModel = null;
if (id == -1)
{
ViewBag.PageContentHeader = "Item Customer";
@@ -76,6 +79,39 @@ public IActionResult Item(int id = -1)
return View(itemModel);
}
+ public IActionResult AddItem(){
+ ViewBag.PageContentHeader = "New Item";
+
+ ViewBag.ItemCategories = Models.SelectListItemHelper.ItemCategories();
+ ViewBag.Measurements = Models.SelectListItemHelper.UnitOfMeasurements();
+ ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups();
+ ViewBag.PreferredVendorId = Models.SelectListItemHelper.Vendors();
+ ViewBag.Accounts = Models.SelectListItemHelper.Accounts();
+
+ Item itemModel = new Item();
+
+ return View(itemModel);
+ }
+
+ [HttpPost]
+ public IActionResult AddItem(Item itemModel){
+ ViewBag.PageContentHeader = "New Item";
+
+ if (ModelState.IsValid) {
+ _logger.LogInformation("Item Model is Valid: " + itemModel.Description);
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(itemModel);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+ var response = Post("Inventory/SaveItem", content);
+ _logger.LogInformation("Response: " + response);
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("Items");
+ }
+
+ return View(itemModel);
+ }
+
+ [HttpPost]
public IActionResult SaveItem(Item itemModel)
{
if (ModelState.IsValid)
@@ -86,7 +122,7 @@ public IActionResult SaveItem(Item itemModel)
var response = PostAsync("inventory/saveitem", content);
- return RedirectToAction("Items");
+ return RedirectToAction("Index");
}
ViewBag.Accounts = Models.SelectListItemHelper.Accounts();
@@ -94,13 +130,12 @@ public IActionResult SaveItem(Item itemModel)
ViewBag.Measurements = Models.SelectListItemHelper.UnitOfMeasurements();
ViewBag.ItemCategories = Models.SelectListItemHelper.ItemCategories();
-
if (itemModel.Id > 0)
ViewBag.PageContentHeader = "Item Item";
else
ViewBag.PageContentHeader = "New Card";
- return View("Item", itemModel);
+ return View("Index");
}
}
}
diff --git a/src/AccountGoWeb/Controllers/PurchasingController.cs b/src/AccountGoWeb/Controllers/PurchasingController.cs
index d8215259c..fe050125a 100644
--- a/src/AccountGoWeb/Controllers/PurchasingController.cs
+++ b/src/AccountGoWeb/Controllers/PurchasingController.cs
@@ -1,16 +1,18 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using System.Net.Http;
+using Dto.Purchasing;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
+ //[Microsoft.AspNetCore.Authorization.Authorize]
public class PurchasingController : BaseController
{
- public PurchasingController(IConfiguration config)
+ private readonly ILogger _logger;
+ public PurchasingController(IConfiguration config, ILogger logger)
{
_baseConfig = config;
Models.SelectListItemHelper._config = config;
+ _logger = logger;
}
public IActionResult Index()
@@ -24,7 +26,7 @@ public IActionResult PurchaseOrders()
string purchaseOrders = GetAsync("purchasing/purchaseorders")
.Result
- .ToString();
+ .ToString()!;
return View(model: purchaseOrders);
}
@@ -32,21 +34,107 @@ public IActionResult PurchaseOrders()
public IActionResult AddPurchaseOrder()
{
ViewBag.PageContentHeader = "Add Purchase Order";
+ PurchaseOrder purchaseOrderModel = new PurchaseOrder();
+ purchaseOrderModel.PurchaseOrderLines = new List { new PurchaseOrderLine {
+ Amount = 0,
+ Discount = 0,
+ ItemId = 1,
+ Quantity = 1,
+ } };
+ purchaseOrderModel.No = new System.Random().Next(1, 99999).ToString();
ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
- return View();
+ return View(purchaseOrderModel);
+ }
+
+ [HttpPost]
+ public IActionResult AddPurchaseOrder(PurchaseOrder purchaseOrder, string? addRowBtn)
+ {
+ ViewBag.PageContentHeader = "Add Purchase Order";
+
+
+ if (!string.IsNullOrEmpty(addRowBtn))
+ {
+ purchaseOrder.PurchaseOrderLines.Add(new PurchaseOrderLine
+ {
+ Amount = 0,
+ Discount = 0,
+ ItemId = 1,
+ Quantity = 1
+ });
+
+ ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(purchaseOrder);
+ }
+ else if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(purchaseOrder);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ var response = PostAsync("purchasing/savepurchaseorder", content);
+ return Json(response);
+ return RedirectToAction("PurchaseOrders");
+ }
+
+ return View("PurchaseOrders");
+ }
+
+ public IActionResult PurchaseInvoice(int id)
+ {
+ ViewBag.PageContentHeader = "Purchase Invoice";
+
+ PurchaseInvoice? purchaseInvoiceModel = null;
+
+ if (id == 0)
+ {
+ ViewBag.PageContentHeader = "New Purchase Invoice";
+ return View("PurchaseInvoice");
+ }
+ else
+ {
+ purchaseInvoiceModel = GetAsync("Purchasing/PurchaseInvoice?id=" + id).Result;
+ }
+
+ ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(purchaseInvoiceModel);
}
- public IActionResult PurchaseOrder(int purchId = 0)
+
+ public IActionResult PurchaseOrder(int id)
{
ViewBag.PageContentHeader = "Purchase Order";
- var purchOrderDto = GetAsync("purchasing/purchaseorder?id=" + purchId).Result;
+ PurchaseOrder? purchaseOrderModel = null;
+
+ if (id == 0)
+ {
+ ViewBag.PageContentHeader = "New Purchase Order";
+ return View();
+ }
+ else
+ {
+ purchaseOrderModel = GetAsync("Purchasing/PurchaseOrder?id=" + id).Result;
+ }
ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
- return View();
+ return View(purchaseOrderModel);
}
public async System.Threading.Tasks.Task PurchaseInvoices()
@@ -54,8 +142,8 @@ public async System.Threading.Tasks.Task PurchaseInvoices()
ViewBag.PageContentHeader = "Purchase Invoices";
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "purchasing/purchaseinvoices");
if (response.IsSuccessStatusCode)
@@ -67,18 +155,59 @@ public async System.Threading.Tasks.Task PurchaseInvoices()
return View();
}
- public IActionResult AddPurchaseInvoice(int purchId = 0)
+ public IActionResult AddPurchaseInvoice()
{
ViewBag.PageContentHeader = "New Invoice";
- return View();
+ PurchaseInvoice purchaseInvoiceModel = new PurchaseInvoice();
+ purchaseInvoiceModel.PurchaseInvoiceLines = new List { new PurchaseInvoiceLine {
+ Amount = 0,
+ Discount = 0,
+ ItemId = 1,
+ Quantity = 1,
+ } };
+ purchaseInvoiceModel.No = new System.Random().Next(1, 99999).ToString();
+
+ ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(purchaseInvoiceModel);
}
- public IActionResult PurchaseInvoice(int id)
+ [HttpPost]
+ public async System.Threading.Tasks.Task AddPurchaseInvoice(PurchaseInvoice purchaseInvoice, string addRowBtn)
{
- ViewBag.PageContentHeader = "Purchase Invoice";
+ ViewBag.PageContentHeader = "New Invoice";
+ if (!string.IsNullOrEmpty(addRowBtn))
+ {
+ purchaseInvoice.PurchaseInvoiceLines.Add(new PurchaseInvoiceLine
+ {
+ Amount = 0,
+ Discount = 0,
+ ItemId = 1,
+ Quantity = 1
+ });
- ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.Vendors = Models.SelectListItemHelper.Vendors();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(purchaseInvoice);
+ }
+ else if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(purchaseInvoice);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ var response = await PostAsync("Purchasing/SavePurchaseInvoice", content);
+ _logger.LogInformation("Purchase Invoice Saved" + purchaseInvoice.Id);
+
+ return RedirectToAction("PurchaseInvoices");
+ }
return View();
}
@@ -95,8 +224,8 @@ public async System.Threading.Tasks.Task Vendors()
ViewBag.PageContentHeader = "Vendors";
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "purchasing/vendors");
if (response.IsSuccessStatusCode)
@@ -109,7 +238,7 @@ public async System.Threading.Tasks.Task Vendors()
}
public IActionResult Vendor(int id = -1)
{
- Dto.Purchasing.Vendor vendorModel = null;
+ Dto.Purchasing.Vendor? vendorModel = null;
if (id == -1)
{
ViewBag.PageContentHeader = "New Vendor";
@@ -169,7 +298,7 @@ public IActionResult Payment(int id)
VendorId = invoice.VendorId,
VendorName = invoice.VendorName,
InvoiceAmount = invoice.Amount,
- AmountPaid = invoice.AmountPaid,
+ AmountPaid = invoice.AmountPaid,
Date = invoice.InvoiceDate
};
@@ -177,7 +306,7 @@ public IActionResult Payment(int id)
return View(model);
}
-
+
[HttpPost]
public IActionResult Payment(Models.Purchasing.Payment model)
{
diff --git a/src/AccountGoWeb/Controllers/QuotationsController.cs b/src/AccountGoWeb/Controllers/QuotationsController.cs
index cbe402648..a6b320e06 100644
--- a/src/AccountGoWeb/Controllers/QuotationsController.cs
+++ b/src/AccountGoWeb/Controllers/QuotationsController.cs
@@ -1,14 +1,17 @@
-using System.Net.Http;
+using Dto.Sales;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
- public class QuotationsController : BaseController
+ //[Microsoft.AspNetCore.Authorization.Authorize]
+ public class QuotationsController : GoodController
{
- public QuotationsController(IConfiguration config) {
- _baseConfig = config;
+ //private readonly IConfiguration _configuration;
+ private readonly ILogger _logger;
+ public QuotationsController(IConfiguration config, ILogger logger)
+ {
+ _configuration = config;
+ _logger = logger;
}
public IActionResult Index()
@@ -22,8 +25,8 @@ public async System.Threading.Tasks.Task Quotations()
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "sales/quotations");
if (response.IsSuccessStatusCode)
@@ -36,18 +39,107 @@ public async System.Threading.Tasks.Task Quotations()
return View();
}
+ [HttpGet]
public IActionResult AddSalesQuotation()
{
ViewBag.PageContentHeader = "Add Sales Quotation";
- return View();
+ SalesQuotation model = new SalesQuotation();
+ model.SalesQuotationLines = new List { new SalesQuotationLine {
+ Amount = 0,
+ Quantity = 1,
+ Discount = 0,
+ ItemId = 1,
+ MeasurementId = 1,
+ } };
+ model.No = new System.Random().Next(1, 99999).ToString(); // TODO: Replace with system generated numbering.
+
+ ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(model);
}
- public IActionResult Quotation()
+ [HttpPost]
+ public async Task AddSalesQuotation(Dto.Sales.SalesQuotation model, string? addRowBtn)
{
- ViewBag.PageContentHeader = "Sales Quotation";
- return View();
+ ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+ if (!string.IsNullOrEmpty(addRowBtn))
+ {
+ _logger.LogInformation("Add Row Button Clicked");
+ model.SalesQuotationLines.Add(new SalesQuotationLine
+ {
+ Amount = 0,
+ Quantity = 1,
+ Discount = 0,
+ ItemId = 1,
+ MeasurementId = 1,
+ });
+
+ return View(model);
+
+ }
+ else if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ using (var client = new HttpClient())
+ {
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new Uri(baseUri!);
+ var response = await client.PostAsync("sales/savequotation", content);
+
+ if (response.IsSuccessStatusCode) {
+ _logger.LogInformation("Quotation has been successfully saved.");
+ return RedirectToAction("quotations");
+ } else {
+ _logger.LogInformation("Quotation save failed.");
+ return View(model);
+ }
+ }
+ } else {
+ _logger.LogInformation("Model State is not valid.");
+ return View(model);
+ }
+
+ }
+
+ [HttpGet]
+ public IActionResult Quotation(int id)
+ {
+ ViewBag.PageContentHeader = "Edit Sale Quotation";
+
+ SalesQuotation? model = null;
+
+ if (id == 0)
+ {
+ ViewBag.PageContentHeader = "Add Sales Quotation";
+ return View("AddSalesQuotation");
+ }
+ else
+ {
+ model = GetAsync("Sales/Quotation?id=" + id).Result;
+ @ViewBag.Id = model.Id;
+ @ViewBag.CustomerName = model.CustomerName;
+ @ViewBag.PaymentTermId = model.PaymentTermId;
+ @ViewBag.SalesQuotationLines = model.SalesQuotationLines;
+ @ViewBag.TotalAmount = Math.Round(model.Amount, 2);
+ }
+
+ @ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ @ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(model);
}
}
}
diff --git a/src/AccountGoWeb/Controllers/SPAProxyController.cs b/src/AccountGoWeb/Controllers/SPAProxyController.cs
index 67313350c..b17b3bbd4 100644
--- a/src/AccountGoWeb/Controllers/SPAProxyController.cs
+++ b/src/AccountGoWeb/Controllers/SPAProxyController.cs
@@ -1,10 +1,7 @@
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using System.Text;
namespace AccountGoWeb.Controllers
{
diff --git a/src/AccountGoWeb/Controllers/SalesController.cs b/src/AccountGoWeb/Controllers/SalesController.cs
index ec582bf86..2663b177e 100644
--- a/src/AccountGoWeb/Controllers/SalesController.cs
+++ b/src/AccountGoWeb/Controllers/SalesController.cs
@@ -1,19 +1,21 @@
using AccountGoWeb.Models;
using Dto.Sales;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using System.Collections.Generic;
-using System.Net.Http;
+using Newtonsoft.Json;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
- public class SalesController : BaseController
+ //[Microsoft.AspNetCore.Authorization.Authorize]
+ public class SalesController : GoodController
{
- public SalesController(IConfiguration config)
+ // private readonly IConfiguration _configuration;
+ private readonly ILogger _logger;
+
+ public SalesController(IConfiguration config, ILogger logger)
{
- _baseConfig = config;
+ _configuration = config;
Models.SelectListItemHelper._config = config;
+ _logger = logger;
}
public IActionResult Index()
@@ -26,8 +28,8 @@ public async System.Threading.Tasks.Task SalesOrders()
ViewBag.PageContentHeader = "Sales Orders";
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "sales/salesorders");
if (response.IsSuccessStatusCode)
@@ -42,35 +44,123 @@ public async System.Threading.Tasks.Task SalesOrders()
public IActionResult AddSalesOrder()
{
ViewBag.PageContentHeader = "Add Sales Order";
-
- return View();
+ SalesOrder salesOrderModel = new SalesOrder();
+ salesOrderModel.SalesOrderLines = new List { new SalesOrderLine {
+ Amount = 0,
+ Discount = 0,
+ ItemId = 1,
+ Quantity = 1,
+ } };
+ salesOrderModel.No = new System.Random().Next(1, 99999).ToString();
+
+ @ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ @ViewBag.Items = Models.SelectListItemHelper.Items();
+ @ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(salesOrderModel);
}
[HttpPost]
- public IActionResult AddSalesOrder(object Dto)
+ public IActionResult AddSalesOrder(SalesOrder Dto, string addRowBtn)
{
- return Ok();
+ if (!string.IsNullOrEmpty(addRowBtn))
+ {
+ Dto.SalesOrderLines.Add(new SalesOrderLine
+ {
+ Amount = 0,
+ Quantity = 1,
+ Discount = 0,
+ ItemId = 1,
+ MeasurementId = 1,
+ });
+
+ ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(Dto);
+ }
+ else if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(Dto);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ var response = Post("Sales/addsalesorder", content);
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("salesorders");
+ }
+ @ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ @ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return RedirectToAction("salesorders");
}
-
+
public IActionResult SalesOrder(int id)
{
ViewBag.PageContentHeader = "Sales Order";
- return View();
+ SalesOrder? salesOrderModel = null;
+ if (id == -1)
+ {
+ ViewBag.PageContentHeader = "Add Sales Order";
+ return View("AddSalesOrder");
+
+ }
+ else
+ {
+ salesOrderModel = GetAsync("Sales/SalesOrder?id=" + id).Result;
+ ViewBag.CustomerName = salesOrderModel.CustomerName;
+ ViewBag.OrderDate = salesOrderModel.OrderDate;
+ ViewBag.SalesOrderLines = salesOrderModel.SalesOrderLines;
+ ViewBag.TotalAmount = salesOrderModel.Amount;
+ }
+
+ @ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ @ViewBag.Items = Models.SelectListItemHelper.Items();
+ @ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(salesOrderModel);
}
public IActionResult SalesInvoice(int id)
{
ViewBag.PageContentHeader = "Sales Invoice";
- return View();
+ SalesInvoice? salesInvoiceModel = null;
+
+ if (id == 0)
+ {
+ ViewBag.PageContentHeader = "Add Sales Invoice";
+ return View("AddSalesInvoice");
+ }
+ else
+ {
+ salesInvoiceModel = GetAsync("Sales/SalesInvoice?id=" + id).Result;
+ ViewBag.Id = salesInvoiceModel.Id;
+ ViewBag.CustomerName = salesInvoiceModel.CustomerName;
+ ViewBag.InvoiceDate = salesInvoiceModel.InvoiceDate;
+ ViewBag.SalesInvoiceLines = salesInvoiceModel.SalesInvoiceLines;
+ ViewBag.TotalAmount = salesInvoiceModel.Amount;
+ }
+
+ @ViewBag.Customers = SelectListItemHelper.Customers();
+ @ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms();
+ @ViewBag.Items = SelectListItemHelper.Items();
+ @ViewBag.Measurements = SelectListItemHelper.Measurements();
+
+ return View("SalesInvoice", salesInvoiceModel);
}
- public async System.Threading.Tasks.Task SalesInvoices()
+ public async Task SalesInvoices()
{
ViewBag.PageContentHeader = "Sales Invoices";
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "sales/salesinvoices");
if (response.IsSuccessStatusCode)
@@ -78,47 +168,164 @@ public async System.Threading.Tasks.Task SalesInvoices()
var responseJson = await response.Content.ReadAsStringAsync();
return View(model: responseJson);
}
+
+ @ViewBag.Customers = SelectListItemHelper.Customers();
+ @ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms();
+ @ViewBag.Items = SelectListItemHelper.Items();
+ @ViewBag.Measurements = SelectListItemHelper.Measurements();
}
return View();
}
+ [HttpPost]
+ public async System.Threading.Tasks.Task SalesInvoice(SalesInvoice salesInvoiceModel)
+ {
+ if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(salesInvoiceModel);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+ string ReadAsStringAsync = await content.ReadAsStringAsync();
+ _logger.LogInformation("SaveSalesInvoice: " + ReadAsStringAsync);
+ var response = Post("Sales/UpdateSalesInvoice", content);
+
+ if (response.IsSuccessStatusCode)
+ {
+ return RedirectToAction("SalesInvoices");
+ }
+ }
+
+ ViewBag.Customers = SelectListItemHelper.Customers();
+ ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = SelectListItemHelper.Items();
+ ViewBag.Measurements = SelectListItemHelper.Measurements();
+ ViewBag.TotalAmount = salesInvoiceModel.Amount;
+
+ return View(salesInvoiceModel);
+ }
+
+ [HttpGet]
public IActionResult AddSalesInvoice()
{
ViewBag.PageContentHeader = "Add Sales Invoice";
- return View();
+ SalesInvoice salesInvoiceModel = new SalesInvoice();
+ salesInvoiceModel.SalesInvoiceLines = new List { new SalesInvoiceLine {
+ Amount = 0,
+ Discount = 0,
+ ItemId = 1,
+ Quantity = 1,
+ } };
+ salesInvoiceModel.No = new System.Random().Next(1, 99999).ToString(); // TODO: Replace with system generated numbering.
+
+ @ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ @ViewBag.Items = Models.SelectListItemHelper.Items();
+ @ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(salesInvoiceModel);
+ }
+
+ [HttpPost]
+ public async Task AddSalesInvoice(SalesInvoice Dto, string? addRowBtn)
+ {
+ if (!string.IsNullOrEmpty(addRowBtn))
+ {
+ Dto.SalesInvoiceLines!.Add(new SalesInvoiceLine
+ {
+ Amount = 0,
+ Quantity = 1,
+ Discount = 0,
+ ItemId = 1,
+ MeasurementId = 1,
+ });
+
+ ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(Dto);
+ }
+ else if (ModelState.IsValid)
+ {
+ _logger.LogInformation("Posted value received: {Posted}", Dto.Posted);
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(Dto);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ _logger.LogInformation("AddSalesInvoice: " + await content.ReadAsStringAsync());
+ var response = Post("Sales/CreateSalesInvoice", content);
+
+ _logger.LogInformation("AddSalesInvoice response: " + response.ToString());
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("salesinvoices");
+ }
+
+ ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.Items = Models.SelectListItemHelper.Items();
+ ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms();
+ ViewBag.Measurements = Models.SelectListItemHelper.Measurements();
+
+ return View(Dto);
}
public async System.Threading.Tasks.Task SalesReceipts()
{
ViewBag.PageContentHeader = "Sales Receipts";
- using (var client = new HttpClient())
+ try
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "sales/salesreceipts");
- if (response.IsSuccessStatusCode)
+ using (var client = new HttpClient())
{
- var responseJson = await response.Content.ReadAsStringAsync();
- return View(model: responseJson);
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+
+ var response = await client.GetAsync("sales/salesreceipts");
+ if (response.IsSuccessStatusCode)
+ {
+ var responseJson = await response.Content.ReadAsStringAsync();
+ return View(model: responseJson);
+ }
+ else
+ {
+ _logger.LogError("Failed to fetch sales receipts. API returned status code: {StatusCode}", response.StatusCode);
+ ViewBag.ErrorMessage = "Failed to load sales receipts. Please try again later.";
+ }
}
}
- return View();
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "An error occurred while fetching sales receipts.");
+ ViewBag.ErrorMessage = "An unexpected error occurred while loading sales receipts.";
+ }
+
+ // Return the view with an error message if the API call fails
+ return View(model: "[]");
}
+ [HttpGet]
public IActionResult AddReceipt()
{
- ViewBag.PageContentHeader = "New Receipt";
-
- var model = new Models.Sales.AddReceipt();
-
- ViewBag.Customers = Models.SelectListItemHelper.Customers();
- ViewBag.DebitAccounts = Models.SelectListItemHelper.CashBanks();
- ViewBag.CreditAccounts = Models.SelectListItemHelper.Accounts();
- ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject(GetAsync>("sales/customers").Result);
-
- return View(model);
+ try
+ {
+ ViewBag.PageContentHeader = "New Receipt";
+ ViewBag.Customers = Models.SelectListItemHelper.Customers();
+ ViewBag.DebitAccounts = Models.SelectListItemHelper.CashBanks();
+ ViewBag.CreditAccounts = Models.SelectListItemHelper.Accounts();
+ ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject(
+ GetAsync>("sales/customers").Result
+ );
+
+ var model = new Models.Sales.AddReceipt();
+ return View(model);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "An error occurred while preparing the Add Receipt page.");
+ ViewBag.ErrorMessage = "Failed to load the page for adding a receipt. Please try again later.";
+ return View(new Models.Sales.AddReceipt());
+ }
}
[HttpPost]
@@ -126,32 +333,49 @@ public IActionResult AddReceipt(Models.Sales.AddReceipt model)
{
if (ModelState.IsValid)
{
- var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model);
- var content = new StringContent(serialize);
- content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
- var response = Post("sales/savereceipt", content);
- if(response.IsSuccessStatusCode)
- return RedirectToAction("salesreceipts");
+ try
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ var response = Post("sales/savereceipt", content);
+ if (response.IsSuccessStatusCode)
+ {
+ return RedirectToAction("SalesReceipts");
+ }
+ else
+ {
+ _logger.LogError("Failed to save receipt. API returned status code: {StatusCode}", response.StatusCode);
+ ViewBag.ErrorMessage = "Failed to save the receipt. Please try again.";
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "An error occurred while saving the receipt.");
+ ViewBag.ErrorMessage = "An unexpected error occurred. Please try again.";
+ }
}
+ // Reload dropdowns and return the view if validation or API call fails
ViewBag.PageContentHeader = "New Receipt";
-
ViewBag.Customers = Models.SelectListItemHelper.Customers();
ViewBag.DebitAccounts = Models.SelectListItemHelper.CashBanks();
ViewBag.CreditAccounts = Models.SelectListItemHelper.Accounts();
- ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject(GetAsync>("sales/customers").Result);
+ ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject(
+ GetAsync>("sales/customers").Result
+ );
return View(model);
}
-
public async System.Threading.Tasks.Task Customers()
{
ViewBag.PageContentHeader = "Customers";
using (var client = new HttpClient())
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
client.DefaultRequestHeaders.Accept.Clear();
var response = await client.GetAsync(baseUri + "sales/customers");
if (response.IsSuccessStatusCode)
@@ -162,10 +386,10 @@ public async System.Threading.Tasks.Task Customers()
}
return View();
}
-
+
public IActionResult Customer(int id = -1)
{
- Customer customerModel = null;
+ Customer? customerModel = null;
if (id == -1)
{
ViewBag.PageContentHeader = "New Customer";
@@ -185,24 +409,50 @@ public IActionResult Customer(int id = -1)
return View(customerModel);
}
- public IActionResult SaveCustomer(Customer customerModel)
+ [HttpPost]
+ public async System.Threading.Tasks.Task SaveSalesInvoice(SalesInvoice salesInvoiceModel)
{
if (ModelState.IsValid)
{
- var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(customerModel);
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(salesInvoiceModel);
var content = new StringContent(serialize);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
- var response = PostAsync("sales/savecustomer", content);
+ string ReadAsStringAsync = await content.ReadAsStringAsync();
+ _logger.LogInformation("SaveSalesInvoice: " + ReadAsStringAsync);
+ var response = Post("Sales/SaveSalesInvoice", content);
+ if (response.IsSuccessStatusCode)
+ {
+ return RedirectToAction("SalesInvoices");
+ }
+ }
+ ViewBag.Customers = SelectListItemHelper.Customers();
+ ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms();
+ ViewBag.Items = SelectListItemHelper.Items();
+ ViewBag.Measurements = SelectListItemHelper.Measurements();
+
+ return View("SalesInvoice", salesInvoiceModel);
+ }
+
+ public async Task SaveCustomer(Customer customerModel)
+ {
+ if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(customerModel);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+ string ReadAsStringAsync = await content.ReadAsStringAsync();
+ var response = await PostAsync("Sales/SaveCustomer", content);
return RedirectToAction("Customers");
}
- else {
+ else
+ {
ViewBag.Accounts = SelectListItemHelper.Accounts();
ViewBag.TaxGroups = SelectListItemHelper.TaxGroups();
ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms();
}
- if(customerModel.Id == -1)
+ if (customerModel.Id == -1)
ViewBag.PageContentHeader = "New Customer";
else
ViewBag.PageContentHeader = "Customer Card";
@@ -210,6 +460,7 @@ public IActionResult SaveCustomer(Customer customerModel)
return View("Customer", customerModel);
}
+
public IActionResult CustomerAllocations(int id)
{
ViewBag.PageContentHeader = "Customer Allocations";
@@ -217,38 +468,71 @@ public IActionResult CustomerAllocations(int id)
return View();
}
+ // [HttpGet]
public IActionResult Allocate(int id)
{
- ViewBag.PageContentHeader = "Receipt Allocation";
+ Console.WriteLine($"Allocate called with ID: {id}");
- var model = new Models.Sales.Allocate();
+ try
+ {
+ ViewBag.PageContentHeader = "Receipt Allocation";
- var receipt = GetAsync("sales/salesreceipt?id=" + id).Result;
+ var model = new Models.Sales.Allocate();
- ViewBag.CustomerName = receipt.CustomerName;
- ViewBag.ReceiptNo = receipt.ReceiptNo;
+ // Fetch receipt details
+ var receipt = GetAsync("sales/salesreceipt?id=" + id).Result;
+ if (receipt == null)
+ {
+ Console.WriteLine($"Receipt not found for ID: {id}");
+ _logger.LogError("Failed to fetch receipt with id: {id}", id);
+ return NotFound($"Receipt with id {id} not found.");
+ }
+
+ ViewBag.CustomerName = receipt.CustomerName;
+ ViewBag.ReceiptNo = receipt.ReceiptNo;
- model.CustomerId = receipt.CustomerId;
- model.ReceiptId = receipt.Id;
- model.Date = receipt.ReceiptDate;
- model.Amount = receipt.Amount;
- model.RemainingAmountToAllocate = receipt.RemainingAmountToAllocate;
+ model.CustomerId = receipt.CustomerId;
+ model.ReceiptId = receipt.Id;
+ model.Date = receipt.ReceiptDate;
+ model.Amount = receipt.Amount;
+ model.RemainingAmountToAllocate = receipt.RemainingAmountToAllocate;
- var invoices = GetAsync>("sales/customerinvoices?id=" + receipt.CustomerId).Result;
+ // Fetch customer invoices
+ _logger.LogInformation("Calling API: sales/customerinvoices?id={id}", receipt.CustomerId);
- foreach (var invoice in invoices) {
- if (invoice.Posted && invoice.TotalAllocatedAmount < invoice.Amount)
+ var invoices = GetAsync>("sales/customerinvoices?id=" + receipt.CustomerId).Result;
+ if (invoices == null)
{
- model.AllocationLines.Add(new Models.Sales.AllocationLine()
+ _logger.LogError("Failed to fetch invoices for customer with id: {CustomerId}", receipt.CustomerId);
+ return NotFound($"Invoices for customer with id {receipt.CustomerId} not found.");
+ }
+
+ foreach (var invoice in invoices)
+ {
+ _logger.LogInformation("Invoice: {Invoice}", JsonConvert.SerializeObject(invoice));
+ if (invoice.Posted && invoice.TotalAllocatedAmount < invoice.Amount)
+ {
+ model.AllocationLines.Add(new Models.Sales.AllocationLine()
+ {
+ InvoiceId = invoice.Id,
+ Amount = invoice.Amount,
+ AllocatedAmount = invoice.TotalAllocatedAmount
+ });
+ }
+ else
{
- InvoiceId = invoice.Id,
- Amount = invoice.Amount,
- AllocatedAmount = invoice.TotalAllocatedAmount
- });
+ _logger.LogInformation("Invoice excluded: Posted={Posted}, TotalAllocatedAmount={TotalAllocatedAmount}, Amount={Amount}",
+ invoice.Posted, invoice.TotalAllocatedAmount, invoice.Amount);
+ }
}
- }
- return View(model);
+ return View(model);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "An error occurred while processing the Allocate action for id: {id}", id);
+ return StatusCode(500, "An error occurred while processing your request.");
+ }
}
[HttpPost]
@@ -256,7 +540,8 @@ public IActionResult Allocate(Models.Sales.Allocate model)
{
if (ModelState.IsValid)
{
- if (model.IsValid()) {
+ if (model.IsValid())
+ {
var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model);
var content = new StringContent(serialize);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
@@ -288,5 +573,22 @@ public IActionResult SalesInvoicePdf(int id)
salesInvoiceModel.SalesInvoiceLines = invoice.SalesInvoiceLines;
return View(salesInvoiceModel);
}
+
+ public async Task DeleteSalesInvoice(int id)
+ {
+ using (var client = new HttpClient())
+ {
+ var baseUri = _configuration!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.DeleteAsync(baseUri + "Sales/DeleteSalesInvoice?id=" + id);
+
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("SalesInvoices");
+ }
+
+ return RedirectToAction("SalesInvoices");
+ }
+
}
}
diff --git a/src/AccountGoWeb/Controllers/TaxController.cs b/src/AccountGoWeb/Controllers/TaxController.cs
index bbc4e9132..85c07e6e5 100644
--- a/src/AccountGoWeb/Controllers/TaxController.cs
+++ b/src/AccountGoWeb/Controllers/TaxController.cs
@@ -1,43 +1,182 @@
-using Microsoft.AspNetCore.Mvc;
+using AccountGoWeb.Models.TaxSystem;
+using AutoMapper;
+using Dto.TaxSystem;
+using Microsoft.AspNetCore.Mvc;
+using System;
namespace AccountGoWeb.Controllers
{
- [Microsoft.AspNetCore.Authorization.Authorize]
+ //[Microsoft.AspNetCore.Authorization.Authorize]
public class TaxController : BaseController
{
- public TaxController(Microsoft.Extensions.Configuration.IConfiguration config)
+ private readonly IMapper _mapper;
+
+ public TaxController(Microsoft.Extensions.Configuration.IConfiguration config, IMapper mapper)
{
_baseConfig = config;
+ _mapper = mapper;
}
- public IActionResult Index() {
+ public IActionResult Index()
+ {
return RedirectToAction("taxes");
}
- public async System.Threading.Tasks.Task Taxes()
+ public async Task Taxes()
{
ViewBag.PageContentHeader = "Tax";
- using (var client = new System.Net.Http.HttpClient())
+ try
{
- var baseUri = _baseConfig["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + "tax/taxes");
- if (response.IsSuccessStatusCode)
+ var taxSystemDto = await GetAsync("tax/taxes");
+
+ if (taxSystemDto != null)
{
- var responseJson = await response.Content.ReadAsStringAsync();
- var taxSystemDto = Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson);
- var taxSystemViewModel = new Models.TaxSystem.TaxSystemViewModel();
- taxSystemViewModel.Taxes = taxSystemDto.Taxes;
- taxSystemViewModel.ItemTaxGroups = taxSystemDto.ItemTaxGroups;
- taxSystemViewModel.TaxGroups = taxSystemDto.TaxGroups;
-
+ var taxSystemViewModel = _mapper.Map(taxSystemDto);
return View(taxSystemViewModel);
}
+
+ // Return empty model if API returns null
+ return View(new Models.TaxSystem.TaxSystemViewModel());
+ }
+ catch (Exception ex)
+ {
+ System.Console.WriteLine($"Error loading taxes: {ex.Message}");
+ return View(new Models.TaxSystem.TaxSystemViewModel());
+ }
+ }
+
+ public IActionResult AddNewTax()
+ {
+ ViewBag.PageContentHeader = "Add New Tax";
+
+ @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups();
+ @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups();
+
+ return View();
+ }
+
+ [HttpPost]
+ public IActionResult AddNewTax(TaxForCreation taxForCreationDto)
+ {
+ if (ModelState.IsValid)
+ {
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(taxForCreationDto);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ var response = Post("Tax/addnewtax", content);
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("Taxes");
}
+ @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups();
+ @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups();
+
return View();
}
+
+ public IActionResult EditTax(string tax, string taxGroup, string itemTaxGroup)
+ {
+ ViewBag.PageContentHeader = "Edit Tax";
+
+ // Mapping Dto to View Model
+ var taxObj = Newtonsoft.Json.JsonConvert.DeserializeObject(tax);
+ var taxGroupObj = Newtonsoft.Json.JsonConvert.DeserializeObject(taxGroup);
+ var itemTaxGroupObj = Newtonsoft.Json.JsonConvert.DeserializeObject(itemTaxGroup);
+
+ var editTaxViewModel = new Models.TaxSystem.EditTaxViewModel();
+ editTaxViewModel.Tax = _mapper.Map(taxObj);
+ editTaxViewModel.TaxGroup = _mapper.Map(taxGroupObj);
+ editTaxViewModel.ItemTaxGroup = _mapper.Map(itemTaxGroupObj);
+
+ @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups();
+ @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups();
+
+ return View(editTaxViewModel);
+ }
+
+ [HttpPost]
+ public async Task EditTax(EditTaxViewModel editTaxViewModel)
+ {
+ if (ModelState.IsValid)
+ {
+ var taxForUpdateDto = _mapper.Map(editTaxViewModel);
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+
+ var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(taxForUpdateDto);
+ var content = new StringContent(serialize);
+ content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
+
+ var response = await client.PutAsync(baseUri + "Tax/edittax", content);
+
+ if (response.IsSuccessStatusCode)
+ {
+ return RedirectToAction("Taxes");
+ }
+ }
+
+ return RedirectToAction("Taxes");
+ }
+
+ @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups();
+ @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups();
+
+ return View(editTaxViewModel);
+ }
+
+ public async Task DeleteTax(int id)
+ {
+ using (var client = new HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.DeleteAsync(baseUri + "Tax/deletetax?id=" + id);
+
+ if(response.IsSuccessStatusCode)
+ return RedirectToAction("Taxes");
+ }
+
+ return RedirectToAction("Taxes");
+ }
+
+ public async Task DeleteTaxGroup(int id)
+ {
+ using (var client = new HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.DeleteAsync(baseUri + "Tax/deletetaxgroup?id=" + id);
+
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("Taxes");
+ }
+
+ return RedirectToAction("Taxes");
+ }
+
+ public async Task DeleteItemTaxGroup(int id)
+ {
+ using (var client = new HttpClient())
+ {
+ var baseUri = _baseConfig!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.DeleteAsync(baseUri + "Tax/deleteitemtaxgroup?id=" + id);
+
+ if (response.IsSuccessStatusCode)
+ return RedirectToAction("Taxes");
+ }
+
+ return RedirectToAction("Taxes");
+ }
+
}
}
diff --git a/src/AccountGoWeb/MappingProfile.cs b/src/AccountGoWeb/MappingProfile.cs
new file mode 100644
index 000000000..86e45f5c1
--- /dev/null
+++ b/src/AccountGoWeb/MappingProfile.cs
@@ -0,0 +1,58 @@
+using AutoMapper;
+
+namespace AccountGoWeb
+{
+ public class MappingProfile : Profile
+ {
+ public MappingProfile()
+ {
+ CreateMap();
+ CreateMap();
+
+ #region TaxSystem
+
+ // TaxView Model
+ CreateMap().ReverseMap();
+ CreateMap().ReverseMap();
+ CreateMap()
+ .ForMember(dest => dest.Taxes, opt => opt.MapFrom((src, dest, i, context) =>
+ {
+ return context.Mapper.Map>(src.Taxes);
+ }))
+ .ReverseMap();
+ CreateMap().ReverseMap();
+ CreateMap()
+ .ForMember(dest => dest.Taxes, opt => opt.MapFrom((src, dest, i, context) =>
+ {
+ return context.Mapper.Map>(src.Taxes);
+ }))
+ .ReverseMap();
+ CreateMap()
+ .ForMember(dest => dest.Tax, opt => opt.MapFrom((src, dest, i, context) =>
+ {
+ return context.Mapper.Map(src.Tax);
+ }))
+ .ReverseMap();
+
+ // TaxSystemViewModel
+ CreateMap()
+ .ForMember(dest => dest.Taxes, opt => opt.MapFrom((src, dest, i, context) =>
+ {
+ return context.Mapper.Map>(src.Taxes);
+ }))
+ .ForMember(dest => dest.TaxGroups, opt => opt.MapFrom((src, dest, i, context) =>
+ {
+ return context.Mapper.Map>(src.TaxGroups);
+ }))
+ .ForMember(dest => dest.ItemTaxGroups, opt => opt.MapFrom((src, dest, i, context) =>
+ {
+ return context.Mapper.Map>(src.ItemTaxGroups);
+ })
+ )
+ .ReverseMap();
+
+ #endregion
+
+ }
+ }
+}
diff --git a/src/AccountGoWeb/Models/Account/LoginViewModel.cs b/src/AccountGoWeb/Models/Account/LoginViewModel.cs
index 2506b0433..418ef2eaa 100644
--- a/src/AccountGoWeb/Models/Account/LoginViewModel.cs
+++ b/src/AccountGoWeb/Models/Account/LoginViewModel.cs
@@ -1,18 +1,17 @@
using System.ComponentModel.DataAnnotations;
-namespace AccountGoWeb.Models.Account
+namespace AccountGoWeb.Models.Account;
+
+public class LoginViewModel
{
- public class LoginViewModel
- {
- [Required]
- [EmailAddress]
- public string Email { get; set; }
+ [Required]
+ [EmailAddress]
+ public string? Email { get; set; }
- [Required]
- [DataType(DataType.Password)]
- public string Password { get; set; }
+ [Required]
+ [DataType(DataType.Password)]
+ public string? Password { get; set; }
- [Display(Name = "Remember me?")]
- public bool RememberMe { get; set; }
- }
+ [Display(Name = "Remember me?")]
+ public bool RememberMe { get; set; }
}
diff --git a/src/AccountGoWeb/Models/Account/RegisterViewModel.cs b/src/AccountGoWeb/Models/Account/RegisterViewModel.cs
index b072ae25e..5fbd58e5f 100644
--- a/src/AccountGoWeb/Models/Account/RegisterViewModel.cs
+++ b/src/AccountGoWeb/Models/Account/RegisterViewModel.cs
@@ -1,29 +1,28 @@
using System.ComponentModel.DataAnnotations;
-namespace AccountGoWeb.Models.Account
+namespace AccountGoWeb.Models.Account;
+
+public class RegisterViewModel
{
- public class RegisterViewModel
- {
- [Required]
- [EmailAddress]
- [Display(Name = "Email")]
- public string Email { get; set; }
+ [Required]
+ [EmailAddress]
+ [Display(Name = "Email")]
+ public string? Email { get; set; }
- [Required]
- [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
- [DataType(DataType.Password)]
- [Display(Name = "Password")]
- public string Password { get; set; }
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ [Display(Name = "Password")]
+ public string? Password { get; set; }
- [DataType(DataType.Password)]
- [Display(Name = "Confirm password")]
- [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
- public string ConfirmPassword { get; set; }
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm password")]
+ [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+ public string? ConfirmPassword { get; set; }
- [Display(Name = "First Name")]
- public string FirstName { get; set; }
+ [Display(Name = "First Name")]
+ public string? FirstName { get; set; }
- [Display(Name = "Last Name")]
- public string LastName { get; set; }
- }
+ [Display(Name = "Last Name")]
+ public string? LastName { get; set; }
}
diff --git a/src/AccountGoWeb/Models/Bogus/Student.cs b/src/AccountGoWeb/Models/Bogus/Student.cs
new file mode 100644
index 000000000..cd50037d1
--- /dev/null
+++ b/src/AccountGoWeb/Models/Bogus/Student.cs
@@ -0,0 +1,62 @@
+namespace AccountGoWeb.Models.Bogus;
+
+public class Student
+{
+ required public int? Id { get; set; }
+ required public string FirstName { get; set; }
+ required public string LastName { get; set; }
+ required public string School { get; set; }
+ public static IQueryable GetStudents()
+ {
+ int ndx = 0;
+ List students = new List() {
+ new Student() { Id = ++ndx, FirstName="Max", LastName="Pao", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Tom", LastName="Fay", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Ann", LastName="Sun", School="Nursing" },
+ new Student() { Id = ++ndx, FirstName="Joe", LastName="Fox", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Sue", LastName="Mai", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Ben", LastName="Lau", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Zoe", LastName="Ray", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Sam", LastName="Ash", School="Medicine" },
+ new Student() { Id = ++ndx, FirstName="Dan", LastName="Lee", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Pat", LastName="Day", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Kim", LastName="Rex", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Tim", LastName="Ram", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Rob", LastName="Wei", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Jan", LastName="Tex", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Jim", LastName="Kid", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Ben", LastName="Chu", School="Medicine" },
+ new Student() { Id = ++ndx, FirstName="Mia", LastName="Tao", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Ted", LastName="Day", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Amy", LastName="Roy", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Ian", LastName="Kit", School="Nursing" },
+ new Student() { Id = ++ndx, FirstName="Liz", LastName="Tan", School="Medicine" },
+ new Student() { Id = ++ndx, FirstName="Mat", LastName="Roy", School="Tourism" },
+ new Student() { Id = ++ndx, FirstName="Deb", LastName="Luo", School="Medicine" },
+ new Student() { Id = ++ndx, FirstName="Ana", LastName="Poe", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Lyn", LastName="Raj", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Amy", LastName="Ash", School="Tourism" },
+ new Student() { Id = ++ndx, FirstName="Kim", LastName="Kid", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Bec", LastName="Fry", School="Nursing" },
+ new Student() { Id = ++ndx, FirstName="Eva", LastName="Lap", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Eli", LastName="Yim", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Sam", LastName="Hui", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Joe", LastName="Jin", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Liz", LastName="Kuo", School="Agriculture" },
+ new Student() { Id = ++ndx, FirstName="Ric", LastName="Mak", School="Tourism" },
+ new Student() { Id = ++ndx, FirstName="Pam", LastName="Day", School="Computing" },
+ new Student() { Id = ++ndx, FirstName="Stu", LastName="Gad", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Tom", LastName="Bee", School="Tourism" },
+ new Student() { Id = ++ndx, FirstName="Bob", LastName="Lam", School="Agriculture" },
+ new Student() { Id = ++ndx, FirstName="Jim", LastName="Ots", School="Medicine" },
+ new Student() { Id = ++ndx, FirstName="Tom", LastName="Mag", School="Mining" },
+ new Student() { Id = ++ndx, FirstName="Hal", LastName="Doe", School="Agriculture" },
+ new Student() { Id = ++ndx, FirstName="Roy", LastName="Kim", School="Nursing" },
+ new Student() { Id = ++ndx, FirstName="Vis", LastName="Cox", School="Science" },
+ new Student() { Id = ++ndx, FirstName="Kay", LastName="Aga", School="Tourism" },
+ new Student() { Id = ++ndx, FirstName="Reo", LastName="Hui", School="Business" },
+ new Student() { Id = ++ndx, FirstName="Bob", LastName="Roe", School="Medicine" },
+ };
+ return students.AsQueryable();
+ }
+}
diff --git a/src/AccountGoWeb/Models/Financial/AccountViewModel.cs b/src/AccountGoWeb/Models/Financial/AccountViewModel.cs
new file mode 100644
index 000000000..604595389
--- /dev/null
+++ b/src/AccountGoWeb/Models/Financial/AccountViewModel.cs
@@ -0,0 +1,13 @@
+namespace AccountGoWeb.Models.Financial
+{
+ public class AccountViewModel
+ {
+ public string? AccountCode { get; set; }
+ public string? AccountName { get; set; }
+ public decimal TotalBalance { get; set; }
+ public decimal TotalDebitBalance { get; set; }
+ public decimal TotalCreditBalance { get; set; }
+ public IList? ChildAccounts { get; set; }
+
+ }
+}
diff --git a/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs b/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs
index 866794e6d..a947408b8 100644
--- a/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs
+++ b/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs
@@ -1,14 +1,13 @@
-namespace AccountGoWeb.Models.Financial
+namespace AccountGoWeb.Models.Financial;
+
+public class GeneralLedgerSetting
{
- public class GeneralLedgerSetting
- {
- public int Id { get; set; }
- public int? CompanyId { get; set; }
- public string CompanyCode { get; set; }
- public int? PayableAccountId { get; set; }
- public int? PurchaseDiscountAccountId { get; set; }
- public int? GoodsReceiptNoteClearingAccountId { get; set; }
- public int? SalesDiscountAccountId { get; set; }
- public int? ShippingChargeAccountId { get; set; }
- }
+ public int Id { get; set; }
+ public int? CompanyId { get; set; }
+ public string? CompanyCode { get; set; }
+ public int? PayableAccountId { get; set; }
+ public int? PurchaseDiscountAccountId { get; set; }
+ public int? GoodsReceiptNoteClearingAccountId { get; set; }
+ public int? SalesDiscountAccountId { get; set; }
+ public int? ShippingChargeAccountId { get; set; }
}
diff --git a/src/AccountGoWeb/Models/FinancialReports.cs b/src/AccountGoWeb/Models/FinancialReports.cs
index 7e755e404..8c34a7d9e 100644
--- a/src/AccountGoWeb/Models/FinancialReports.cs
+++ b/src/AccountGoWeb/Models/FinancialReports.cs
@@ -1,53 +1,42 @@
-//-----------------------------------------------------------------------
-//
-// Copyright (c) AccountGo. All rights reserved.
-// Marvin Perez
-// 1/11/2015 9:48:38 AM
-//
-//-----------------------------------------------------------------------
+namespace AccountGoWeb.Models;
-using System;
-
-namespace AccountGoWeb.Models
+public class TrialBalance
{
- public class TrialBalance
- {
- public int AccountId { get; set; }
- public string AccountCode { get; set; }
- public string AccountName { get; set; }
- public decimal Debit { get; set; }
- public decimal Credit { get; set; }
- }
+ public int AccountId { get; set; }
+ public string? AccountCode { get; set; }
+ public string? AccountName { get; set; }
+ public decimal Debit { get; set; }
+ public decimal Credit { get; set; }
+}
- public class BalanceSheet
- {
- public int AccountId { get; set; }
- public int AccountClassId { get; set; }
- public string AccountCode { get; set; }
- public string AccountName { get; set; }
- public decimal Amount { get; set; }
- }
+public class BalanceSheet
+{
+ public int AccountId { get; set; }
+ public int AccountClassId { get; set; }
+ public string? AccountCode { get; set; }
+ public string? AccountName { get; set; }
+ public decimal Amount { get; set; }
+}
- public class IncomeStatement
- {
- public int AccountId { get; set; }
- public bool IsExpense { get; set; }
- public string AccountCode { get; set; }
- public string AccountName { get; set; }
- public decimal Amount { get; set; }
- }
+public class IncomeStatement
+{
+ public int AccountId { get; set; }
+ public bool IsExpense { get; set; }
+ public string? AccountCode { get; set; }
+ public string? AccountName { get; set; }
+ public decimal Amount { get; set; }
+}
- public partial class MasterGeneralLedger
- {
- public int Id { get; set; }
- public int AccountId { get; set; }
- public int CurrencyId { get; set; }
- public string DocumentType { get; set; }
- public int TransactionNo { get; set; }
- public string AccountCode { get; set; }
- public string AccountName { get; set; }
- public DateTime Date { get; set; }
- public decimal Debit { get; set; }
- public decimal Credit { get; set; }
- }
+public partial class MasterGeneralLedger
+{
+ public int Id { get; set; }
+ public int AccountId { get; set; }
+ public int CurrencyId { get; set; }
+ public string? DocumentType { get; set; }
+ public int TransactionNo { get; set; }
+ public string? AccountCode { get; set; }
+ public string? AccountName { get; set; }
+ public DateTime Date { get; set; }
+ public decimal Debit { get; set; }
+ public decimal Credit { get; set; }
}
diff --git a/src/AccountGoWeb/Models/ObjectExtensions.cs b/src/AccountGoWeb/Models/ObjectExtensions.cs
index 7e3d8c24b..780d99dfb 100644
--- a/src/AccountGoWeb/Models/ObjectExtensions.cs
+++ b/src/AccountGoWeb/Models/ObjectExtensions.cs
@@ -1,16 +1,14 @@
using Newtonsoft.Json;
-using System.IO;
-namespace AccountGoWeb.Models
+namespace AccountGoWeb.Models;
+
+public static class ObjectExtensions
{
- public static class ObjectExtensions
+ public static string ToJson(this object obj)
{
- public static string ToJson(this object obj)
- {
- JsonSerializer js = JsonSerializer.Create(new JsonSerializerSettings());
- var jw = new StringWriter();
- js.Serialize(jw, obj);
- return jw.ToString();
- }
+ JsonSerializer js = JsonSerializer.Create(new JsonSerializerSettings());
+ var jw = new StringWriter();
+ js.Serialize(jw, obj);
+ return jw.ToString();
}
}
diff --git a/src/AccountGoWeb/Models/Purchasing/Payment.cs b/src/AccountGoWeb/Models/Purchasing/Payment.cs
index 58c9f230a..16aae98e5 100644
--- a/src/AccountGoWeb/Models/Purchasing/Payment.cs
+++ b/src/AccountGoWeb/Models/Purchasing/Payment.cs
@@ -1,19 +1,18 @@
-namespace AccountGoWeb.Models.Purchasing
+namespace AccountGoWeb.Models.Purchasing;
+
+public class Payment
{
- public class Payment
- {
- public int InvoiceId { get; set; }
- public string InvoiceNo { get; set; }
- public int VendorId { get; set; }
- public string VendorName { get; set; }
- public decimal InvoiceAmount { get; set; }
- public decimal AmountPaid { get; set; }
- public decimal Balance { get { return InvoiceAmount - AmountPaid; } }
- [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay <= Balance", ErrorMessage = "Amount to pay cannot be greater than remaining amount to pay.")]
- [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay > 0", ErrorMessage = "Amount to pay cannot be zero.")]
- public decimal AmountToPay { get; set; }
- [System.ComponentModel.DataAnnotations.Required]
- public int? AccountId { get; set; }
- public System.DateTime Date { get; set; }
- }
+ public int InvoiceId { get; set; }
+ public string? InvoiceNo { get; set; }
+ public int VendorId { get; set; }
+ public string? VendorName { get; set; }
+ public decimal InvoiceAmount { get; set; }
+ public decimal AmountPaid { get; set; }
+ public decimal Balance { get { return InvoiceAmount - AmountPaid; } }
+ [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay <= Balance", ErrorMessage = "Amount to pay cannot be greater than remaining amount to pay.")]
+ [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay > 0", ErrorMessage = "Amount to pay cannot be zero.")]
+ public decimal AmountToPay { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? AccountId { get; set; }
+ public System.DateTime Date { get; set; }
}
diff --git a/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs b/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs
index 67f10e309..377a9ecdb 100644
--- a/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs
+++ b/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs
@@ -1,20 +1,19 @@
-namespace AccountGoWeb.Models.Sales
+namespace AccountGoWeb.Models.Sales;
+
+public class AddReceipt
{
- public class AddReceipt
- {
- [System.ComponentModel.DataAnnotations.Required]
- public int? AccountToDebitId { get; set; }
- [System.ComponentModel.DataAnnotations.Required]
- public int? AccountToCreditId { get; set; }
- [System.ComponentModel.DataAnnotations.Required]
- public int? CustomerId { get; set; }
- public System.DateTime ReceiptDate {get;set;}
- [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")]
- public decimal Amount { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? AccountToDebitId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? AccountToCreditId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? CustomerId { get; set; }
+ public System.DateTime ReceiptDate {get;set;}
+ [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")]
+ public decimal Amount { get; set; }
- public AddReceipt()
- {
- ReceiptDate = System.DateTime.Now;
- }
+ public AddReceipt()
+ {
+ ReceiptDate = System.DateTime.Now;
}
}
diff --git a/src/AccountGoWeb/Models/Sales/Allocate.cs b/src/AccountGoWeb/Models/Sales/Allocate.cs
index 78da38d73..704ead295 100644
--- a/src/AccountGoWeb/Models/Sales/Allocate.cs
+++ b/src/AccountGoWeb/Models/Sales/Allocate.cs
@@ -1,51 +1,48 @@
-using System.Collections.Generic;
+namespace AccountGoWeb.Models.Sales;
-namespace AccountGoWeb.Models.Sales
+public class Allocate
{
- public class Allocate
- {
- [System.ComponentModel.DataAnnotations.Required]
- public int? CustomerId { get; set; }
- [System.ComponentModel.DataAnnotations.Required]
- public int? ReceiptId { get; set; }
- [System.ComponentModel.DataAnnotations.Required]
- public System.DateTime Date { get; set; }
- public decimal Amount { get; set; }
- public decimal RemainingAmountToAllocate { get; set; }
- public decimal SumAllocatedAmount { get { return ComputeSumToAllocateAmount(); } }
- public IList AllocationLines { get; set; }
-
- public Allocate()
- {
- AllocationLines = new List();
- }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? CustomerId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? ReceiptId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public System.DateTime Date { get; set; }
+ public decimal Amount { get; set; }
+ public decimal RemainingAmountToAllocate { get; set; }
+ public decimal SumAllocatedAmount { get { return ComputeSumToAllocateAmount(); } }
+ public IList AllocationLines { get; set; }
- private decimal ComputeSumToAllocateAmount()
- {
- decimal sum = 0;
+ public Allocate()
+ {
+ AllocationLines = new List();
+ }
- foreach (var line in AllocationLines) {
- sum += line.AmountToAllocate.GetValueOrDefault();
- }
+ private decimal ComputeSumToAllocateAmount()
+ {
+ decimal sum = 0;
- return sum;
+ foreach (var line in AllocationLines) {
+ sum += line.AmountToAllocate.GetValueOrDefault();
}
- public bool IsValid()
- {
- if (RemainingAmountToAllocate < SumAllocatedAmount)
- return false;
- else
- return true;
- }
+ return sum;
}
- public class AllocationLine
+ public bool IsValid()
{
- [System.ComponentModel.DataAnnotations.Required]
- public int? InvoiceId { get; set; }
- public decimal? Amount { get; set; }
- public decimal? AllocatedAmount { get; set; }
- public decimal? AmountToAllocate { get; set; }
- }
+ if (RemainingAmountToAllocate < SumAllocatedAmount)
+ return false;
+ else
+ return true;
+ }
}
+
+public class AllocationLine
+{
+ [System.ComponentModel.DataAnnotations.Required]
+ public int? InvoiceId { get; set; }
+ public decimal? Amount { get; set; }
+ public decimal? AllocatedAmount { get; set; }
+ public decimal? AmountToAllocate { get; set; }
+}
diff --git a/src/AccountGoWeb/Models/Sales/SalesQuotation.cs b/src/AccountGoWeb/Models/Sales/SalesQuotation.cs
new file mode 100644
index 000000000..226716fca
--- /dev/null
+++ b/src/AccountGoWeb/Models/Sales/SalesQuotation.cs
@@ -0,0 +1,21 @@
+namespace AccountGoWeb.Models.Sales;
+public class SalesQuotations {
+ [System.ComponentModel.DataAnnotations.Required]
+ public int CustomerId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int PaymentTermId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int ItemId { get; set; }
+ [System.ComponentModel.DataAnnotations.Required]
+ public int Quantity { get; set; }
+ [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")]
+ public decimal Amount { get; set; }
+ public System.DateTime Date { get; set; }
+ public decimal Discount { get; set; }
+
+ public SalesQuotations()
+ {
+ Date = System.DateTime.Now;
+ }
+
+}
\ No newline at end of file
diff --git a/src/AccountGoWeb/Models/SelectListItemHelper.cs b/src/AccountGoWeb/Models/SelectListItemHelper.cs
index 39b6ccf30..2546a4da1 100644
--- a/src/AccountGoWeb/Models/SelectListItemHelper.cs
+++ b/src/AccountGoWeb/Models/SelectListItemHelper.cs
@@ -1,152 +1,147 @@
-using Microsoft.Extensions.Configuration;
-using System.Collections.Generic;
-using System.Net.Http;
+namespace AccountGoWeb.Models;
-namespace AccountGoWeb.Models
+public static class SelectListItemHelper
{
- public static class SelectListItemHelper
- {
- public static IConfiguration _config;
+ public static IConfiguration? _config;
- public static IEnumerable Accounts()
- {
- var accounts = GetAsync>("common/postingaccounts").Result;
+ public static IEnumerable Accounts()
+ {
+ var accounts = GetAsync>("common/postingaccounts").Result;
- var selectAccounts = new HashSet();
- selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var account in accounts)
- selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = account.Id.ToString(), Text = account.AccountName });
+ var selectAccounts = new HashSet();
+ selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var account in accounts)
+ selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = account.Id.ToString(), Text = account.AccountName });
- return selectAccounts;
- }
+ return selectAccounts;
+ }
- public static IEnumerable TaxGroups()
- {
- var taxGroups = GetAsync>("tax/taxgroups").Result;
- var selectTaxGroups = new HashSet();
- selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var taxGroup in taxGroups)
- selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Description });
+ public static IEnumerable TaxGroups()
+ {
+ var taxGroups = GetAsync>("tax/taxgroups").Result;
+ var selectTaxGroups = new HashSet();
+ selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var taxGroup in taxGroups)
+ selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Description });
- return selectTaxGroups;
- }
+ return selectTaxGroups;
+ }
- public static IEnumerable ItemTaxGroups()
- {
- var itemtaxgroups = GetAsync>("tax/itemtaxgroups").Result;
- var selectitemtaxgroups = new HashSet();
- selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var taxGroup in itemtaxgroups)
- selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Name });
+ public static IEnumerable ItemTaxGroups()
+ {
+ var itemtaxgroups = GetAsync>("tax/itemtaxgroups").Result;
+ var selectitemtaxgroups = new HashSet();
+ selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var taxGroup in itemtaxgroups)
+ selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Name });
- return selectitemtaxgroups;
- }
+ return selectitemtaxgroups;
+ }
- public static IEnumerable PaymentTerms()
- {
- var paymentTerms = GetAsync>("common/paymentterms").Result;
- var selectPaymentTerms = new HashSet();
- selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var term in paymentTerms)
- selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = term.Id.ToString(), Text = term.Description });
+ public static IEnumerable PaymentTerms()
+ {
+ var paymentTerms = GetAsync>("common/paymentterms").Result;
+ var selectPaymentTerms = new HashSet();
+ selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var term in paymentTerms)
+ selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = term.Id.ToString(), Text = term.Description });
- return selectPaymentTerms;
- }
+ return selectPaymentTerms;
+ }
- public static IEnumerable UnitOfMeasurements()
- {
- var uoms = GetAsync>("common/measurements").Result;
- var selectUOMS = new HashSet();
- selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in uoms)
- selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description });
+ public static IEnumerable UnitOfMeasurements()
+ {
+ var uoms = GetAsync>("common/measurements").Result;
+ var selectUOMS = new HashSet();
+ selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in uoms)
+ selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description });
- return selectUOMS;
- }
+ return selectUOMS;
+ }
- public static IEnumerable ItemCategories()
- {
- var categories = GetAsync>("common/itemcategories").Result;
- var selectCategories = new HashSet();
- selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in categories)
- selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
+ public static IEnumerable ItemCategories()
+ {
+ var categories = GetAsync>("common/itemcategories").Result;
+ var selectCategories = new HashSet();
+ selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in categories)
+ selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
- return selectCategories;
- }
+ return selectCategories;
+ }
- public static IEnumerable CashBanks()
- {
- var cashBanks = GetAsync>("common/cashbanks").Result;
- var selectCashBanks = new HashSet();
- selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in cashBanks)
- selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
+ public static IEnumerable CashBanks()
+ {
+ var cashBanks = GetAsync>("common/cashbanks").Result;
+ var selectCashBanks = new HashSet();
+ selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in cashBanks)
+ selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
- return selectCashBanks;
- }
+ return selectCashBanks;
+ }
- public static IEnumerable Customers()
- {
- var customers = GetAsync>("sales/customers").Result;
- var selectCustomers = new HashSet();
- selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in customers)
- selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
+ public static IEnumerable Customers()
+ {
+ var customers = GetAsync>("sales/customers").Result;
+ var selectCustomers = new HashSet();
+ selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in customers)
+ selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
- return selectCustomers;
- }
+ return selectCustomers;
+ }
- public static IEnumerable Vendors()
- {
- var vendors = GetAsync>("purchasing/vendors").Result;
- var selectVendors = new HashSet();
- selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in vendors)
- selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
+ public static IEnumerable Vendors()
+ {
+ var vendors = GetAsync>("purchasing/vendors").Result;
+ var selectVendors = new HashSet();
+ selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in vendors)
+ selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name });
- return selectVendors;
- }
+ return selectVendors;
+ }
- public static IEnumerable Items()
- {
- var items = GetAsync>("inventory/items").Result;
- var selectItems = new HashSet();
- selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in items)
- selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description });
+ public static IEnumerable Items()
+ {
+ var items = GetAsync>("inventory/items").Result;
+ var selectItems = new HashSet();
+ selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in items)
+ selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description });
- return selectItems;
- }
+ return selectItems;
+ }
- public static IEnumerable Measurements()
- {
- var measurements = GetAsync>("inventory/items").Result;
- var selectMeasurements = new HashSet();
- selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
- foreach (var item in measurements)
- selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description });
+ public static IEnumerable Measurements()
+ {
+ var measurements = GetAsync>("common/measurements").Result;
+ var selectMeasurements = new HashSet();
+ selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" });
+ foreach (var item in measurements)
+ selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description });
- return selectMeasurements;
- }
+ return selectMeasurements;
+ }
- #region Private methods
- public static async System.Threading.Tasks.Task GetAsync(string uri)
+ #region Private methods
+ public static async System.Threading.Tasks.Task GetAsync(string uri)
+ {
+ string responseJson = string.Empty;
+ using (var client = new HttpClient())
{
- string responseJson = string.Empty;
- using (var client = new HttpClient())
+ var baseUri = _config!["ApiUrl"];
+ client.BaseAddress = new System.Uri(baseUri!);
+ client.DefaultRequestHeaders.Accept.Clear();
+ var response = await client.GetAsync(baseUri + uri);
+ if (response.IsSuccessStatusCode)
{
- var baseUri = _config["ApiUrl"];
- client.BaseAddress = new System.Uri(baseUri);
- client.DefaultRequestHeaders.Accept.Clear();
- var response = await client.GetAsync(baseUri + uri);
- if (response.IsSuccessStatusCode)
- {
- responseJson = await response.Content.ReadAsStringAsync();
- }
+ responseJson = await response.Content.ReadAsStringAsync();
}
- return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson);
}
- #endregion
+ return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!;
}
+ #endregion
}
diff --git a/src/AccountGoWeb/Models/TaxSystem/BaseViewModel.cs b/src/AccountGoWeb/Models/TaxSystem/BaseViewModel.cs
new file mode 100644
index 000000000..3164ec6b7
--- /dev/null
+++ b/src/AccountGoWeb/Models/TaxSystem/BaseViewModel.cs
@@ -0,0 +1,9 @@
+namespace AccountGoWeb.Models.TaxSystem
+{
+ public abstract class BaseViewModel
+ {
+ public virtual int Id { get; set; }
+ // TODO: Get the user from the logged in user
+ public string? ModifiedBy { get; set; }
+ }
+}
diff --git a/src/AccountGoWeb/Models/TaxSystem/EditTaxViewModel.cs b/src/AccountGoWeb/Models/TaxSystem/EditTaxViewModel.cs
new file mode 100644
index 000000000..68aded1b5
--- /dev/null
+++ b/src/AccountGoWeb/Models/TaxSystem/EditTaxViewModel.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AccountGoWeb.Models.TaxSystem
+{
+ public class EditTaxViewModel
+ {
+ public int SalesAccountId { get; set; }
+ public int PurchaseAccountId { get; set; }
+ public Tax? Tax { get; set; }
+ public TaxGroup? TaxGroup { get; set; }
+ public ItemTaxGroup? ItemTaxGroup { get; set; }
+ }
+}
diff --git a/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroup.cs b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroup.cs
new file mode 100644
index 000000000..a525eb093
--- /dev/null
+++ b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroup.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AccountGoWeb.Models.TaxSystem
+{
+ public class ItemTaxGroup : BaseViewModel
+ {
+ [Required(ErrorMessage = "The Tax Group Name field is Required.")]
+ [StringLength(50)]
+ public string? Name { get; set; }
+
+ [Display(Name = "Fully Exempt")]
+ public bool IsFullyExempt { get; set; }
+ public IList Taxes { get; set; }
+
+ public ItemTaxGroup()
+ {
+ Taxes = new List();
+ }
+ }
+}
diff --git a/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroupTax.cs b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroupTax.cs
new file mode 100644
index 000000000..8ed90b741
--- /dev/null
+++ b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroupTax.cs
@@ -0,0 +1,9 @@
+namespace AccountGoWeb.Models.TaxSystem
+{
+ public class ItemTaxGroupTax
+ {
+ public int TaxId { get; set; }
+ public int ItemTaxGroupId { get; set; }
+ public bool IsExempt { get; set; }
+ }
+}
diff --git a/src/AccountGoWeb/Models/TaxSystem/Tax.cs b/src/AccountGoWeb/Models/TaxSystem/Tax.cs
new file mode 100644
index 000000000..644a6af0c
--- /dev/null
+++ b/src/AccountGoWeb/Models/TaxSystem/Tax.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AccountGoWeb.Models.TaxSystem
+{
+ public class Tax : BaseViewModel
+ {
+ [Required(ErrorMessage = "The Tax Name field is Required.")]
+ [StringLength(50)]
+ public string? TaxName { get; set; }
+
+ [Required(ErrorMessage = "The Tax Code field is Required.")]
+ [StringLength(16)]
+ public string? TaxCode { get; set; }
+
+ [Range(0, 100, ErrorMessage = "The Rate field must be between 0 and 100.")]
+ public decimal Rate { get; set; }
+
+ public bool IsActive { get; set; }
+ }
+}
diff --git a/src/AccountGoWeb/Models/TaxSystem/TaxGroup.cs b/src/AccountGoWeb/Models/TaxSystem/TaxGroup.cs
new file mode 100644
index 000000000..eae959112
--- /dev/null
+++ b/src/AccountGoWeb/Models/TaxSystem/TaxGroup.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace AccountGoWeb.Models.TaxSystem
+{
+ public class TaxGroup : BaseViewModel
+ {
+ [Required(ErrorMessage = "The Tax Group Name field is Required.")]
+ [StringLength(50)]
+ public string? Description { get; set; }
+ public bool TaxAppliedToShipping { get; set; }
+ public bool IsActive { get; set; }
+
+ public IList Taxes { get; set; }
+
+ public TaxGroup()
+ {
+ Taxes = new List