- /fedex: batch create, document upload/replace/delete, save shipment, imported list
- /api/orders, /api/invoices, /api/pb-file, /api/pb/refresh-token, /api/stock-codes
- /api/payments/bundle: generate + download-all remittance bundles
- shared app/components/DocCell.tsx and app/lib/{invoice-docs,pb-fetch,pb-token}.ts
- recon: invoice reconciliation reports and PowerShell fix-plan scripts
- .gitignore: exclude one-off scripts/_* debugging artefacts, recon/pdfs+txts, data backups
178 lines
No EOL
7.5 KiB
PowerShell
178 lines
No EOL
7.5 KiB
PowerShell
# JEI Line Fix APPLY (destructive!)
|
|
# Reads recon\fix_plan.json (produced by fix_plan_dryrun.ps1) and:
|
|
# - DELETEs each record marked for deletion
|
|
# - CREATEs each new line for missing lines
|
|
# Plus:
|
|
# - Uploads the attached 165709.pdf to replace the 600-byte stub
|
|
#
|
|
# Usage: powershell -ExecutionPolicy Bypass -File recon\fix_plan_apply.ps1
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$pbUrl = 'https://pb.agent.apphub.co.za'
|
|
$envPath = 'C:\Users\SJ\.minimax-agent\projects\JEI\.env.local'
|
|
$reconDir = 'C:\Users\SJ\.minimax-agent\projects\JEI\recon'
|
|
$attachedPdf = 'C:\Users\SJ\.minimax\v2\assets\2026\10\12\13-32-47-717-asset_20261012-133247-717_2646651ee030_f6d94428-InV CI877409722884.pdf'
|
|
|
|
$token = (Get-Content $envPath | Select-String -Pattern '^PB_TOKEN=' | ForEach-Object { $_ -replace '^PB_TOKEN=','' }).Trim()
|
|
$headers = @{ Authorization = "Bearer $token" }
|
|
|
|
# Cache stock codes for quick lookup
|
|
$codeIdCache = @{}
|
|
function Get-StockId($code) {
|
|
if ($codeIdCache.ContainsKey($code)) { return $codeIdCache[$code] }
|
|
$r = Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_stock_codes/records?filter=code=`"$code`"&fields=id" -Headers $headers -Method Get
|
|
if ($r.items.Count -eq 0) {
|
|
Write-Host " [WARN] stock code '$code' not found in master"
|
|
return $null
|
|
}
|
|
$codeIdCache[$code] = $r.items[0].id
|
|
return $r.items[0].id
|
|
}
|
|
|
|
# ----------------------------------------------------------------------
|
|
# 1. Replace 165709 stub PDF with the attached real PDF
|
|
# ----------------------------------------------------------------------
|
|
Write-Host "==== 1. Replacing 165709 stub PDF ===="
|
|
$inv165709 = Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_invoices/records?filter=invoice_no=`"165709`"&fields=id,pdf_file" -Headers $headers -Method Get
|
|
$recId = $inv165709.items[0].id
|
|
Write-Host " record id = $recId"
|
|
Write-Host " old pdf_file = $($inv165709.items[0].pdf_file)"
|
|
|
|
# PB file upload uses multipart/form-data. PowerShell 5.1 lacks -Form on Invoke-RestMethod,
|
|
# so use curl.exe which handles multipart correctly.
|
|
# IMPORTANT: field name must match the schema field name ('pdf_file'), not 'file'.
|
|
$tmpHeaders = New-TemporaryFile
|
|
& curl.exe -sS -L -X PATCH -H "Authorization: Bearer $token" -F "pdf_file=@$attachedPdf" "$pbUrl/api/collections/JEI_invoices/records/$recId" -D $tmpHeaders.FullName -o "$reconDir\165709_patch_resp.json"
|
|
$respStatus = Select-String -Path $tmpHeaders.FullName -Pattern '^HTTP' | Select-Object -First 1
|
|
Remove-Item $tmpHeaders.FullName -Force
|
|
Write-Host " HTTP: $($respStatus.Line.Trim())"
|
|
Write-Host " new pdf_file = $(([System.IO.File]::ReadAllText("$reconDir\165709_patch_resp.json") | ConvertFrom-Json).pdf_file)"
|
|
|
|
# ----------------------------------------------------------------------
|
|
# 2. Run delete + add plan from fix_plan.json
|
|
# ----------------------------------------------------------------------
|
|
Write-Host ""
|
|
Write-Host "==== 2. Reading fix_plan.json ===="
|
|
$plan = Get-Content "$reconDir\fix_plan.json" -Raw | ConvertFrom-Json
|
|
|
|
# Group entries by invoice
|
|
$byInvoice = @{}
|
|
foreach ($entry in $plan) {
|
|
$inv = $entry.invoice
|
|
if (-not $byInvoice.ContainsKey($inv)) { $byInvoice[$inv] = @() }
|
|
$byInvoice[$inv] += $entry
|
|
}
|
|
|
|
# Lookup invoice IDs
|
|
$invIdCache = @{}
|
|
function Get-InvId($invNo) {
|
|
if ($invIdCache.ContainsKey($invNo)) { return $invIdCache[$invNo] }
|
|
$r = Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_invoices/records?filter=invoice_no=`"$invNo`"&fields=id" -Headers $headers -Method Get
|
|
$invIdCache[$invNo] = $r.items[0].id
|
|
return $r.items[0].id
|
|
}
|
|
|
|
# Lookup line_no for adding new lines -- use max existing line_no + 1
|
|
function Get-NextLineNo($invId) {
|
|
$r = Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_invoice_lines/records?filter=invoice=`"$invId`"&perPage=500&fields=line_no" -Headers $headers -Method Get
|
|
$maxLine = 0
|
|
foreach ($x in $r.items) {
|
|
if ([int]$x.line_no -gt $maxLine) { $maxLine = [int]$x.line_no }
|
|
}
|
|
return $maxLine + 1
|
|
}
|
|
|
|
# Lookup commodity/country from PDF text by looking at all DB records for that code+qty
|
|
$pdfContextCache = @{}
|
|
function Get-PdfContext($invNo, $code, $qty) {
|
|
$key = "$invNo|$code|$qty"
|
|
if ($pdfContextCache.ContainsKey($key)) { return $pdfContextCache[$key] }
|
|
$txt = ''
|
|
$txtPath = "$reconDir\txts\$invNo.txt"
|
|
if (Test-Path $txtPath) {
|
|
$txt = [System.IO.File]::ReadAllText($txtPath, [System.Text.Encoding]::UTF8)
|
|
}
|
|
# Search for line: <qty> <code> ... <description>... [commodity: ...] [country: ...]
|
|
$re = "(?ms)^\s*$([regex]::Escape([string]$qty))\s+$([regex]::Escape($code))\s+(.+?)(?=^\s*\d+\.\d+\s+[A-Z]|^\s*Deliver To)"
|
|
$m = [regex]::Match($txt, $re)
|
|
$commodity = ''
|
|
$country = ''
|
|
if ($m.Success) {
|
|
$block = $m.Groups[3].Value
|
|
$cm = [regex]::Match($block, 'COMMODITY\s*CODE[:\s]+(\d+)')
|
|
if ($cm.Success) { $commodity = $cm.Groups[1].Value }
|
|
$cy = [regex]::Match($block, 'COUNTRY\s*OF\s*ORIGIN[:\s]+(\w{2,3})')
|
|
if ($cy.Success) { $country = $cy.Groups[1].Value }
|
|
}
|
|
$ctx = [pscustomobject]@{ commodity = $commodity; country = $country }
|
|
$pdfContextCache[$key] = $ctx
|
|
return $ctx
|
|
}
|
|
|
|
$totalDel = 0
|
|
$totalAdd = 0
|
|
|
|
foreach ($invNo in ($byInvoice.Keys | Sort-Object)) {
|
|
$entries = $byInvoice[$invNo]
|
|
$invId = Get-InvId $invNo
|
|
|
|
Write-Host ""
|
|
Write-Host "==== $invNo ===="
|
|
|
|
# Pass A: DELETEs
|
|
foreach ($e in $entries) {
|
|
foreach ($d in $e.deletes) {
|
|
$recId = $d.rec.id
|
|
Write-Host (" DELETE rec={0,-22} line_no={1,3} code={2,-22} qty={3,5} net={4,10} [{5}]" -f $recId, $d.rec.line_no, $d.rec.code, $d.rec.qty, $d.rec.net, $d.reason)
|
|
Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_invoice_lines/records/$recId" -Headers $headers -Method Delete | Out-Null
|
|
$totalDel++
|
|
}
|
|
}
|
|
|
|
# Pass B: ADDs
|
|
if (@($e.adds).Count -gt 0) {
|
|
$nextLine = Get-NextLineNo $invId
|
|
foreach ($a in $e.adds) {
|
|
$code = $a.code
|
|
$qty = [double]$a.qty
|
|
$stockId = Get-StockId $code
|
|
if (-not $stockId) {
|
|
Write-Host (" [SKIP-ADD] code='{0}' not in master" -f $code)
|
|
continue
|
|
}
|
|
$ctx = Get-PdfContext $invNo $code $qty
|
|
# Pull unit_price and net from an existing DB record that matches code+qty (best-effort)
|
|
$body = @{
|
|
invoice = $invId
|
|
line_no = $nextLine
|
|
stock_code = $stockId
|
|
quantity = $qty
|
|
unit_price = 0
|
|
net_amount = 0
|
|
commodity_code = $ctx.commodity
|
|
country_of_origin = $ctx.country
|
|
}
|
|
# Try to find unit_price and net_amount from existing DB record matching code+qty on this invoice
|
|
$existing = Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_invoice_lines/records?filter=invoice=`"$invId`"+AND+stock_code=`"$stockId`"&perPage=10" -Headers $headers -Method Get
|
|
foreach ($ex in $existing.items) {
|
|
if ([double]$ex.quantity -eq $qty) {
|
|
$body.unit_price = [double]$ex.unit_price
|
|
$body.net_amount = [double]$ex.net_amount
|
|
$body.commodity_code = if ($ex.commodity_code) { $ex.commodity_code } else { $ctx.commodity }
|
|
$body.country_of_origin = if ($ex.country_of_origin) { $ex.country_of_origin } else { $ctx.country }
|
|
break
|
|
}
|
|
}
|
|
Write-Host (" ADD line_no={0,3} code={1,-22} qty={2,5} country={3,-3} commodity={4}" -f $nextLine, $code, $qty, $body.country_of_origin, $body.commodity_code)
|
|
$created = Invoke-RestMethod -Uri "$pbUrl/api/collections/JEI_invoice_lines/records" -Headers $headers -Method Post -Body ($body | ConvertTo-Json) -ContentType "application/json"
|
|
$totalAdd++
|
|
$nextLine++
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "==== DONE ===="
|
|
Write-Host ("Total deletes: {0}" -f $totalDel)
|
|
Write-Host ("Total adds: {0}" -f $totalAdd) |