diff --git a/packages/apps/dms/src/api/document-api.js b/packages/apps/dms/src/api/document-api.js index 114291616..da9560587 100644 --- a/packages/apps/dms/src/api/document-api.js +++ b/packages/apps/dms/src/api/document-api.js @@ -17,6 +17,7 @@ */ import { + axiosApiProvider, createStandardSearch, formatRequestBody, getActionApi, @@ -107,6 +108,45 @@ const createDocumentCriteria = ({ return criteria; }; +const createFetchDirectoryCriteria = ({model, modelId}) => { + return [ + { + fieldName: 'isDirectory', + operator: '=', + value: true, + }, + { + fieldName: 'relatedId', + operator: '=', + value: modelId, + }, + { + fieldName: 'relatedModel', + operator: '=', + value: model, + }, + { + fieldName: 'parent.relatedModel', + operator: '=', + value: model, + }, + { + operator: 'or', + criteria: [ + { + fieldName: 'parent.relatedId', + operator: 'isNull', + }, + { + fieldName: 'parent.relatedId', + operator: '=', + value: 0, + }, + ], + }, + ]; +}; + export async function searchDocument({ searchValue = null, authorId, @@ -148,14 +188,18 @@ export async function searchDirectory({searchValue, authorId, page = 0}) { }); } -export async function createDocument({document}) { +export async function createDocument({document, model, modelId}) { const {matchers} = formatRequestBody(document, 'data'); return getActionApi().send({ url: '/ws/rest/com.axelor.dms.db.DMSFile', method: 'put', body: { - data: document, + data: { + relatedModel: model, + relatedId: modelId, + ...document, + }, }, description: 'create document', matchers: { @@ -185,3 +229,33 @@ export async function updateDocument({document}) { }, }); } + +export async function fetchDirectory({model, modelId}) { + return createStandardSearch({ + model: 'com.axelor.dms.db.DMSFile', + criteria: createFetchDirectoryCriteria({model, modelId}), + fieldKey: 'dms_document', + sortKey: 'dms_document', + page: 0, + numberElementsByPage: 1, + provider: 'model', + }); +} + +export async function countAttachedFiles({model, modelId}) { + return axiosApiProvider.post({ + url: '/ws/rest/com.axelor.dms.db.DMSFile/search', + data: { + data: { + _domain: + 'self.relatedModel = :name AND self.relatedId = :id ' + + 'AND COALESCE(self.isDirectory, FALSE) = FALSE', + _domainContext: { + name: model, + id: modelId, + }, + }, + fields: ['id'], + }, + }); +} diff --git a/packages/apps/dms/src/api/index.ts b/packages/apps/dms/src/api/index.ts index 49e9e1b98..3791b3514 100644 --- a/packages/apps/dms/src/api/index.ts +++ b/packages/apps/dms/src/api/index.ts @@ -17,7 +17,9 @@ */ export { + countAttachedFiles as countAttachedDocumentsApi, createDocument as createDocumentApi, + fetchDirectory as fetchDirectoryApi, searchDirectory as searchDirectoryApi, searchDocument as searchDocumentApi, updateDocument as updateDocumentApi, diff --git a/packages/apps/dms/src/hooks/use-dms-header-actions.ts b/packages/apps/dms/src/hooks/use-dms-header-actions.ts index 216d12823..ec3623edb 100644 --- a/packages/apps/dms/src/hooks/use-dms-header-actions.ts +++ b/packages/apps/dms/src/hooks/use-dms-header-actions.ts @@ -20,12 +20,15 @@ import {useEffect} from 'react'; import { headerActionsProvider, useNavigation, + useSelector, useTranslator, } from '@axelor/aos-mobile-core'; import {useThemeColor} from '@axelor/aos-mobile-ui'; +import {getAction} from '../utils'; export const useDMSHeaders = () => { useAllDocumentsActions(); + useAttachedFilesGenericAction(); }; const useAllDocumentsActions = () => { @@ -49,3 +52,24 @@ const useAllDocumentsActions = () => { }); }, [Colors, I18n, navigation]); }; + +const useAttachedFilesGenericAction = () => { + const I18n = useTranslator(); + const navigation = useNavigation(); + + const {mobileSettings} = useSelector((state: any) => state.appConfig); + + useEffect(() => { + headerActionsProvider.registerGenericAction( + 'attached_files_generic_action', + async ({model, modelId}) => + await getAction({ + model, + modelId, + isFolderCreationAllowed: mobileSettings?.isFolderCreationAllowed, + navigation, + translator: I18n.t, + }), + ); + }, [I18n.t, mobileSettings?.isFolderCreationAllowed, navigation]); +}; diff --git a/packages/apps/dms/src/i18n/en.json b/packages/apps/dms/src/i18n/en.json index 7706172c7..766d59259 100644 --- a/packages/apps/dms/src/i18n/en.json +++ b/packages/apps/dms/src/i18n/en.json @@ -14,9 +14,14 @@ "Dms_Folder": "Folder", "Dms_File": "File", "Dms_Root": "Root", + "Dms_AttachedFiles": "Attached files", + "Dms_NoAttachedFiles": "No attached files", + "Dms_DoYouWantToAddFile": "Do you want to add a file?", "Dms_SliceAction_SearchDocument": "search document", "Dms_SliceAction_SearchDirectory": "search directory", "Dms_SliceAction_SearchFavoriteDocument": "search favorite document", "Dms_SliceAction_CreateDocument": "create document", - "Dms_SliceAction_UpdateDocument": "update document" + "Dms_SliceAction_UpdateDocument": "update document", + "Dms_SliceAction_FetchDirectory": "fetch directory", + "Dms_SliceAction_CountAttachedFiles": "count attached files" } diff --git a/packages/apps/dms/src/i18n/fr.json b/packages/apps/dms/src/i18n/fr.json index 2d81b17f5..9fbb3f5b6 100644 --- a/packages/apps/dms/src/i18n/fr.json +++ b/packages/apps/dms/src/i18n/fr.json @@ -14,9 +14,14 @@ "Dms_Folder": "Dossier", "Dms_File": "Fichier", "Dms_Root": "Racine", + "Dms_AttachedFiles": "Fichiers joints", + "Dms_NoAttachedFiles": "Pas de fichiers joints", + "Dms_DoYouWantToAddFile": "Vouslez-vous ajouter un fichier ?", "Dms_SliceAction_SearchDocument": "recherche sur les documents", "Dms_SliceAction_SearchDirectory": "recherche sur les dossiers", "Dms_SliceAction_SearchFavoriteDocument": "recherche sur les documents favoris", "Dms_SliceAction_CreateDocument": "création d'un document", - "Dms_SliceAction_UpdateDocument": "mise à jour d'un document" + "Dms_SliceAction_UpdateDocument": "mise à jour d'un document", + "Dms_SliceAction_FetchDirectory": "récupération d'un dossier", + "Dms_SliceAction_CountAttachedFiles": "comptage des fichiers joints" } diff --git a/packages/apps/dms/src/models/forms.ts b/packages/apps/dms/src/models/forms.ts index c424c63e2..f0f6635f6 100644 --- a/packages/apps/dms/src/models/forms.ts +++ b/packages/apps/dms/src/models/forms.ts @@ -29,7 +29,9 @@ export const dms_formsRegister: FormConfigs = { type: 'object', widget: 'custom', customComponent: ParentDirectorySearchBar, - requiredIf: ({objectState}) => !objectState.parent?.fileName, + hideIf: ({objectState}) => objectState.isAttachedFileCreation, + requiredIf: ({objectState}) => + !objectState.parent?.fileName && !objectState.isAttachedFileCreation, }, fileName: { titleKey: 'Dms_Name', diff --git a/packages/apps/dms/src/screens/AttachedFilesScreen.tsx b/packages/apps/dms/src/screens/AttachedFilesScreen.tsx new file mode 100644 index 000000000..d4fb5c985 --- /dev/null +++ b/packages/apps/dms/src/screens/AttachedFilesScreen.tsx @@ -0,0 +1,85 @@ +/* + * Axelor Business Solutions + * + * Copyright (C) 2024 Axelor (). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, {useEffect, useState} from 'react'; +import { + handlerApiCall, + useIsFocused, + useTranslator, +} from '@axelor/aos-mobile-core'; +import {Alert, Text} from '@axelor/aos-mobile-ui'; +import {fetchDirectory} from '../api/document-api'; +import {DocumentList} from '../components'; + +const AttachedFilesScreen = ({navigation, route}) => { + const _parent = route?.params?.parent; + const model = route?.params?.model; + const modelId = route?.params?.modelId; + const I18n = useTranslator(); + const isFocused = useIsFocused(); + + const [isVisible, setIsVisible] = useState(false); + const [parent, setParent] = useState(_parent); + + useEffect(() => { + !parent && setIsVisible(true); + }, [parent]); + + useEffect(() => { + if (isFocused && parent == null) { + handlerApiCall({ + fetchFunction: fetchDirectory, + data: {model, modelId}, + action: 'Dms_SliceAction_FetchDirectory', + getState: () => {}, + responseOptions: {isArrayResponse: false}, + }).then(directory => setParent(directory)); + } + }, [isFocused, model, modelId, parent]); + + return ( + <> + {!isVisible && } + { + setIsVisible(false); + navigation.goBack(); + }, + }} + confirmButtonConfig={{ + title: I18n.t('Base_Yes'), + onPress: () => { + setIsVisible(false); + navigation.navigate('DocumentFormScreen', { + model, + modelId, + }); + }, + }} + translator={I18n.t}> + {I18n.t('Dms_DoYouWantToAddFile')} + + + ); +}; + +export default AttachedFilesScreen; diff --git a/packages/apps/dms/src/screens/DocumentFormScreen.tsx b/packages/apps/dms/src/screens/DocumentFormScreen.tsx index dd79e31f2..a6a6c202b 100644 --- a/packages/apps/dms/src/screens/DocumentFormScreen.tsx +++ b/packages/apps/dms/src/screens/DocumentFormScreen.tsx @@ -23,6 +23,8 @@ import {createDocument, updateDocument} from '../features/documentSlice'; const DocumentFormScreen = ({navigation, route}) => { const parent = route?.params?.parent; const document = route?.params?.document; + const model = route?.params?.model; + const modelId = route?.params?.modelId; const I18n = useTranslator(); const {user} = useSelector(state => state.user); @@ -30,9 +32,10 @@ const DocumentFormScreen = ({navigation, route}) => { const creationDefaultValue = useMemo( () => ({ + isAttachedFileCreation: model && modelId, parent: parent, }), - [parent], + [model, modelId, parent], ); const defaultValue = useMemo( @@ -48,25 +51,26 @@ const DocumentFormScreen = ({navigation, route}) => { const documentAPI = useCallback( (_document, isCreation, dispatch) => { - const parentId = _document.parent.id; + const parentId = _document.parent?.id; if (parentId == null) { _document.parent = null; } - if ( - parentId === user.dmsRoot?.id || - parentId === mobileSettings.defaultDmsRoot?.id - ) { - _document.parent = user.dmsRoot ?? mobileSettings.defaultDmsRoot; + if (parentId === user.dmsRoot?.id) { + _document.parent = user.dmsRoot; + } + + if (parentId === mobileSettings.defaultDmsRoot?.id) { + _document.parent = mobileSettings.defaultDmsRoot; } const sliceFunction = isCreation ? createDocument : updateDocument; - dispatch((sliceFunction as any)({document: _document})); + dispatch((sliceFunction as any)({document: _document, model, modelId})); - navigation.navigate('AllDocumentsScreen'); + navigation.goBack(); }, - [mobileSettings.defaultDmsRoot, navigation, user.dmsRoot], + [mobileSettings.defaultDmsRoot, model, modelId, navigation, user.dmsRoot], ); return ( diff --git a/packages/apps/dms/src/screens/index.ts b/packages/apps/dms/src/screens/index.ts index d09601cc8..0185e6c1d 100644 --- a/packages/apps/dms/src/screens/index.ts +++ b/packages/apps/dms/src/screens/index.ts @@ -19,6 +19,7 @@ import AllDocumentsScreen from './AllDocumentsScreen'; import MyFavoriteDocumentsScreen from './MyFavoriteDocumentsScreen'; import DocumentFormScreen from './DocumentFormScreen'; +import AttachedFilesScreen from './AttachedFilesScreen'; export default { AllDocumentsScreen: { @@ -41,8 +42,16 @@ export default { title: 'Dms_Document', component: DocumentFormScreen, }, + AttachedFilesScreen: { + title: 'Dms_AttachedFiles', + component: AttachedFilesScreen, + options: { + shadedHeader: false, + }, + }, }; export {AllDocumentsScreen}; export {MyFavoriteDocumentsScreen}; export {DocumentFormScreen}; +export {AttachedFilesScreen}; diff --git a/packages/apps/dms/src/utils/attachedFilesGenericAction.ts b/packages/apps/dms/src/utils/attachedFilesGenericAction.ts new file mode 100644 index 000000000..99f0fd4ea --- /dev/null +++ b/packages/apps/dms/src/utils/attachedFilesGenericAction.ts @@ -0,0 +1,68 @@ +/* + * Axelor Business Solutions + * + * Copyright (C) 2024 Axelor (). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {handlerApiCall} from '@axelor/aos-mobile-core'; +import {countAttachedFiles, fetchDirectory} from '../api/document-api'; + +interface getActionProps { + model: string; + modelId: number; + isFolderCreationAllowed: boolean; + navigation: any; + translator: (key: string) => string; +} + +export const getAction = async ({ + model, + modelId, + isFolderCreationAllowed, + navigation, + translator, +}: getActionProps) => { + const directory = await handlerApiCall({ + fetchFunction: fetchDirectory, + data: {model, modelId}, + action: 'Dms_SliceAction_FetchDirectory', + getState: () => {}, + responseOptions: {isArrayResponse: false}, + }); + + const numberAttachedFiles = await handlerApiCall({ + fetchFunction: countAttachedFiles, + data: {model, modelId}, + action: 'Dms_SliceAction_CountAttachedFiles', + getState: () => {}, + responseOptions: {returnTotal: true}, + }); + + return { + key: 'dmsAttachedFiles', + order: 10, + iconName: 'paperclip', + indicator: numberAttachedFiles, + title: translator('Dms_AttachedFiles'), + onPress: () => + navigation.navigate('AttachedFilesScreen', { + parent: directory, + model, + modelId, + }), + showInHeader: true, + hideIf: !directory && !isFolderCreationAllowed, + }; +}; diff --git a/packages/apps/dms/src/utils/index.ts b/packages/apps/dms/src/utils/index.ts index 68c414890..8773f4fa4 100644 --- a/packages/apps/dms/src/utils/index.ts +++ b/packages/apps/dms/src/utils/index.ts @@ -16,4 +16,5 @@ * along with this program. If not, see . */ +export * from './attachedFilesGenericAction'; export * from './moduleHelper';