feat(documents): folder filter on list + per-doc move endpoint

listDocuments accepts folderId (string | null | undefined) and
includeDescendants. folderId=null returns only docs at root;
includeDescendants=true expands the subtree via collectDescendantIds
(in-memory walk over the cached tree -- folder trees are small).

PATCH /api/v1/documents/[id]/folder moves a single document under
documents.manage_folders, with audit-log metadata { type: 'folder_move' }.
Bumping updatedAt is correct for per-doc moves because reps deliberately
acted on that document -- different semantics from the bulk soft-rescue
in Task 4.

createDocument accepts an optional folderId for the upcoming UI's
"create in current folder" affordance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 20:03:25 +02:00
parent e9d5df647d
commit a0ffa1baae
5 changed files with 236 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ import { DOCUMENT_TYPES, DOCUMENT_STATUSES } from '@/lib/constants';
export const createDocumentSchema = z.object({
interestId: z.string().optional(),
clientId: z.string().optional(),
folderId: z.string().nullable().optional(),
documentType: z.enum(DOCUMENT_TYPES),
title: z.string().min(1).max(200),
notes: z.string().optional(),
@@ -83,6 +84,8 @@ export const listDocumentsSchema = baseListQuerySchema.extend({
interestId: z.string().optional(),
clientId: z.string().optional(),
documentType: z.string().optional(),
folderId: z.string().nullable().optional(),
includeDescendants: z.coerce.boolean().optional(),
status: z.string().optional(),
/** Hub tab filter - applies tab-specific status / signer-membership constraints. */
tab: z.enum(documentsHubTabs).optional(),