feat(conversations): align toggle status fallback

This commit is contained in:
2026-06-07 10:56:29 +08:00
parent f7515b111d
commit e6c9660659
4 changed files with 62 additions and 9 deletions
+8 -4
View File
@@ -49,11 +49,11 @@ Hermes task landing checklist:
## Current Baseline ## Current Baseline
- Current tracking checkpoint: 2026-06-07 P3.92 automation-rule attachment parity, prepared as `feat(automation): align attachment uploads`. - Current tracking checkpoint: 2026-06-07 P3.93 conversation toggle-status optional status parity, prepared as `feat(conversations): align toggle status fallback`.
- Latest implementation checkpoint: this checkpoint, prepared as `feat(automation): align attachment uploads`. - Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): align toggle status fallback`.
- Latest documentation/tooling checkpoint: this tracker update records Chatwoot automation-rule attachment parity. No route artifacts change. - Latest documentation/tooling checkpoint: this tracker update records Chatwoot conversation toggle-status fallback parity. No route artifacts change.
- Plan landing status: complete for the current known Hermes plans and user-confirmed scope. Future work should update this file directly instead of opening a parallel tracker. - Plan landing status: complete for the current known Hermes plans and user-confirmed scope. Future work should update this file directly instead of opening a parallel tracker.
- Worktree status at this implementation checkpoint: automation rules now share the Chatwoot `AttachmentConcern` contract already used by uploads/macros: `send_attachment` actions validate account uploads, normalize signed upload UUIDs or numeric blob IDs into stored `blob_id` values, serialize `files`, preserve existing actions when update omits `actions`, reject invalid blobs with raw `422`, and execute outgoing attachment messages with persisted attachment rows. This retains P3.91 upload/macro attachment parity, P3.90 SearchAPI filter-surface parity, P3.89 fixed page size, P3.88 global result type scoping, P3.87 article payloads, P3.86 message payloads, P3.85 conversation payloads, P3.84 agent-sender filter parity, and prior checkpoints. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack. - Worktree status at this implementation checkpoint: account conversation `toggle_status` now follows Chatwoot's optional `params[:status]` fallback: empty bodies or omitted status toggle open conversations to resolved and pending/snoozed/resolved conversations to open while retaining the Chatwoot `{ meta: {}, payload: { success, conversation_id, current_status, snoozed_until } }` response. This retains P3.92 automation-rule attachment parity, P3.91 upload/macro attachment parity, P3.90 SearchAPI filter-surface parity, P3.89 fixed page size, P3.88 global result type scoping, P3.87 article payloads, P3.86 message payloads, P3.85 conversation payloads, P3.84 agent-sender filter parity, and prior checkpoints. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack.
- Next executable implementation checkpoint: continue Phase 2/3 drift audit for the next reused-frontend mismatch, or run B12 live smoke when the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack is available. Re-run Phase 6 placeholder audit after future route/smoke changes. - Next executable implementation checkpoint: continue Phase 2/3 drift audit for the next reused-frontend mismatch, or run B12 live smoke when the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack is available. Re-run Phase 6 placeholder audit after future route/smoke changes.
- `go test ./...` passes when run outside the restricted socket sandbox for the latest implementation baseline; the latest docs/tooling checkpoint verified `scripts/parity_frontend_smoke.sh --check` with workspace-local temp/cache dirs after `/tmp` was full. - `go test ./...` passes when run outside the restricted socket sandbox for the latest implementation baseline; the latest docs/tooling checkpoint verified `scripts/parity_frontend_smoke.sh --check` with workspace-local temp/cache dirs after `/tmp` was full.
- Route dump succeeds with `967` registered routes after profile MFA route tracking. - Route dump succeeds with `967` registered routes after profile MFA route tracking.
@@ -156,6 +156,7 @@ This table is the shortest authoritative handoff view. If an older lower section
| Priority | Workstream | Current state | Next checkpoint | Commit close rule | | Priority | Workstream | Current state | Next checkpoint | Commit close rule |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| 0 | P3.93 conversation toggle-status optional status parity | Implemented for reused conversation action paths and direct API compatibility: `POST /conversations/:conversation_id/toggle_status` now accepts empty bodies or `{}` without requiring `status`, then applies Chatwoot `Conversation#toggle_status` fallback semantics. | Keep in Review; reopen from B12 conversation action smoke or fresh reference evidence for bot handoff, assignment side effects, or exact `@status` success value drift beyond the inspected controller/model/Jbuilder contract. | Focused ConversationHandler toggle-status tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| 0 | P3.92 automation-rule attachment parity | Implemented for reused automation settings flows: automation rule create/update validates `send_attachment` uploads, normalizes signed upload UUIDs or numeric IDs into stored blob IDs, serializes Chatwoot-style `files`, preserves existing actions when update omits `actions`, and rule execution creates outgoing attachment messages. | Keep in Review; reopen from automation attachment smoke, fresh ActiveStorage signed-id evidence, or reference changes in automation `AttachmentConcern` semantics. | Focused AutomationRuleHandler and AutomationRuleService tests passed; focused ActionService coverage is exercised through automation execution; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | | 0 | P3.92 automation-rule attachment parity | Implemented for reused automation settings flows: automation rule create/update validates `send_attachment` uploads, normalizes signed upload UUIDs or numeric IDs into stored blob IDs, serializes Chatwoot-style `files`, preserves existing actions when update omits `actions`, and rule execution creates outgoing attachment messages. | Keep in Review; reopen from automation attachment smoke, fresh ActiveStorage signed-id evidence, or reference changes in automation `AttachmentConcern` semantics. | Focused AutomationRuleHandler and AutomationRuleService tests passed; focused ActionService coverage is exercised through automation execution; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| 0 | P3.91 upload and macro attachment parity | Implemented for reused `uploadHelper` and macro settings flows: account upload accepts Chatwoot `attachment` multipart fields plus `external_url` JSON bodies, returns raw blob payloads consumed by the frontend, and macro `send_attachment` actions resolve those blob IDs into `files` payload entries and executable attachment messages. | Keep in Review; reopen from macro/automation attachment smoke, external fetch edge cases, or fresh reference evidence for ActiveStorage signed-id semantics not covered by the current direct-upload bridge. | Focused UploadHandler, MacroHandler, MacroService, and macro execution tests passed, including the external-url test outside the restricted socket sandbox; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | | 0 | P3.91 upload and macro attachment parity | Implemented for reused `uploadHelper` and macro settings flows: account upload accepts Chatwoot `attachment` multipart fields plus `external_url` JSON bodies, returns raw blob payloads consumed by the frontend, and macro `send_attachment` actions resolve those blob IDs into `files` payload entries and executable attachment messages. | Keep in Review; reopen from macro/automation attachment smoke, external fetch edge cases, or fresh reference evidence for ActiveStorage signed-id semantics not covered by the current direct-upload bridge. | Focused UploadHandler, MacroHandler, MacroService, and macro execution tests passed, including the external-url test outside the restricted socket sandbox; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| 0 | P3.90 SearchAPI filter-surface parity | Implemented for reused SearchAPI endpoints: only Chatwoot-supported search filters are allowed through. `since/until` date filters remain available across result families when advanced search is enabled; `from` and `inbox_id` are preserved only for message search; unsupported local filters are stripped before DB fallback and Meilisearch execution. Global search applies message-only filters only to message results, so conversations/contacts/articles are not narrowed by message inbox or sender filters. | Keep in Review; reopen from B12 advanced-search smoke or fresh reference evidence for new upstream filter params, Searchkick advanced-search extensions, or endpoint-specific filter drift. | Focused SearchHandler, SearchService, and Meilisearch filter-surface tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | | 0 | P3.90 SearchAPI filter-surface parity | Implemented for reused SearchAPI endpoints: only Chatwoot-supported search filters are allowed through. `since/until` date filters remain available across result families when advanced search is enabled; `from` and `inbox_id` are preserved only for message search; unsupported local filters are stripped before DB fallback and Meilisearch execution. Global search applies message-only filters only to message results, so conversations/contacts/articles are not narrowed by message inbox or sender filters. | Keep in Review; reopen from B12 advanced-search smoke or fresh reference evidence for new upstream filter params, Searchkick advanced-search extensions, or endpoint-specific filter drift. | Focused SearchHandler, SearchService, and Meilisearch filter-surface tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
@@ -256,6 +257,7 @@ These rows are the executable development plan from this point forward. A checkp
| ID | Owner files | Reference files | Work to land | Exit gate | | ID | Owner files | Reference files | Work to land | Exit gate |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| P3.93 conversation toggle-status optional status parity | `internal/handler/api/v1/conversation_handler.go`, `internal/service/conversation_service.go`, conversation handler/service tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#toggle_status`, `reference/chatwoot/app/models/conversation.rb#toggle_status`, `reference/chatwoot/app/views/api/v1/accounts/conversations/toggle_status.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js`, reused conversation status action components/store | Account conversation `toggle_status` now accepts omitted status/empty request bodies like Chatwoot and falls back to model toggling: open becomes resolved, while pending/snoozed/resolved becomes open. Explicit `status` behavior and the existing Chatwoot response envelope remain intact. | Review by `feat(conversations): align toggle status fallback`; focused tests prove omitted-status empty-body toggle and `{}` pending-to-open fallback; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| P3.92 automation-rule attachment parity | `internal/handler/api/v1/automation_rule_handler.go`, `internal/automation/service.go`, `internal/automation/model.go`, `internal/automation/action_service.go`, automation rule handler/service/action tests | `reference/chatwoot/app/controllers/api/v1/accounts/automation_rules_controller.rb`, `reference/chatwoot/app/controllers/concerns/attachment_concern.rb`, `reference/chatwoot/app/models/automation_rule.rb`, `reference/chatwoot/app/views/api/v1/accounts/automation_rules/partials/_automation_rule.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/store/modules/automations.js`, `reference/chatwoot/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue` | Automation rules now share the Chatwoot attachment contract already implemented for uploads/macros: create/update validate `send_attachment` blobs, normalize signed upload UUIDs or numeric blob IDs to stored upload IDs in `action_params`, serialize rule `files` with `id`, `automation_rule_id`, `file_type`, `account_id`, `file_url`, `blob_id`, and `filename`, preserve existing actions when update omits `actions`, and execution creates outgoing attachment messages with persisted attachment rows. | Review by `feat(automation): align attachment uploads`; focused tests prove create normalization/files serialization, invalid blob `422`, omitted-action update preservation, and automation execution attachment side effects; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | | P3.92 automation-rule attachment parity | `internal/handler/api/v1/automation_rule_handler.go`, `internal/automation/service.go`, `internal/automation/model.go`, `internal/automation/action_service.go`, automation rule handler/service/action tests | `reference/chatwoot/app/controllers/api/v1/accounts/automation_rules_controller.rb`, `reference/chatwoot/app/controllers/concerns/attachment_concern.rb`, `reference/chatwoot/app/models/automation_rule.rb`, `reference/chatwoot/app/views/api/v1/accounts/automation_rules/partials/_automation_rule.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/store/modules/automations.js`, `reference/chatwoot/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue` | Automation rules now share the Chatwoot attachment contract already implemented for uploads/macros: create/update validate `send_attachment` blobs, normalize signed upload UUIDs or numeric blob IDs to stored upload IDs in `action_params`, serialize rule `files` with `id`, `automation_rule_id`, `file_type`, `account_id`, `file_url`, `blob_id`, and `filename`, preserve existing actions when update omits `actions`, and execution creates outgoing attachment messages with persisted attachment rows. | Review by `feat(automation): align attachment uploads`; focused tests prove create normalization/files serialization, invalid blob `422`, omitted-action update preservation, and automation execution attachment side effects; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| P3.91 upload and macro attachment parity | `internal/handler/api/v1/upload_handler.go`, `internal/service/upload_service.go`, `internal/handler/api/v1/macro_handler.go`, `internal/automation/macro_service.go`, `internal/automation/action_service.go`, upload/macro tests | `reference/chatwoot/app/controllers/api/v1/accounts/upload_controller.rb`, `reference/chatwoot/app/controllers/concerns/attachment_concern.rb`, `reference/chatwoot/app/controllers/api/v1/accounts/macros_controller.rb`, `reference/chatwoot/app/models/macro.rb`, `reference/chatwoot/app/services/macros/execution_service.rb`, `reference/chatwoot/app/javascript/dashboard/helper/uploadHelper.js`, macro settings helper/components | Account upload now follows Chatwoot's reused frontend helper contract: multipart field `attachment`, JSON `external_url`, raw `file_url/blob_id/blob_key` response. Macro create/update validates `send_attachment` blob IDs, normalizes signed upload UUIDs to stored blob IDs, serializes `files` with `blob_id`, `filename`, `file_url`, and macro execution creates outgoing messages with persisted attachment rows. | Review by `feat(macros): align attachment uploads`; focused tests prove upload helper payloads, invalid attachment rejection, macro file serialization, durable/synchronous macro execution, and attachment message creation; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | | P3.91 upload and macro attachment parity | `internal/handler/api/v1/upload_handler.go`, `internal/service/upload_service.go`, `internal/handler/api/v1/macro_handler.go`, `internal/automation/macro_service.go`, `internal/automation/action_service.go`, upload/macro tests | `reference/chatwoot/app/controllers/api/v1/accounts/upload_controller.rb`, `reference/chatwoot/app/controllers/concerns/attachment_concern.rb`, `reference/chatwoot/app/controllers/api/v1/accounts/macros_controller.rb`, `reference/chatwoot/app/models/macro.rb`, `reference/chatwoot/app/services/macros/execution_service.rb`, `reference/chatwoot/app/javascript/dashboard/helper/uploadHelper.js`, macro settings helper/components | Account upload now follows Chatwoot's reused frontend helper contract: multipart field `attachment`, JSON `external_url`, raw `file_url/blob_id/blob_key` response. Macro create/update validates `send_attachment` blob IDs, normalizes signed upload UUIDs to stored blob IDs, serializes `files` with `blob_id`, `filename`, `file_url`, and macro execution creates outgoing messages with persisted attachment rows. | Review by `feat(macros): align attachment uploads`; focused tests prove upload helper payloads, invalid attachment rejection, macro file serialization, durable/synchronous macro execution, and attachment message creation; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| P3.90 SearchAPI filter-surface parity | `internal/handler/api/v1/search_handler.go`, `internal/search/search_service.go`, `internal/search/engine_meili.go`, SearchHandler/SearchService/Meilisearch tests | `reference/chatwoot/app/services/search_service.rb`, `reference/chatwoot/app/controllers/api/v1/accounts/search_controller.rb`, `reference/chatwoot/app/javascript/dashboard/api/search.js`, `reference/chatwoot/app/javascript/dashboard/modules/search/components/SearchView.vue`, `reference/chatwoot/app/javascript/dashboard/modules/search/helpers/searchHelper.js` | SearchAPI filters now match the reused dashboard/reference surface: `since/until` are retained as advanced date filters; `from` and `inbox_id` are retained for message search only; local filters not present in Chatwoot `SearchService` (`status`, `priority`, `labels`, `assignee_id`, `team_id`, `message_type`, `content_type`, `private`, `contact_source`, `portal_id`, `article_status`, `locale`) are stripped by the handler. Global search applies message-only filters only to message results, including Meilisearch multi-type searches. | Review by `fix(search): align search filters`; focused tests prove conversation endpoints strip unsupported filters, message endpoints keep only `from/inbox_id`, global service filters per result type, and Meilisearch applies explicit inbox only to messages; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | | P3.90 SearchAPI filter-surface parity | `internal/handler/api/v1/search_handler.go`, `internal/search/search_service.go`, `internal/search/engine_meili.go`, SearchHandler/SearchService/Meilisearch tests | `reference/chatwoot/app/services/search_service.rb`, `reference/chatwoot/app/controllers/api/v1/accounts/search_controller.rb`, `reference/chatwoot/app/javascript/dashboard/api/search.js`, `reference/chatwoot/app/javascript/dashboard/modules/search/components/SearchView.vue`, `reference/chatwoot/app/javascript/dashboard/modules/search/helpers/searchHelper.js` | SearchAPI filters now match the reused dashboard/reference surface: `since/until` are retained as advanced date filters; `from` and `inbox_id` are retained for message search only; local filters not present in Chatwoot `SearchService` (`status`, `priority`, `labels`, `assignee_id`, `team_id`, `message_type`, `content_type`, `private`, `contact_source`, `portal_id`, `article_status`, `locale`) are stripped by the handler. Global search applies message-only filters only to message results, including Meilisearch multi-type searches. | Review by `fix(search): align search filters`; focused tests prove conversation endpoints strip unsupported filters, message endpoints keep only `from/inbox_id`, global service filters per result type, and Meilisearch applies explicit inbox only to messages; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
@@ -372,6 +374,7 @@ This ledger records the committed parity checkpoints that future slices should b
| Commit | Scope | Verification summary | Follow-up state | | Commit | Scope | Verification summary | Follow-up state |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `feat(conversations): align toggle status fallback` | Advances P3.93 with Chatwoot conversation `toggle_status` optional-status parity. The account conversation endpoint now accepts omitted status/empty bodies and uses Chatwoot model fallback toggling while preserving the existing response envelope consumed by the reused dashboard. | Focused ConversationHandler toggle-status tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.93 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
| `feat(automation): align attachment uploads` | Advances P3.92 with Chatwoot automation-rule attachment parity. Automation rule create/update now validates and normalizes `send_attachment` uploads like `AttachmentConcern`, exposes Chatwoot-style `files`, preserves existing actions when update omits `actions`, and execution creates outgoing attachment messages with persisted attachment rows. | Focused AutomationRuleHandler and AutomationRuleService tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.92 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(automation): align attachment uploads` | Advances P3.92 with Chatwoot automation-rule attachment parity. Automation rule create/update now validates and normalizes `send_attachment` uploads like `AttachmentConcern`, exposes Chatwoot-style `files`, preserves existing actions when update omits `actions`, and execution creates outgoing attachment messages with persisted attachment rows. | Focused AutomationRuleHandler and AutomationRuleService tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.92 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
| `docs: land parity tracking handoff` | Documentation-only checkpoint after `168f120 feat(macros): align attachment uploads`. Records the committed baseline SHA, keeps user-confirmed scope and Hermes plan landing rules in this active tracker, and promotes P3.92 automation-rule attachment parity from audit evidence into the next executable slice. | `git diff --check`; no Go code or route artifacts changed. | Start P3.92 automation-rule attachment parity, then continue Phase 2/3 drift audit or B12 live smoke when the full stack is available. | | `docs: land parity tracking handoff` | Documentation-only checkpoint after `168f120 feat(macros): align attachment uploads`. Records the committed baseline SHA, keeps user-confirmed scope and Hermes plan landing rules in this active tracker, and promotes P3.92 automation-rule attachment parity from audit evidence into the next executable slice. | `git diff --check`; no Go code or route artifacts changed. | Start P3.92 automation-rule attachment parity, then continue Phase 2/3 drift audit or B12 live smoke when the full stack is available. |
| `feat(macros): align attachment uploads` | Advances P3.91 with Chatwoot account upload and macro attachment parity. Account upload now accepts the frontend `attachment` form key and `external_url` JSON body, returns raw blob fields, and macro attachment actions validate/normalize those blob IDs, expose Chatwoot-style `files`, and create outgoing attachment messages during execution. | Focused UploadHandler/MacroHandler/MacroService tests passed, including the external-url upload test outside the restricted socket sandbox; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.91 to Review; continue Phase 2/3 drift audit, B9.4 automation attachment depth if fresh smoke exposes it, Phase 6 placeholder audit, or B12 live smoke. | | `feat(macros): align attachment uploads` | Advances P3.91 with Chatwoot account upload and macro attachment parity. Account upload now accepts the frontend `attachment` form key and `external_url` JSON body, returns raw blob fields, and macro attachment actions validate/normalize those blob IDs, expose Chatwoot-style `files`, and create outgoing attachment messages during execution. | Focused UploadHandler/MacroHandler/MacroService tests passed, including the external-url upload test outside the restricted socket sandbox; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.91 to Review; continue Phase 2/3 drift audit, B9.4 automation attachment depth if fresh smoke exposes it, Phase 6 placeholder audit, or B12 live smoke. |
@@ -2750,3 +2753,4 @@ Verification milestone gates:
- 2026-06-07: P3.91 upload and macro attachment checkpoint prepared as `feat(macros): align attachment uploads`; audited Chatwoot `UploadController`, `AttachmentConcern`, `MacrosController`, `Macro#file_base_data`, `Macros::ExecutionService#send_attachment`, and reused dashboard `uploadHelper` plus macro attachment helpers. GoChat account upload now accepts `attachment` multipart fields and `external_url` JSON, returns raw `file_url/blob_id/blob_key`, normalizes macro `send_attachment` actions from upload UUIDs to stored blob IDs, serializes macro `files`, rejects invalid blobs with raw `422`, and creates outgoing attachment messages from macro execution. Focused UploadHandler/MacroHandler/MacroService tests passed, including external-url upload outside the restricted socket sandbox; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. - 2026-06-07: P3.91 upload and macro attachment checkpoint prepared as `feat(macros): align attachment uploads`; audited Chatwoot `UploadController`, `AttachmentConcern`, `MacrosController`, `Macro#file_base_data`, `Macros::ExecutionService#send_attachment`, and reused dashboard `uploadHelper` plus macro attachment helpers. GoChat account upload now accepts `attachment` multipart fields and `external_url` JSON, returns raw `file_url/blob_id/blob_key`, normalizes macro `send_attachment` actions from upload UUIDs to stored blob IDs, serializes macro `files`, rejects invalid blobs with raw `422`, and creates outgoing attachment messages from macro execution. Focused UploadHandler/MacroHandler/MacroService tests passed, including external-url upload outside the restricted socket sandbox; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
- 2026-06-07: Documentation checkpoint prepared as `docs: land parity tracking handoff`; confirms `168f120 feat(macros): align attachment uploads` is committed, records the user-confirmed scope and Hermes plan landing rules in this active tracker, and opens P3.92 automation-rule attachment parity as the next executable slice from fresh Chatwoot `AutomationRulesController`, `AttachmentConcern`, automation model/Jbuilder, and reused automation frontend inspection. Verification for this docs-only checkpoint: `git diff --check`; no Go code or route artifacts changed. - 2026-06-07: Documentation checkpoint prepared as `docs: land parity tracking handoff`; confirms `168f120 feat(macros): align attachment uploads` is committed, records the user-confirmed scope and Hermes plan landing rules in this active tracker, and opens P3.92 automation-rule attachment parity as the next executable slice from fresh Chatwoot `AutomationRulesController`, `AttachmentConcern`, automation model/Jbuilder, and reused automation frontend inspection. Verification for this docs-only checkpoint: `git diff --check`; no Go code or route artifacts changed.
- 2026-06-07: P3.92 automation-rule attachment checkpoint prepared as `feat(automation): align attachment uploads`; audited Chatwoot `AutomationRulesController`, `AttachmentConcern`, `AutomationRule#file_base_data`, automation rule Jbuilder partial, and reused dashboard automation upload/form flows. GoChat automation rule create/update now validates `send_attachment` direct uploads, normalizes upload UUIDs or numeric blob IDs into stored `blob_id` values, serializes `files` with `automation_rule_id`, rejects invalid blobs with raw `422`, preserves existing actions when update omits `actions`, and automation execution creates outgoing attachment messages with persisted attachment rows. Focused AutomationRuleHandler/AutomationRuleService tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. - 2026-06-07: P3.92 automation-rule attachment checkpoint prepared as `feat(automation): align attachment uploads`; audited Chatwoot `AutomationRulesController`, `AttachmentConcern`, `AutomationRule#file_base_data`, automation rule Jbuilder partial, and reused dashboard automation upload/form flows. GoChat automation rule create/update now validates `send_attachment` direct uploads, normalizes upload UUIDs or numeric blob IDs into stored `blob_id` values, serializes `files` with `automation_rule_id`, rejects invalid blobs with raw `422`, preserves existing actions when update omits `actions`, and automation execution creates outgoing attachment messages with persisted attachment rows. Focused AutomationRuleHandler/AutomationRuleService tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.93 conversation toggle-status checkpoint prepared as `feat(conversations): align toggle status fallback`; audited Chatwoot `ConversationsController#toggle_status`, `Conversation#toggle_status`, `toggle_status.json.jbuilder`, and reused dashboard conversation status API/store/actions. GoChat account conversation `toggle_status` now accepts omitted `status` and empty request bodies, toggles open conversations to resolved and pending/snoozed/resolved conversations to open, and keeps the Chatwoot response envelope with `success`, display `conversation_id`, `current_status`, and `snoozed_until`. Focused ConversationHandler toggle-status tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
@@ -2,6 +2,7 @@ package v1
import ( import (
"errors" "errors"
"io"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -297,9 +298,11 @@ func (h *ConversationHandler) ToggleStatus(c *gin.Context) {
} }
var req service.ToggleStatusRequest var req service.ToggleStatusRequest
if err := c.ShouldBindJSON(&req); err != nil { if c.Request.Body != nil && c.Request.Body != http.NoBody {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
return response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
} }
conversation, ok := h.resolveConversationRoute(c, accountID, conversationID) conversation, ok := h.resolveConversationRoute(c, accountID, conversationID)
@@ -702,6 +702,45 @@ func (s *ConversationCrudTestSuite) TestToggleStatus_Success() {
assert.Equal(s.T(), "resolved", resp.Payload.CurrentStatus) assert.Equal(s.T(), "resolved", resp.Payload.CurrentStatus)
} }
func (s *ConversationCrudTestSuite) TestToggleStatus_NoStatusTogglesLikeChatwoot() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", s.convURL(s.testConv.ID)+"/toggle_status", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp struct {
Payload struct {
Success bool `json:"success"`
CurrentStatus string `json:"current_status"`
} `json:"payload"`
}
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(s.T(), err)
assert.True(s.T(), resp.Payload.Success)
assert.Equal(s.T(), "resolved", resp.Payload.CurrentStatus)
}
func (s *ConversationCrudTestSuite) TestToggleStatus_NoStatusReopensPendingLikeChatwoot() {
s.Require().NoError(s.db.Model(&model.Conversation{}).Where("id = ?", s.testConv.ID).Update("status", "pending").Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", s.convURL(s.testConv.ID)+"/toggle_status", bytes.NewReader([]byte(`{}`)))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp struct {
Payload struct {
CurrentStatus string `json:"current_status"`
} `json:"payload"`
}
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(s.T(), err)
assert.Equal(s.T(), "open", resp.Payload.CurrentStatus)
}
func (s *ConversationCrudTestSuite) TestToggleStatus_InvalidAccountID() { func (s *ConversationCrudTestSuite) TestToggleStatus_InvalidAccountID() {
body, _ := json.Marshal(map[string]interface{}{ body, _ := json.Marshal(map[string]interface{}{
"status": "resolved", "status": "resolved",
+9 -2
View File
@@ -475,7 +475,7 @@ func (s *ConversationService) UnassignAgent(ctx context.Context, accountID, id u
// ToggleStatusRequest is the DTO for toggling conversation status. // ToggleStatusRequest is the DTO for toggling conversation status.
// Reference: Chatwoot app/controllers/api/v1/accounts/conversations_controller.rb #toggle_status // Reference: Chatwoot app/controllers/api/v1/accounts/conversations_controller.rb #toggle_status
type ToggleStatusRequest struct { type ToggleStatusRequest struct {
Status string `json:"status" validate:"required,oneof=open resolved pending snoozed"` Status string `json:"status" validate:"omitempty,oneof=open resolved pending snoozed"`
AssigneeID *uint `json:"assignee_id,omitempty"` // Chatwoot: auto-assign on reopen AssigneeID *uint `json:"assignee_id,omitempty"` // Chatwoot: auto-assign on reopen
SnoozedUntil *int64 `json:"snoozed_until,omitempty"` // Chatwoot: snooze with wake-up time (unix timestamp) SnoozedUntil *int64 `json:"snoozed_until,omitempty"` // Chatwoot: snooze with wake-up time (unix timestamp)
UserID *uint `json:"user_id,omitempty"` // Chatwoot: should_assign_conversation — auto-assign to agent who opens UserID *uint `json:"user_id,omitempty"` // Chatwoot: should_assign_conversation — auto-assign to agent who opens
@@ -495,8 +495,15 @@ func (s *ConversationService) ToggleStatus(ctx context.Context, accountID, id ui
return nil, err return nil, err
} }
newStatus := model.ConversationStatus(req.Status)
oldStatus := model.ConversationStatus(conversation.Status) oldStatus := model.ConversationStatus(conversation.Status)
newStatus := model.ConversationStatus(req.Status)
if strings.TrimSpace(req.Status) == "" {
if oldStatus == model.ConversationStatusOpen {
newStatus = model.ConversationStatusResolved
} else {
newStatus = model.ConversationStatusOpen
}
}
// Reference: Chatwoot conversations_controller#toggle_status // Reference: Chatwoot conversations_controller#toggle_status
// 1. pending_to_open_by_bot: AgentBot moves pending→open triggers bot_handoff! // 1. pending_to_open_by_bot: AgentBot moves pending→open triggers bot_handoff!