From f136b03fac5b97648d0265370a69de77cb78a524 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:03:20 -0300 Subject: [PATCH 01/10] feat: add NONE mode to SignatureFlow enum Allow admin to not enforce signing flow, letting users choose per document. Updates enum, File entity, and migration defaults. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Db/File.php | 2 +- lib/Enum/SignatureFlow.php | 4 ++++ lib/Migration/Version15000Date20251209000000.php | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Db/File.php b/lib/Db/File.php index 07c112f51c..04b93a1c26 100644 --- a/lib/Db/File.php +++ b/lib/Db/File.php @@ -57,7 +57,7 @@ class File extends Entity { protected ?string $callback = null; protected ?array $metadata = null; protected int $modificationStatus = 0; - protected int $signatureFlow = SignatureFlow::NUMERIC_PARALLEL; + protected int $signatureFlow = SignatureFlow::NUMERIC_NONE; protected int $docmdpLevel = 0; public const STATUS_NOT_LIBRESIGN_FILE = -1; public const STATUS_DRAFT = 0; diff --git a/lib/Enum/SignatureFlow.php b/lib/Enum/SignatureFlow.php index 71d31ba538..f0305c437f 100644 --- a/lib/Enum/SignatureFlow.php +++ b/lib/Enum/SignatureFlow.php @@ -13,14 +13,17 @@ * Signature flow modes */ enum SignatureFlow: string { + case NONE = 'none'; case PARALLEL = 'parallel'; case ORDERED_NUMERIC = 'ordered_numeric'; + public const NUMERIC_NONE = 0; public const NUMERIC_PARALLEL = 1; public const NUMERIC_ORDERED_NUMERIC = 2; public function toNumeric(): int { return match($this) { + self::NONE => self::NUMERIC_NONE, self::PARALLEL => self::NUMERIC_PARALLEL, self::ORDERED_NUMERIC => self::NUMERIC_ORDERED_NUMERIC, }; @@ -28,6 +31,7 @@ public function toNumeric(): int { public static function fromNumeric(int $value): self { return match($value) { + self::NUMERIC_NONE => self::NONE, self::NUMERIC_PARALLEL => self::PARALLEL, self::NUMERIC_ORDERED_NUMERIC => self::ORDERED_NUMERIC, default => throw new \ValueError("Invalid numeric value for SignatureFlow: $value"), diff --git a/lib/Migration/Version15000Date20251209000000.php b/lib/Migration/Version15000Date20251209000000.php index d68f3947e4..fd183c665c 100644 --- a/lib/Migration/Version15000Date20251209000000.php +++ b/lib/Migration/Version15000Date20251209000000.php @@ -62,8 +62,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if (!$tableFile->hasColumn('signature_flow')) { $tableFile->addColumn('signature_flow', Types::SMALLINT, [ 'notnull' => true, - 'default' => SignatureFlow::NUMERIC_PARALLEL, - 'comment' => 'Signature flow mode: 1=parallel, 2=ordered_numeric', + 'default' => SignatureFlow::NUMERIC_NONE, + 'comment' => 'Signature flow mode: 0=none (no admin enforcement), 1=parallel, 2=ordered_numeric', ]); } } From 4e6b71e8b4f0779beb093d407122d20a8d29b497 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:03:29 -0300 Subject: [PATCH 02/10] feat: allow signature flow update on existing files Enable users to change signature flow when updating file if admin has not enforced a specific flow mode. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/RequestSignatureService.php | 50 +++++++++++++++++-------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/Service/RequestSignatureService.php b/lib/Service/RequestSignatureService.php index 3308a1540a..aee7d3bb10 100644 --- a/lib/Service/RequestSignatureService.php +++ b/lib/Service/RequestSignatureService.php @@ -79,6 +79,7 @@ public function save(array $data): FileEntity { public function saveFile(array $data): FileEntity { if (!empty($data['uuid'])) { $file = $this->fileMapper->getByUuid($data['uuid']); + $this->updateSignatureFlowIfAllowed($file, $data); return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0); } $fileId = null; @@ -90,6 +91,7 @@ public function saveFile(array $data): FileEntity { if (!is_null($fileId)) { try { $file = $this->fileMapper->getByFileId($fileId); + $this->updateSignatureFlowIfAllowed($file, $data); return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0); } catch (\Throwable) { } @@ -118,27 +120,45 @@ public function saveFile(array $data): FileEntity { $file->setStatus(FileEntity::STATUS_ABLE_TO_SIGN); } - if (isset($data['signatureFlow']) && is_string($data['signatureFlow'])) { - try { - $signatureFlow = \OCA\Libresign\Enum\SignatureFlow::from($data['signatureFlow']); - $file->setSignatureFlowEnum($signatureFlow); - } catch (\ValueError) { - $this->setSignatureFlowFromGlobalConfig($file); - } - } else { - $this->setSignatureFlowFromGlobalConfig($file); - } - + $this->setSignatureFlow($file, $data); $this->setDocMdpLevelFromGlobalConfig($file); $this->fileMapper->insert($file); return $file; } - private function setSignatureFlowFromGlobalConfig(FileEntity $file): void { - $globalFlowValue = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::PARALLEL->value); - $globalFlow = SignatureFlow::from($globalFlowValue); - $file->setSignatureFlowEnum($globalFlow); + private function updateSignatureFlowIfAllowed(FileEntity $file, array $data): void { + $adminFlow = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::NONE->value); + $adminForcedConfig = $adminFlow !== SignatureFlow::NONE->value; + + if ($adminForcedConfig) { + $adminFlowEnum = SignatureFlow::from($adminFlow); + if ($file->getSignatureFlowEnum() !== $adminFlowEnum) { + $file->setSignatureFlowEnum($adminFlowEnum); + $this->fileMapper->update($file); + } + return; + } + + if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) { + $newFlow = SignatureFlow::from($data['signatureFlow']); + if ($file->getSignatureFlowEnum() !== $newFlow) { + $file->setSignatureFlowEnum($newFlow); + $this->fileMapper->update($file); + } + } + } + + private function setSignatureFlow(FileEntity $file, array $data): void { + $adminFlow = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::NONE->value); + + if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) { + $file->setSignatureFlowEnum(SignatureFlow::from($data['signatureFlow'])); + } elseif ($adminFlow !== SignatureFlow::NONE->value) { + $file->setSignatureFlowEnum(SignatureFlow::from($adminFlow)); + } else { + $file->setSignatureFlowEnum(SignatureFlow::NONE); + } } private function setDocMdpLevelFromGlobalConfig(FileEntity $file): void { From df331fc8b79e908010b4f74736d489c04aa46f42 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:03:37 -0300 Subject: [PATCH 03/10] feat: add signatureFlow parameter to PATCH endpoint Add optional signatureFlow parameter to request-signature PATCH endpoint. Updates OpenAPI specs and TypeScript types. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Controller/RequestSignatureController.php | 13 +++++++++++-- openapi-full.json | 5 +++++ openapi.json | 5 +++++ src/types/openapi/openapi-full.ts | 2 ++ src/types/openapi/openapi.ts | 2 ++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/Controller/RequestSignatureController.php b/lib/Controller/RequestSignatureController.php index e436cfdbee..1557796bdb 100644 --- a/lib/Controller/RequestSignatureController.php +++ b/lib/Controller/RequestSignatureController.php @@ -136,6 +136,7 @@ public function request( * @param LibresignVisibleElement[]|null $visibleElements Visible elements on document * @param LibresignNewFile|array|null $file File object. * @param integer|null $status Numeric code of status * 0 - no signers * 1 - signed * 2 - pending + * @param string|null $signatureFlow Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration * @return DataResponse|DataResponse}, array{}> * * 200: OK @@ -145,7 +146,14 @@ public function request( #[NoCSRFRequired] #[RequireManager] #[ApiRoute(verb: 'PATCH', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])] - public function updateSign(?array $users = [], ?string $uuid = null, ?array $visibleElements = null, ?array $file = [], ?int $status = null): DataResponse { + public function updateSign( + ?array $users = [], + ?string $uuid = null, + ?array $visibleElements = null, + ?array $file = [], + ?int $status = null, + ?string $signatureFlow = null, + ): DataResponse { $user = $this->userSession->getUser(); $data = [ 'uuid' => $uuid, @@ -153,7 +161,8 @@ public function updateSign(?array $users = [], ?string $uuid = null, ?array $vis 'users' => $users, 'userManager' => $user, 'status' => $status, - 'visibleElements' => $visibleElements + 'visibleElements' => $visibleElements, + 'signatureFlow' => $signatureFlow, ]; try { $this->validateHelper->validateExistingFile($data); diff --git a/openapi-full.json b/openapi-full.json index 4c065cb325..7e85c0293d 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -6679,6 +6679,11 @@ "format": "int64", "nullable": true, "description": "Numeric code of status * 0 - no signers * 1 - signed * 2 - pending" + }, + "signatureFlow": { + "type": "string", + "nullable": true, + "description": "Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration" } } } diff --git a/openapi.json b/openapi.json index 0eb99d6307..8173128411 100644 --- a/openapi.json +++ b/openapi.json @@ -6529,6 +6529,11 @@ "format": "int64", "nullable": true, "description": "Numeric code of status * 0 - no signers * 1 - signed * 2 - pending" + }, + "signatureFlow": { + "type": "string", + "nullable": true, + "description": "Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration" } } } diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index f068780225..eaa8ee4e72 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -4106,6 +4106,8 @@ export interface operations { * @description Numeric code of status * 0 - no signers * 1 - signed * 2 - pending */ status?: number | null; + /** @description Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration */ + signatureFlow?: string | null; }; }; }; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 8e0923626c..bdbee18ff9 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -3628,6 +3628,8 @@ export interface operations { * @description Numeric code of status * 0 - no signers * 1 - signed * 2 - pending */ status?: number | null; + /** @description Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration */ + signatureFlow?: string | null; }; }; }; From fb4ddf10290cfacbf0bc649d7446bd8a69fed1a4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:03:45 -0300 Subject: [PATCH 04/10] fix: use NONE as default signature flow in initial states Update default signature flow from 'parallel' to 'none' in page controller, template loader, admin settings, and settings view. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Controller/PageController.php | 2 +- lib/Files/TemplateLoader.php | 2 +- lib/Settings/Admin.php | 2 +- src/views/Settings/SignatureFlow.vue | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 143b25c190..04d3ef795f 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -95,7 +95,7 @@ public function index(): TemplateResponse { $this->provideSignerSignatues(); $this->initialState->provideInitialState('identify_methods', $this->identifyMethodService->getIdentifyMethodsSettings()); - $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::PARALLEL->value)); + $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::NONE->value)); $this->initialState->provideInitialState('legal_information', $this->appConfig->getValueString(Application::APP_ID, 'legal_information')); Util::addScript(Application::APP_ID, 'libresign-main'); diff --git a/lib/Files/TemplateLoader.php b/lib/Files/TemplateLoader.php index a5f826222b..8832fb1df3 100644 --- a/lib/Files/TemplateLoader.php +++ b/lib/Files/TemplateLoader.php @@ -59,7 +59,7 @@ public function handle(Event $event): void { $this->initialState->provideInitialState( 'signature_flow', - $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::PARALLEL->value) + $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::NONE->value) ); try { diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 857cf4eeae..c1cc7b0a70 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -78,7 +78,7 @@ public function getForm(): TemplateResponse { $this->initialState->provideInitialState('tsa_username', $this->appConfig->getValueString(Application::APP_ID, 'tsa_username', '')); $this->initialState->provideInitialState('tsa_password', $this->appConfig->getValueString(Application::APP_ID, 'tsa_password', self::PASSWORD_PLACEHOLDER)); $this->initialState->provideInitialState('docmdp_config', $this->docMdpConfigService->getConfig()); - $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', '')); + $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::NONE->value)); return new TemplateResponse(Application::APP_ID, 'admin_settings'); } diff --git a/src/views/Settings/SignatureFlow.vue b/src/views/Settings/SignatureFlow.vue index ec981e575e..caafeb155b 100644 --- a/src/views/Settings/SignatureFlow.vue +++ b/src/views/Settings/SignatureFlow.vue @@ -104,9 +104,9 @@ export default { methods: { loadConfig() { try { - const mode = loadState('libresign', 'signature_flow', null) + const mode = loadState('libresign', 'signature_flow', 'none') - if (mode === null || mode === '') { + if (mode === 'none') { this.enabled = false this.selectedFlow = this.availableFlows[0] } else { From bc8bbf610fdbb101ce2a2ae7b33edfc04ee6e9a5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:03:54 -0300 Subject: [PATCH 05/10] fix: handle numeric signature flow values in frontend Convert numeric flow values (0=none, 1=parallel, 2=ordered_numeric) to string equivalents in Signer, Signers, and files store. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/Components/Signers/Signer.vue | 9 ++++++++- src/Components/Signers/Signers.vue | 9 ++++++++- src/store/files.js | 24 ++++++++++++++++++++---- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Components/Signers/Signer.vue b/src/Components/Signers/Signer.vue index 0aa6f4405a..3e7d0a4b27 100644 --- a/src/Components/Signers/Signer.vue +++ b/src/Components/Signers/Signer.vue @@ -96,7 +96,14 @@ export default { computed: { signatureFlow() { const file = this.filesStore.getFile() - return file?.signatureFlow ?? 'parallel' + let flow = file?.signatureFlow ?? 'parallel' + + if (typeof flow === 'number') { + const flowMap = { 0: 'none', 1: 'parallel', 2: 'ordered_numeric' } + flow = flowMap[flow] || 'parallel' + } + + return flow }, signer() { const file = this.filesStore.getFile() diff --git a/src/Components/Signers/Signers.vue b/src/Components/Signers/Signers.vue index 4e3b62eb34..9efa9f92e2 100644 --- a/src/Components/Signers/Signers.vue +++ b/src/Components/Signers/Signers.vue @@ -83,7 +83,14 @@ export default { }, isOrderedNumeric() { const file = this.filesStore.getFile() - return file?.signatureFlow === 'ordered_numeric' + let flow = file?.signatureFlow + + if (typeof flow === 'number') { + const flowMap = { 0: 'none', 1: 'parallel', 2: 'ordered_numeric' } + flow = flowMap[flow] + } + + return flow === 'ordered_numeric' }, canReorder() { return this.filesStore.canSave() && this.signers.length > 1 diff --git a/src/store/files.js b/src/store/files.js index c9efae2836..913d121424 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -381,8 +381,15 @@ export const useFilesStore = function(...args) { filesSorted() { return this.ordered.map(key => this.files[key]) }, - async saveWithVisibleElements({ visibleElements = [], signers = null, uuid = null, nodeId = null }) { + async saveWithVisibleElements({ visibleElements = [], signers = null, uuid = null, nodeId = null, signatureFlow = null }) { const file = this.getFile() + + let flowValue = signatureFlow || file.signatureFlow + if (typeof flowValue === 'number') { + const flowMap = { 0: 'none', 1: 'parallel', 2: 'ordered_numeric' } + flowValue = flowMap[flowValue] || 'parallel' + } + const config = { url: generateOcsUrl('/apps/libresign/api/v1/request-signature'), method: uuid || file.uuid ? 'patch' : 'post', @@ -391,10 +398,12 @@ export const useFilesStore = function(...args) { users: signers || file.signers, visibleElements, status: 0, + signatureFlow: flowValue, }, } - if (uuid || file.uuid) { + + if (uuid || file.uuid) { config.data.uuid = uuid || file.uuid } else { config.data.file = { @@ -406,8 +415,15 @@ export const useFilesStore = function(...args) { this.addFile(data.ocs.data.data) return data.ocs.data }, - async updateSignatureRequest({ visibleElements = [], signers = null, uuid = null, nodeId = null, status = 1 }) { + async updateSignatureRequest({ visibleElements = [], signers = null, uuid = null, nodeId = null, status = 1, signatureFlow = null }) { const file = this.getFile() + + let flowValue = signatureFlow || file.signatureFlow + if (typeof flowValue === 'number') { + const flowMap = { 0: 'none', 1: 'parallel', 2: 'ordered_numeric' } + flowValue = flowMap[flowValue] || 'parallel' + } + const config = { url: generateOcsUrl('/apps/libresign/api/v1/request-signature'), method: uuid || file.uuid ? 'patch' : 'post', @@ -416,6 +432,7 @@ export const useFilesStore = function(...args) { users: signers || file.signers, visibleElements, status, + signatureFlow: flowValue, }, } @@ -426,7 +443,6 @@ export const useFilesStore = function(...args) { fileId: nodeId || this.selectedNodeId, } } - const { data } = await axios(config) this.addFile(data.ocs.data.data) return data.ocs.data From 4cfe6677f9d916a2c2da4470371a44443e5f0686 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:04:03 -0300 Subject: [PATCH 06/10] feat: add SigningOrderDiagram visual component Add visual diagram component showing signing flow with: - Sender stage at top - Numbered order stages with signers - Status indicators (signed/pending/draft) - NcPopover with detailed signer info - Completed stage at bottom Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../SigningOrder/SigningOrderDiagram.vue | 473 ++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 src/Components/SigningOrder/SigningOrderDiagram.vue diff --git a/src/Components/SigningOrder/SigningOrderDiagram.vue b/src/Components/SigningOrder/SigningOrderDiagram.vue new file mode 100644 index 0000000000..f030790fc2 --- /dev/null +++ b/src/Components/SigningOrder/SigningOrderDiagram.vue @@ -0,0 +1,473 @@ + + + + + + From 71cb2d3f9e192726cbf6ba2e40d2ff37f27fc6b7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:04:12 -0300 Subject: [PATCH 07/10] feat: add preserve order toggle and view diagram button Add UI controls in RequestSignatureTab: - Preserve signing order toggle switch - View signing order diagram button - Modal with SigningOrderDiagram component - Sync preserve order state with file changes Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../RightSidebar/RequestSignatureTab.vue | 166 ++++++++++++++++-- 1 file changed, 154 insertions(+), 12 deletions(-) diff --git a/src/Components/RightSidebar/RequestSignatureTab.vue b/src/Components/RightSidebar/RequestSignatureTab.vue index b3327bbaeb..df518fd65c 100644 --- a/src/Components/RightSidebar/RequestSignatureTab.vue +++ b/src/Components/RightSidebar/RequestSignatureTab.vue @@ -15,6 +15,20 @@ @click="addSigner"> {{ t('libresign', 'Add signer') }} + + {{ t('libresign', 'Preserve signing order') }} + + + + {{ t('libresign', 'View signing order') }} + + + + +