Compare commits

...

4 Commits

14 changed files with 169 additions and 17 deletions

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Only allow delete of attachment on workflows that are not final
time: 2025-07-24T16:15:56.042884578+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Move up signature buttons on index workflow page for easier access
time: 2025-07-24T16:16:28.609598883+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Filter out document from attachment list if it is the same as the workflow document
time: 2025-07-24T17:20:13.118537573+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,5 +1,6 @@
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
import {fetchResults, makeFetch} from "ChillMainAssets/lib/api/apiMethods";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import {EntityWorkflow} from "ChillMainAssets/types";
export function fetch_generic_docs_by_accompanying_period(
periodId: number,
@@ -8,3 +9,17 @@ export function fetch_generic_docs_by_accompanying_period(
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
);
}
export const fetchWorkflow = async (
workflowId: number,
): Promise<EntityWorkflow> => {
try {
return await makeFetch<null, EntityWorkflow>(
"GET",
`/api/1.0/main/workflow/${workflowId}.json`,
);
} catch (error) {
console.error(`Failed to fetch workflow ${workflowId}:`, error);
throw error;
}
};

View File

@@ -25,7 +25,7 @@ export interface GenericDoc {
type: "doc_store_generic_doc";
uniqueKey: string;
key: string;
identifiers: object;
identifiers: { id: number; };
context: "person" | "accompanying-period";
doc_date: DateTime;
metadata: GenericDocMetadata;

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Pagination\PaginatorFactory;
@@ -27,7 +28,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
class WorkflowApiController
class WorkflowApiController extends ApiController
{
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly PaginatorFactory $paginatorFactory, private readonly Security $security, private readonly SerializerInterface $serializer) {}

View File

@@ -30,6 +30,7 @@ use Chill\MainBundle\Controller\UserGroupAdminController;
use Chill\MainBundle\Controller\UserGroupApiController;
use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\Controller\WorkflowApiController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\Age;
use Chill\MainBundle\Doctrine\DQL\Extract;
@@ -66,6 +67,7 @@ use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Form\CenterType;
use Chill\MainBundle\Form\CivilityType;
use Chill\MainBundle\Form\CountryType;
@@ -79,6 +81,7 @@ use Chill\MainBundle\Form\UserGroupType;
use Chill\MainBundle\Form\UserJobType;
use Chill\MainBundle\Form\UserType;
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
use Ramsey\Uuid\Doctrine\UuidType;
use Symfony\Component\Config\FileLocator;
@@ -940,6 +943,21 @@ class ChillMainExtension extends Extension implements
],
],
],
[
'class' => EntityWorkflow::class,
'name' => 'workflow',
'base_path' => '/api/1.0/main/workflow',
'base_role' => EntityWorkflowVoter::SEE,
'controller' => WorkflowApiController::class,
'actions' => [
'_entity' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
],
],
],
],
],
]);
}

View File

@@ -1,5 +1,6 @@
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import {Person} from "../../../ChillPersonBundle/Resources/public/types";
export interface DateTime {
datetime: string;
@@ -202,6 +203,55 @@ export interface WorkflowAttachment {
genericDoc: null | GenericDoc;
}
export interface Workflow {
name: string;
text: string;
}
export interface EntityWorkflowStep {
type: "entity_workflow_step";
id: number;
comment: string;
currentStep: StepDefinition;
isFinal: boolean;
isFreezed: boolean;
isFinalized: boolean;
transitionPrevious: Transition | null;
transitionAfter: Transition | null;
previousId: number | null;
nextId: number | null;
transitionPreviousBy: User | null;
transitionPreviousAt: DateTime | null;
}
export interface Transition {
name: string;
text: string;
isForward: boolean;
}
export interface StepDefinition {
name: string;
text: string;
}
export interface EntityWorkflow {
type: "entity_workflow";
id: number;
relatedEntityClass: string;
relatedEntityId: number;
workflow: Workflow;
currentStep: EntityWorkflowStep;
steps: EntityWorkflowStep[];
datas: WorkflowData;
title: string;
isOnHoldAtCurrentStep: boolean;
}
export interface WorkflowData {
persons: Person[];
}
export interface ExportGeneration {
id: string;
type: "export_generation";

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import { computed, useTemplateRef } from "vue";
import type { WorkflowAttachment } from "ChillMainAssets/types";
import {computed, onMounted, ref, useTemplateRef} from "vue";
import type {EntityWorkflow, WorkflowAttachment} from "ChillMainAssets/types";
import PickGenericDocModal from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocModal.vue";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import AttachmentList from "ChillMainAssets/vuejs/WorkflowAttachment/Component/AttachmentList.vue";
import { GenericDoc } from "ChillDocStoreAssets/types";
import {fetch_generic_docs_by_accompanying_period, fetchWorkflow} from "ChillDocStoreAssets/js/generic-doc-api";
interface AppConfig {
workflowId: number;
@@ -34,6 +35,13 @@ const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>(
) as GenericDocForAccompanyingPeriod[],
);
const workflow = ref<EntityWorkflow | null>(null);
onMounted(async () => {
workflow.value = await fetchWorkflow(Number(props.workflowId));
console.log('workflow', workflow.value);
});
const openModal = function () {
pickDocModal.value?.openModal();
};
@@ -53,12 +61,14 @@ const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => {
<template>
<pick-generic-doc-modal
:workflow="workflow"
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc"
ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal>
<attachment-list
:workflow="workflow"
:attachments="props.attachments"
@removeAttachment="onRemoveAttachment"
></attachment-list>

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import { WorkflowAttachment } from "ChillMainAssets/types";
import {EntityWorkflow, WorkflowAttachment} from "ChillMainAssets/types";
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface AttachmentListProps {
attachments: WorkflowAttachment[];
workflow: EntityWorkflow | null;
}
const emit = defineEmits<{
@@ -36,7 +37,7 @@ const props = defineProps<AttachmentListProps>();
:stored-object="a.genericDoc.storedObject"
></document-action-buttons-group>
</li>
<li>
<li v-if="!workflow?.currentStep.isFinal">
<button
type="button"
class="btn btn-delete"

View File

@@ -1,13 +1,12 @@
<script setup lang="ts">
import {
GenericDoc,
GenericDocForAccompanyingPeriod,
} from "ChillDocStoreAssets/types/generic_doc";
import {GenericDoc, GenericDocForAccompanyingPeriod,} from "ChillDocStoreAssets/types/generic_doc";
import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue";
import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api";
import { computed, onMounted, ref } from "vue";
import {fetch_generic_docs_by_accompanying_period} from "ChillDocStoreAssets/js/generic-doc-api";
import {computed, onMounted, ref} from "vue";
import {EntityWorkflow} from "ChillMainAssets/types";
interface PickGenericDocProps {
workflow: EntityWorkflow | null;
accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[];
@@ -36,9 +35,19 @@ const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean =>
) !== -1;
onMounted(async () => {
genericDocs.value = await fetch_generic_docs_by_accompanying_period(
const fetchedGenericDocs = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId,
);
const documentClasses = [
"Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument",
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
"Chill\\DocStoreBundle\\Entity\\PersonDocument"
];
genericDocs.value = fetchedGenericDocs.filter(doc =>
!documentClasses.includes(props.workflow?.relatedEntityClass || '') ||
props.workflow?.relatedEntityId !== doc.identifiers.id
);
loaded.value = true;
});

View File

@@ -3,8 +3,10 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { computed, ref, useTemplateRef } from "vue";
import PickGenericDoc from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDoc.vue";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import {EntityWorkflow} from "ChillMainAssets/types";
interface PickGenericDocModalProps {
workflow: EntityWorkflow | null;
accompanyingPeriodId: number;
toRemove: GenericDocForAccompanyingPeriod[];
}
@@ -80,6 +82,7 @@ defineExpose({ openModal, closeModal });
</template>
<template v-slot:body>
<pick-generic-doc
:workflow="props.workflow"
:accompanying-period-id="props.accompanyingPeriodId"
:to-remove="props.toRemove"
:picked-list="pickeds"

View File

@@ -58,12 +58,14 @@
{% endif %}
</section>
{% if signatures|length > 0 %}
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>
{% endif %}
<section class="step my-4">{% include '@ChillMain/Workflow/_attachment.html.twig' %}</section>
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
{% if signatures|length > 0 %}
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>
{% elseif entity_workflow.currentStep.sends|length > 0 %}
{% if entity_workflow.currentStep.sends|length > 0 %}
<section class="step my-4">
<h2>{{ 'workflow.external_views.title'|trans({'numberOfSends': entity_workflow.currentStep.sends|length }) }}</h2>
{% include '@ChillMain/Workflow/_send_views_list.html.twig' with {'sends': entity_workflow.currentStep.sends} %}

View File

@@ -965,6 +965,31 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/UserJob"
/1.0/main/workflow/{id}.json:
get:
tags:
- workflow
summary: Return a workflow
parameters:
- name: id
in: path
required: true
description: The workflow id
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "ok"
content:
application/json:
schema:
$ref: "#/components/schemas/Workflow"
404:
description: "not found"
401:
description: "Unauthorized"
/1.0/main/workflow/my:
get:
tags: