Compare commits

..

7 Commits

Author SHA1 Message Date
spouliot bc9de38da3 Fix Cash account subtype missing from debit-normal balance check
AccountSubType.Cash was not included in IsNormalDebitBalance in both
AccountBalanceService and LedgerService, causing Cash accounts to be
treated as credit-normal. Payments deposited to a Cash account were
debited in the wrong direction, producing a negative balance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:13:24 -04:00
spouliot 2694863d07 Fix health check URL to use production custom domain
Jenkins agent cannot resolve linuxpcl.azurewebsites.net due to DNS
restrictions on the build machine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:08:30 -04:00
spouliot 8646fa83c8 Fix PowerShell syntax error in zip creation step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:56:12 -04:00
spouliot 796d084ea6 Fix zip entry paths to use forward slashes for Linux compatibility
ZipFile.CreateFromDirectory on Windows produces backslash paths in zip
entries. Linux treats backslash as a literal filename character so
wwwroot\css\site.css is never found at wwwroot/css/site.css. Build the
zip manually with Replace backslash→forward slash on each entry name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:46:24 -04:00
spouliot 6d23c63912 Stop app before deploy to prevent rsync file lock failures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:11:04 -04:00
spouliot 3803d16731 Fix prod Jenkins pipeline: add tests, restart, and health check stage
- Add Test stage (unit tests only) before migrations so bad code fails fast
- Add az webapp restart after async deploy to ensure new code is loaded
- Add Health Check stage that polls app for up to 3 minutes post-deploy
- Restore xcopy of wwwroot (needed for linux-x64 publish on Windows)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 21:44:51 -04:00
spouliot 29fd7163dc Merge dev into master: billing email, SMS consent, incoming powder, invoice/job fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 21:13:54 -04:00
3 changed files with 53 additions and 3 deletions
Vendored
+49 -1
View File
@@ -24,6 +24,17 @@ pipeline {
} }
} }
stage('Test') {
steps {
bat 'dotnet test tests\\PowderCoating.UnitTests --no-build -c Release --logger "trx;LogFileName=results.trx" --results-directory TestResults'
}
post {
always {
junit testResults: 'TestResults/*.trx', allowEmptyResults: true
}
}
}
stage('Run Migrations') { stage('Run Migrations') {
steps { steps {
bat 'dotnet tool install --global dotnet-ef 2>nul || dotnet tool update --global dotnet-ef 2>nul' bat 'dotnet tool install --global dotnet-ef 2>nul || dotnet tool update --global dotnet-ef 2>nul'
@@ -42,7 +53,18 @@ pipeline {
stage('Deploy to Azure') { stage('Deploy to Azure') {
steps { steps {
bat 'powershell -Command "Add-Type -Assembly System.IO.Compression.FileSystem; if (Test-Path deploy.zip) { Remove-Item deploy.zip }; [System.IO.Compression.ZipFile]::CreateFromDirectory(\'publish\', \'deploy.zip\')"' powershell '''
Add-Type -Assembly System.IO.Compression.FileSystem
if (Test-Path deploy.zip) { Remove-Item deploy.zip }
$publishDir = (Resolve-Path "publish").Path
$zip = [System.IO.Compression.ZipFile]::Open("deploy.zip", "Create")
Get-ChildItem -Path $publishDir -Recurse -File | ForEach-Object {
$entryName = $_.FullName.Substring($publishDir.Length + 1).Replace("\\", "/")
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, $entryName, "Optimal") | Out-Null
}
$zip.Dispose()
Write-Host "deploy.zip created with forward-slash entry paths"
'''
withCredentials([azureServicePrincipal( withCredentials([azureServicePrincipal(
credentialsId: 'azure-pcl', credentialsId: 'azure-pcl',
subscriptionIdVariable: 'AZ_SUB_ID', subscriptionIdVariable: 'AZ_SUB_ID',
@@ -57,6 +79,32 @@ pipeline {
} }
} }
} }
stage('Health Check') {
steps {
powershell '''
$url = "https://app.powdercoatinglogix.com/"
$timeout = 180
$elapsed = 0
Write-Host "Polling $url for up to $timeout seconds..."
do {
Start-Sleep -Seconds 10
$elapsed += 10
try {
$r = Invoke-WebRequest $url -UseBasicParsing -TimeoutSec 10
if ($r.StatusCode -lt 400) {
Write-Host "App responded HTTP $($r.StatusCode) after ${elapsed}s"
exit 0
}
} catch {
Write-Host "[${elapsed}s] Not yet responding: $_"
}
} while ($elapsed -lt $timeout)
Write-Error "App did not come healthy within $timeout seconds"
exit 1
'''
}
}
} }
post { post {
@@ -117,7 +117,8 @@ public class AccountBalanceService : IAccountBalanceService
/// </summary> /// </summary>
private static bool IsNormalDebitBalance(AccountSubType subType) => subType switch private static bool IsNormalDebitBalance(AccountSubType subType) => subType switch
{ {
AccountSubType.Checking AccountSubType.Cash
or AccountSubType.Checking
or AccountSubType.Savings or AccountSubType.Savings
or AccountSubType.AccountsReceivable or AccountSubType.AccountsReceivable
or AccountSubType.Inventory or AccountSubType.Inventory
@@ -349,7 +349,8 @@ public class LedgerService : ILedgerService
private static bool IsNormalDebitBalance(AccountSubType subType) => subType switch private static bool IsNormalDebitBalance(AccountSubType subType) => subType switch
{ {
// Asset subtypes → normal debit balance // Asset subtypes → normal debit balance
AccountSubType.Checking AccountSubType.Cash
or AccountSubType.Checking
or AccountSubType.Savings or AccountSubType.Savings
or AccountSubType.AccountsReceivable or AccountSubType.AccountsReceivable
or AccountSubType.Inventory or AccountSubType.Inventory