Commit Graph

4 Commits

Author SHA1 Message Date
PointStar b6624a924f feat(save-attachments): show case#, court#, status in candidate picker
Replace the single-line ListBox with a DataGrid (case number, name,
court case number, status) so users disambiguate same-named cases
(e.g. two "שלומוב זויה" cases). Hydrates each search hit via
GET /Case/{id}?select=id,name,number,status,cCourtCaseNumber in
parallel so columns populate without an extra click. CourtCaseNumber
is mapped to the EspoCRM custom field cCourtCaseNumber.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:51:15 +03:00
PointStar 5242abb585 feat(attachments): rewire to Marcus-Law NetworkStorageIntegration
After reading the NetworkStorageIntegration repo I realized the previous
"save attachments" flow was wrong on every level — Marcus-Law doesn't
use EspoCRM's stock Document/DocumentFolder model. Folders are real
filesystem paths under the network share, named after the Case (format
"<number>-<primary contact name>") with four standard subfolders:
מסמכים, אסמכתאות, התכתבויות, כללי.

Rewire the save pipeline against the integration's real endpoints
(documented in routes.json on the server repo):

Core — three new IEspoCrmClient methods:
- EnsureCaseSubfoldersAsync(caseId)
    POST /NetworkStorage/action/createSubfolders
    Idempotent — creates case folder + defaults if missing.
- ListNetworkStorageFolderAsync(path)
    GET /NetworkStorage/folderContents?path=…
    Returns folders + files; we filter to type=folder for the dropdown.
- UploadAttachmentToNetworkStorageAsync(attachmentId, targetPath)
    POST /NetworkStorage/action/upload
    Moves the staged Attachment binary onto the share at <path>/<name>.

CaseEntity gets a new `networkStorageFolderPath` field (set by the
integration's hook on first Document save; empty until then).

NetworkStorageItem DTO captures the folder-listing rows.

AttachmentSaveService rewritten — no more Document creation, no more
DocumentFolder picker. The new contract is:
    SaveAsync(attachments, targetFolderPath)
Where targetFolderPath = "<case folder>[/subfolder]". For each
attachment: upload binary via standard /Attachment, then move via
NetworkStorage upload.

UI — SaveAttachmentsViewModel:
- Subfolder dropdown switched from DocumentFolder objects to strings.
- Static defaults loaded up-front:
    "(שורש התיק)" + 4 server-side defaults.
- When the user picks a case:
    1) hydrate Case (number + networkStorageFolderPath) via GetCase
    2) call EnsureCaseSubfoldersAsync to materialise standard folders
    3) listFolder at the case path to merge any custom subfolders into
       the dropdown.
- ResolveCaseFolderPath: use Case.networkStorageFolderPath if set,
  otherwise compute "<number>-<contact-name>" with a client-side
  SanitizeFolderSegment that mirrors LocalFilesystemClient::sanitizeFileName
  on the server (geresh/gershayim/quotes removed, illegal chars → hyphen).

AddInHost.LaunchSaveAttachmentsAsync:
- Composes <caseFolder>[/subfolder] from the VM choice.
- Pre-flight ensureCaseSubfolders as a safety net (in case the user
  submitted before the dialog's lazy refresh completed).
- Summary MessageBox now shows the resolved storage path instead of a
  bare entity name.

Tests: 39 passing. The build is clean across Core / UI / Tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:42:22 +03:00
PointStar 414f71706a fix: restore PNG embed entries + handle numeric Case.number from EspoCRM
Two issues surfaced from the addin log:

1) The four Klear ribbon PNGs were not in the assembly manifest
   (Available resources listed only the XMLs and Properties.Resources).
   The EmbeddedResource entries for Images got dropped from
   OutlookAddin.csproj during a previous rebase. Re-add them in the same
   spot they had before.

2) MatchingService failed for one of the contacts with:
     System.Text.Json.JsonException at $.list[0].number
     Cannot get the value of a token type Number as a string.
   EspoCRM emits Case.number as a JSON number on installs that use
   auto-increment, and as a string when a custom case-code format is
   configured. Add a small FlexibleStringConverter that reads either
   token type into the same string field, and attach it to
   CaseEntity.Number.

Tests: 39 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:57:15 +03:00
PointStar f563dd17dd feat(core): implement EspoCrmClient, DPAPI store, settings, logger (Task #2)
- EspoCrmClient: typed REST wrapper with Polly v8 pipeline
  (Timeout 15s -> Retry 3 exp+jitter -> CircuitBreaker 0.5/5/30s).
  Auth via Espo-Authorization header (base64 username:apiKey).
  Treats 409 on /MailRouter/file as success (already-filed dedup),
  401/403 raise EspoCrmAuthorizationException for re-auth UI.
- DTOs in Models/ matching MailRouter v1.2.0 contract from
  PROJECT-BRIEFING.md (FileEmailRequest now includes replyTo + isHtml,
  attachments use contentType+contentBase64).
- DpapiCredentialStore (Security/): ProtectedData.Protect with
  CurrentUser scope, SHA256("MarcusLaw.OutlookAddin.v1") entropy.
- SettingsManager + AppSettings + WatchedFolder (Configuration/):
  System.Text.Json with UnsafeRelaxedJsonEscaping for Hebrew strings,
  UTF-8 without BOM, recovers to defaults on missing/corrupt file.
- LoggerFactory (Logging/): Serilog daily-rolling File sink with
  14-day retention.

24 xUnit tests covering happy/4xx/5xx/auth/Hebrew round-trips all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:27:05 +03:00