feat: in-app updater button + correct case-folder resolution

Two related improvements:

1. About tab → "בדוק עדכונים" button. Calls ClickOnce's
   ApplicationDeployment.CurrentDeployment.CheckForDetailedUpdate +
   Update on background threads, so the user can pull a new release
   immediately without leaving Outlook — no PowerShell, no
   CleanOnlineAppCache dance.

   Status messages cover the four real outcomes:
     • not deployed via ClickOnce (F5 / dev install) → explicit error
     • on the latest version → green "already up to date"
     • update available → progress text, then "installed — restart
       Outlook"
     • download / manifest / generic exception → red, with reason

2. Case folder resolution now matches the server. The previous client
   logic used Case.name (the lawsuit title, e.g. "מונאס אריאל נ' ביטוח
   לאומי") which produced "47-מונאס אריאל נ ביטוח לאומי". But
   NetworkStorageIntegration's buildEntityFolderName uses the primary
   contact's name from Case.contactsIds[0] → server actually creates
   "47-אריאל מונאס" for the same case, so the two diverged and our
   uploads went to a phantom folder.

   New flow on case selection:
     a) EnsureCaseSubfoldersAsync (creates server's canonical folders)
     b) GetCase with select=contactsIds + networkStorageFolderPath
     c) GetContactAsync on contactsIds[0] → grab Name
     d) ResolveCaseFolderPath(case, contactName) → exact match
     e) Cache result on vm.ResolvedCaseFolderPath; AddInHost reads it
        instead of recomputing.

   ResolveCaseFolderPath fallback order is now (1) the integration's
   stored path, (2) <number>-<primary contact name>, (3) <number>-<case
   name>, (4) the contact / case name alone — matching the server's
   buildEntityFolderName logic step by step.

Tests: 39 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
PointStar
2026-05-12 23:08:20 +03:00
parent 1788b5bbee
commit be5edc203c
4 changed files with 137 additions and 20 deletions
@@ -40,6 +40,15 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
[ObservableProperty]
private Brush statusBrush = NeutralBrush;
[ObservableProperty]
private string? updateStatus;
[ObservableProperty]
private Brush updateStatusBrush = NeutralBrush;
[ObservableProperty]
private bool isCheckingForUpdate;
public string InitialApiKey { get; }
public string VersionLine { get; }
@@ -129,6 +138,66 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
private static string WatchedKey(string storeId, string path) => storeId + "|" + path;
[RelayCommand(CanExecute = nameof(CanCheckForUpdate))]
private async Task CheckForUpdateAsync()
{
IsCheckingForUpdate = true;
UpdateStatusBrush = NeutralBrush;
UpdateStatus = "בודק עדכונים…";
try
{
if (!System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
{
UpdateStatusBrush = ErrorBrush;
UpdateStatus = "התוסף לא מותקן דרך ClickOnce — אין כאן עדכון אוטומטי.";
return;
}
var deployment = System.Deployment.Application.ApplicationDeployment.CurrentDeployment;
var info = await Task.Run(() => deployment.CheckForDetailedUpdate(false)).ConfigureAwait(true);
if (!info.UpdateAvailable)
{
UpdateStatusBrush = SuccessBrush;
UpdateStatus = "אתה על הגרסה האחרונה (" + deployment.CurrentVersion + ").";
return;
}
UpdateStatus = $"זמינה גרסה {info.AvailableVersion} — מוריד…";
var result = await Task.Run(() => deployment.Update()).ConfigureAwait(true);
UpdateStatusBrush = SuccessBrush;
UpdateStatus = $"הותקנה גרסה {info.AvailableVersion}. סגור ופתח את Outlook כדי לטעון אותה.";
}
catch (System.Deployment.Application.DeploymentDownloadException ex)
{
_logger.Warning(ex, "CheckForUpdate: download failed");
UpdateStatusBrush = ErrorBrush;
UpdateStatus = "הורדת העדכון נכשלה: " + ex.Message;
}
catch (System.Deployment.Application.InvalidDeploymentException ex)
{
_logger.Warning(ex, "CheckForUpdate: invalid deployment manifest");
UpdateStatusBrush = ErrorBrush;
UpdateStatus = "ה-deployment manifest על השרת לא תקין: " + ex.Message;
}
catch (Exception ex)
{
_logger.Warning(ex, "CheckForUpdate failed");
UpdateStatusBrush = ErrorBrush;
UpdateStatus = "שגיאה בבדיקת עדכונים: " + ex.Message;
}
finally
{
IsCheckingForUpdate = false;
}
}
private bool CanCheckForUpdate() => !IsCheckingForUpdate;
partial void OnIsCheckingForUpdateChanged(bool value)
{
CheckForUpdateCommand.NotifyCanExecuteChanged();
}
[RelayCommand]
private async Task TestConnectionAsync()
{