Add per-practice logins, a standalone login test, and pre-commit checks

The ChatGPT transcript showed each CAMBS practice has its own SFTP
account (YSMD is YAM62682 on io.myallylogin.com:14147), not one shared
entity login - so the wizard needed a way to set those without hand-
writing nine practice.json files. Option 5 does that, and the settings
screen now lists which login each practice will actually use.

test-login.bat tests one account on its own, with the details typed in
rather than read from a config, which isolates the account from whatever
a script is doing to the credentials. It reports password length,
stray whitespace, and $ / backtick characters - the last being the usual
reason a login works in the WinSCP window but fails from a script, since
"$pass" interpolates in PowerShell while '$pass' does not. On failure it
prints the authentication methods the server offered.

check.sh runs what has actually caught bugs here: non-ASCII characters,
"$name?" (? is legal in an unbraced variable name, so it renders empty -
this bit twice), parse errors, 5.1 syntax compatibility, and JSON validity.
This commit is contained in:
2026-08-30 20:28:32 -05:00
parent cfa83d91d5
commit e384719ecb
5 changed files with 424 additions and 4 deletions
+121 -3
View File
@@ -323,6 +323,121 @@ function Add-Practices($entityName) {
}
}
function Get-PracticeNames($entityName) {
$export1 = Join-Path (Join-Path $entitiesDir $entityName) "Export1"
if (-not (Test-Path $export1)) { return @() }
return @(Get-ChildItem -Path $export1 -Directory | ForEach-Object { $_.Name })
}
# Same rules engine.ps1 uses: practice.json replaces or adds to entity.json for
# that one practice, so what gets tested here is what will actually be sent.
function Get-MergedPracticeConfig($entityName, $practiceName, $entityConfig) {
$file = Join-Path (Join-Path (Join-Path (Join-Path $entitiesDir $entityName) "Export1") $practiceName) "practice.json"
$merged = $entityConfig | ConvertTo-Json -Depth 10 | ConvertFrom-Json
if (-not (Test-Path $file)) { return $merged }
$override = Get-Content $file -Raw | ConvertFrom-Json
foreach ($section in $override.PSObject.Properties) {
if ($section.Name -like "_*") { continue }
if ($null -eq $merged.PSObject.Properties[$section.Name]) {
$merged | Add-Member -NotePropertyName $section.Name -NotePropertyValue $section.Value
} elseif ($section.Value -is [System.Management.Automation.PSCustomObject]) {
foreach ($prop in $section.Value.PSObject.Properties) {
$merged.$($section.Name) | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
}
} else {
Set-Field $merged $section.Name $section.Value
}
}
return $merged
}
function Set-PracticeLogin($entityName, $entityConfig) {
$practices = Get-PracticeNames $entityName
if ($practices.Count -eq 0) {
Write-Host ""
Write-Host "No practice folders yet - add some first." -ForegroundColor Yellow
return
}
$choice = Read-Choice "Which practice?" $practices 1
$practiceName = $practices[$choice - 1]
$dir = Join-Path (Join-Path (Join-Path $entitiesDir $entityName) "Export1") $practiceName
$file = Join-Path $dir "practice.json"
$existing = $null
if (Test-Path $file) { $existing = Get-Content $file -Raw | ConvertFrom-Json }
Write-Section "$practiceName"
Write-Host "Anything you set here is used for $practiceName only." -ForegroundColor DarkGray
Write-Host "Anything you leave alone still comes from $entityName." -ForegroundColor DarkGray
$ftps = [ordered]@{}
if ($existing -and $existing.ftps) {
foreach ($p in $existing.ftps.PSObject.Properties) { $ftps[$p.Name] = $p.Value }
}
Write-Host ""
if (Read-YesNo "Does $practiceName have its own username and password?" "y") {
Write-Host ""
$ftps["username"] = Read-Answer "Username for $practiceName" $ftps["username"]
$keep = $false
if ($ftps["password"]) {
Write-Host ""
Write-Host "A password is already saved for $practiceName." -ForegroundColor DarkGray
$keep = Read-YesNo "Keep it?" "y"
}
if (-not $keep) { $ftps["password"] = Read-Answer "Password for $practiceName" $null }
}
Write-Host ""
if (Read-YesNo "Does $practiceName use a different server than the rest of ${entityName}?" "n") {
$srv = Read-ServerDetails "$practiceName" $existing.ftps
$ftps["host"] = $srv.host
$ftps["protocol"] = $srv.protocol
$ftps["username"] = $srv.username
$ftps["password"] = $srv.password
}
Write-Host ""
if (Read-YesNo "Does $practiceName use a different folder on the server?" "n") {
$ftps["tiff_path"] = Read-RemotePath "$practiceName's daily image files" $ftps["tiff_path"]
}
if ($ftps.Count -eq 0) {
if (Test-Path $file) {
Remove-Item $file -Force
Write-Host ""
Write-Host "Nothing set - removed $practiceName's practice.json." -ForegroundColor Gray
} else {
Write-Host ""
Write-Host "Nothing set for $practiceName." -ForegroundColor Gray
}
return
}
$out = [ordered]@{
"_note" = "Settings for $practiceName only. Anything not listed comes from ..\..\entity.json."
ftps = [PSCustomObject]$ftps
}
([PSCustomObject]$out) | ConvertTo-Json -Depth 10 | Set-Content -Path $file -Encoding UTF8
Write-Host ""
Write-Host "Saved $file" -ForegroundColor Green
}
function Show-PracticeLogins($entityName, $entityConfig) {
$practices = Get-PracticeNames $entityName
if ($practices.Count -eq 0) { return }
Write-Host ""
Write-Host " Practices" -ForegroundColor White
foreach ($p in $practices) {
$merged = Get-MergedPracticeConfig $entityName $p $entityConfig
$own = Test-Path (Join-Path (Join-Path (Join-Path (Join-Path $entitiesDir $entityName) "Export1") $p) "practice.json")
$tag = if ($own) { "own settings" } else { "uses $entityName's" }
Write-Host (" {0,-12} {1,-18} login: {2}" -f $p, $tag, (Show-Value $merged.ftps.username))
}
}
function Edit-Entity($entityName) {
$config = Get-EntityConfig $entityName
if (-not $config) {
@@ -340,15 +455,17 @@ function Edit-Entity($entityName) {
while ($true) {
Write-Header "$entityName - current settings"
Show-Settings $config
Show-PracticeLogins $entityName $config
$choice = Read-Choice "What do you want to change?" @(
"The daily-upload server (address, protocol, login)",
"Where the daily image files go on the server",
"Where PT STMT files from the dated folder go",
"Patient files (server and folder)",
"Give one practice its own login or folder",
"Add practice folders",
"Nothing - save and go back"
) 6
) 7
switch ($choice) {
1 {
@@ -396,8 +513,9 @@ function Edit-Entity($entityName) {
Write-Host " Patient files turned off for $entityName." -ForegroundColor Gray
}
}
5 { Add-Practices $entityName }
6 {
5 { Set-PracticeLogin $entityName $config }
6 { Add-Practices $entityName }
7 {
$file = Save-EntityConfig $entityName $config
Copy-RunBat $entityName
Write-Host ""