diff --git a/papermerge/core/routers/documents.py b/papermerge/core/routers/documents.py index d8e76364b..a5f8c1eda 100644 --- a/papermerge/core/routers/documents.py +++ b/papermerge/core/routers/documents.py @@ -4,6 +4,7 @@ from celery.app import default_app as celery_app from fastapi import APIRouter, Depends, HTTPException, Security, UploadFile +from pydantic import BaseModel from sqlalchemy.exc import NoResultFound from papermerge.conf import settings @@ -18,6 +19,10 @@ ) +class DocumentTypeArg(BaseModel): + document_type_id: uuid.UUID | None = None + + @router.get("/type/{document_type_id}") @utils.docstring_parameter(scope=scopes.NODE_VIEW) def get_documents_by_type( @@ -62,38 +67,6 @@ def get_document_details( return doc -@router.post("/{document_id}/custom-fields") -@utils.docstring_parameter(scope=scopes.NODE_UPDATE) -def add_document_custom_field_values( - document_id: uuid.UUID, - custom_fields_add: schemas.DocumentCustomFieldsAdd, - user: Annotated[ - schemas.User, Security(get_current_user, scopes=[scopes.NODE_UPDATE]) - ], - db_session: db.Session = Depends(db.get_session), -) -> list[schemas.CustomFieldValue]: - """ - Associates document type to specified `document_type_id` and set it custom field - value(s). This API will create a NEW custom field value - - All custom fields must be part of `DocumentType` specified by `document_type_id`, - otherwise response will return error 400 - invalid request. - - Required scope: `{scope}` - """ - try: - added_entries = db.add_document_custom_field_values( - db_session, - id=document_id, - custom_fields_add=custom_fields_add, - user_id=user.id, - ) - except NoResultFound: - raise HTTPException(status_code=404, detail="Document not found") - - return added_entries - - @router.patch("/{document_id}/custom-fields") @utils.docstring_parameter(scope=scopes.NODE_UPDATE) def update_document_custom_field_values( @@ -150,6 +123,31 @@ def get_document_custom_field_values( return doc +@router.patch("/{document_id}/type") +@utils.docstring_parameter(scope=scopes.NODE_UPDATE) +def update_document_type( + document_id: uuid.UUID, + document_type: DocumentTypeArg, + user: Annotated[ + schemas.User, Security(get_current_user, scopes=[scopes.NODE_VIEW]) + ], + db_session: db.Session = Depends(db.get_session), +): + """ + Updates document type + + Required scope: `{scope}` + """ + try: + db.update_doc_type( + db_session, + document_id=document_id, + document_type_id=document_type.document_type_id, + ) + except NoResultFound: + raise HTTPException(status_code=404, detail="Document not found") + + @router.post("/{document_id}/upload") @utils.docstring_parameter(scope=scopes.DOCUMENT_UPLOAD) def upload_file( diff --git a/papermerge/core/schemas/documents.py b/papermerge/core/schemas/documents.py index 46dfbd1b6..40cf162bd 100644 --- a/papermerge/core/schemas/documents.py +++ b/papermerge/core/schemas/documents.py @@ -253,6 +253,6 @@ class DocumentCustomFieldsUpdateValue(BaseModel): class DocumentCustomFieldsUpdate(BaseModel): - custom_field_value_id: UUID | None + custom_field_value_id: UUID | None = None key: CFNameType value: CFValueType diff --git a/ui2/src/features/document/apiSlice.ts b/ui2/src/features/document/apiSlice.ts index c20494384..1820f5255 100644 --- a/ui2/src/features/document/apiSlice.ts +++ b/ui2/src/features/document/apiSlice.ts @@ -62,6 +62,16 @@ type GetDocsByTypeArgs = { ancestor_id: string } +type UpdateDocumentTypeArgs = { + document_id?: string + invalidatesTags: { + documentTypeID?: string + } + body: { + document_type_id: string | null + } +} + export const apiSliceWithDocuments = apiSlice.injectEndpoints({ endpoints: builder => ({ getDocument: builder.query({ @@ -168,6 +178,19 @@ export const apiSliceWithDocuments = apiSlice.injectEndpoints({ ] } }), + updateDocumentType: builder.mutation({ + query: data => ({ + url: `/documents/${data.document_id}/type`, + method: "PATCH", + body: data.body + }), + invalidatesTags: (_result, _error, arg) => { + return [ + {type: "Document", id: arg.document_id}, + {type: "DocumentCFV", id: arg.invalidatesTags.documentTypeID} + ] + } + }), getDocumentCustomFields: builder.query({ query: documentID => ({ url: `/documents/${documentID}/custom-fields` @@ -194,6 +217,7 @@ export const { useMovePagesMutation, useExtractPagesMutation, useUpdateDocumentCustomFieldsMutation, + useUpdateDocumentTypeMutation, useGetDocumentCustomFieldsQuery, useGetDocsByTypeQuery } = apiSliceWithDocuments diff --git a/ui2/src/features/document/components/DocumentDetails/CustomFields.tsx b/ui2/src/features/document/components/DocumentDetails/CustomFields.tsx index ab1b8f486..78654fa93 100644 --- a/ui2/src/features/document/components/DocumentDetails/CustomFields.tsx +++ b/ui2/src/features/document/components/DocumentDetails/CustomFields.tsx @@ -15,29 +15,32 @@ import { } from "@/features/document-types/apiSlice" import { useGetDocumentCustomFieldsQuery, - useUpdateDocumentCustomFieldsMutation + useUpdateDocumentCustomFieldsMutation, + useUpdateDocumentTypeMutation } from "@/features/document/apiSlice" import {selectCurrentNodeID} from "@/features/ui/uiSlice" import type {CFV, PanelMode} from "@/types" import {Button, ComboboxItem, Select, Skeleton, TextInput} from "@mantine/core" export default function CustomFields() { - const [showSaveButton, setShowSaveButton] = useState(false) - const {data: allDocumentTypes = [], isSuccess: isSuccessAllDocumentTypes} = - useGetDocumentTypesQuery() const mode: PanelMode = useContext(PanelContext) - const docID = useAppSelector(s => selectCurrentNodeID(s, mode)) - const {currentData: doc, isLoading} = useGetDocumentQuery(docID ?? skipToken) + const [showSaveButton, setShowSaveButton] = useState(false) + const [customFieldValues, setCustomFieldValues] = useState([]) const [documentTypeID, setDocumentTypeID] = useState( null ) + + const docID = useAppSelector(s => selectCurrentNodeID(s, mode)) + + const {data: allDocumentTypes = [], isSuccess: isSuccessAllDocumentTypes} = + useGetDocumentTypesQuery() + const {currentData: doc, isLoading} = useGetDocumentQuery(docID ?? skipToken) const {currentData: documentType} = useGetDocumentTypeQuery( documentTypeID?.value ?? skipToken ) - const [customFieldValues, setCustomFieldValues] = useState([]) const [updateDocumentCustomFields, {error}] = useUpdateDocumentCustomFieldsMutation() - + const [updateDocumentType] = useUpdateDocumentTypeMutation() const {data: documentCustomFields, isSuccess: isSuccessDocumentCustomFields} = useGetDocumentCustomFieldsQuery(docID ?? skipToken) @@ -124,35 +127,34 @@ export default function CustomFields() { /> )) - const onDocumentTypeChange = (_: any, option: ComboboxItem) => { - setDocumentTypeID(option) - if (option && option.value != doc?.document_type_id) { - setShowSaveButton(true) - } else { - setShowSaveButton(false) - } - if ( - documentTypeID && - documentTypeID.value == doc?.document_type_id && - isSuccessDocumentCustomFields && - documentCustomFields && - documentCustomFields.length > 0 - ) { - const initialCustFieldValues = documentCustomFields.map(i => { - return {...i, value: i.value} - }) - setCustomFieldValues(initialCustFieldValues) + const onDocumentTypeChange = async (_: any, option: ComboboxItem) => { + const documentTypeIDToInvalidate = + doc?.document_type_id || (option ? option.value : undefined) + + const data = { + document_id: docID!, + invalidatesTags: { + documentTypeID: documentTypeIDToInvalidate + }, + body: { + document_type_id: option ? option.value : null + } } + await updateDocumentType(data) + + setDocumentTypeID(option) + setCustomFieldValues([]) + setShowSaveButton(false) } - const onClear = () => { + const onClearDocumentType = () => { setDocumentTypeID(null) setShowSaveButton(true) setCustomFieldValues([]) } const onSave = async () => { - if (documentCustomFields && documentCustomFields.length > 0) { + if (customFieldValues && customFieldValues.length > 0) { // document already has custom fields associated // we need to update existing custom field value const content = customFieldValues.map(i => { @@ -188,7 +190,7 @@ export default function CustomFields() { value={documentTypeID ? documentTypeID.value : null} placeholder="Pick Value" onChange={onDocumentTypeChange} - onClear={onClear} + onClear={onClearDocumentType} clearable /> {genericCustomFieldsComponents} diff --git a/ui2/src/features/document/components/customFields/Date.tsx b/ui2/src/features/document/components/customFields/Date.tsx index 3e365d2d3..386ec6ef9 100644 --- a/ui2/src/features/document/components/customFields/Date.tsx +++ b/ui2/src/features/document/components/customFields/Date.tsx @@ -36,6 +36,8 @@ export default function CustomFieldDate({ const DATE_FORMAT = "YYYY-MM-DD" const strValue = d.format(DATE_FORMAT) onChange({customField, value: strValue}) + } else { + onChange({customField, value: ""}) } setValue(value) }