fix(documents): set publishDate on Document create (Marcus-Law requires it)

The server returned:
  400 {"messageTranslation":{"label":"validationFailure",
       "data":{"field":"publishDate","type":"required"}}}

Marcus-Law's EspoCRM install marks Document.publishDate as required.
By default the field is optional, but this tenant tightened it. Send
today's date (UTC, YYYY-MM-DD form) so the validation passes — the
user can still edit the published date in EspoCRM later if needed.

Also add the same verbose-error pattern to CreateDocumentAsync that we
already use on UploadAttachmentAsync: when the create returns non-2xx,
log the response body + X-Status-Reason header so any future required-
field mismatch shows up as a single readable line instead of
"שמירת הקבצים נכשלה".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
PointStar
2026-05-12 21:03:16 +03:00
parent 7ee9a00fc4
commit 7924cda520
@@ -262,21 +262,47 @@ namespace MarcusLaw.OutlookAddin.Core.Services
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name required", nameof(name)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name required", nameof(name));
if (string.IsNullOrWhiteSpace(attachmentFileId)) throw new ArgumentException("attachmentFileId required", nameof(attachmentFileId)); if (string.IsNullOrWhiteSpace(attachmentFileId)) throw new ArgumentException("attachmentFileId required", nameof(attachmentFileId));
// Marcus-Law's EspoCRM has `publishDate` configured as required on
// Document. The field accepts a plain date (YYYY-MM-DD). Default
// to today; the user can edit it later in EspoCRM if needed.
var todayIso = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
var payload = new Dictionary<string, object?> var payload = new Dictionary<string, object?>
{ {
["name"] = name, ["name"] = name,
["fileId"] = attachmentFileId, ["fileId"] = attachmentFileId,
["status"] = "Active" ["status"] = "Active",
["publishDate"] = todayIso
}; };
if (!string.IsNullOrWhiteSpace(parentType)) payload["parentType"] = parentType; if (!string.IsNullOrWhiteSpace(parentType)) payload["parentType"] = parentType;
if (!string.IsNullOrWhiteSpace(parentId)) payload["parentId"] = parentId; if (!string.IsNullOrWhiteSpace(parentId)) payload["parentId"] = parentId;
if (!string.IsNullOrWhiteSpace(folderId)) payload["folderId"] = folderId; if (!string.IsNullOrWhiteSpace(folderId)) payload["folderId"] = folderId;
var json = JsonSerializer.Serialize(payload, JsonOptions); var json = JsonSerializer.Serialize(payload, JsonOptions);
_logger.Information(
"CreateDocument POST /Document (name={Name}, fileId={FileId}, parent={ParentType}/{ParentId}, folder={FolderId})",
name, attachmentFileId, parentType, parentId, folderId ?? "(none)");
var response = await ExecuteAsync(HttpMethod.Post, "Document", json, cancellationToken).ConfigureAwait(false); var response = await ExecuteAsync(HttpMethod.Post, "Document", json, cancellationToken).ConfigureAwait(false);
try try
{ {
await EnsureSuccessAsync(response).ConfigureAwait(false); if (!response.IsSuccessStatusCode)
{
var errBody = await ReadBodyAsync(response).ConfigureAwait(false);
var reason = response.Headers.TryGetValues("X-Status-Reason", out var v)
? string.Join(",", v) : "(none)";
_logger.Warning(
"CreateDocument failed: {Status} (X-Status-Reason={Reason}) body={Body}",
response.StatusCode, reason, string.IsNullOrEmpty(errBody) ? "(empty)" : Truncate(errBody, 500));
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
{
throw new EspoCrmAuthorizationException(
$"EspoCRM rejected credentials ({(int)response.StatusCode} {response.StatusCode}). Re-enter API key.");
}
throw new EspoCrmHttpException(response.StatusCode, errBody,
$"EspoCRM Document create returned {(int)response.StatusCode} {response.StatusCode}" +
(string.IsNullOrEmpty(reason) ? "" : " (reason: " + reason + ")"));
}
var body = await ReadBodyAsync(response).ConfigureAwait(false); var body = await ReadBodyAsync(response).ConfigureAwait(false);
return JsonSerializer.Deserialize<DocumentEntity>(body, JsonOptions) return JsonSerializer.Deserialize<DocumentEntity>(body, JsonOptions)
?? throw new EspoCrmHttpException(response.StatusCode, body, "Empty DocumentEntity from EspoCRM"); ?? throw new EspoCrmHttpException(response.StatusCode, body, "Empty DocumentEntity from EspoCRM");