Compare commits

..

1 Commits

Author SHA1 Message Date
171f7585c2 WIP translation conventions 2025-03-26 11:10:02 +01:00
96 changed files with 697 additions and 1202 deletions

View File

@@ -1,6 +0,0 @@
kind: DX
body: Remove dead code for wopi-link module
time: 2025-04-30T14:45:50.406111606+02:00
custom:
Issue: "352"
SchemaChange: No schema change

View File

@@ -1,7 +0,0 @@
kind: Feature
body: Add the document file name to the document title when a user upload a document,
unless there is already a document title.
time: 2025-04-24T14:22:11.800975422+02:00
custom:
Issue: "377"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Feature
body: Add desactivation date for social action and issue csv export
time: 2025-05-20T09:56:28.108941934+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,7 +0,0 @@
kind: Fixed
body: trying to prevent bug of typeerror in doc-history + improved display of document
history
time: 2025-04-24T13:39:43.878468232+02:00
custom:
Issue: "376"
SchemaChange: No schema change

View File

@@ -1,7 +0,0 @@
kind: Fixed
body: Display previous participation in acc course work even if the person has left
the acc course
time: 2025-04-24T16:37:46.970203594+02:00
custom:
Issue: "381"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix display of text in calendar events
time: 2025-05-05T10:27:15.461493066+02:00
custom:
Issue: "372"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Add missing translation for user_group.no_user_groups
time: 2025-05-14T14:53:39.53927329+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
time: 2025-04-23T17:26:24.45777387+02:00
custom:
Issue: "374"
SchemaChange: No schema change

View File

@@ -1,19 +0,0 @@
## v3.11.0 - 2025-04-17
### Feature
* ([#365](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/365)) Add counters of actions and activities, with 2 boxes to (1) show the number of active actions on total actions and (2) show the number of activities in a accompanying period, and pills in menus for showing the number of active actions and the number of activities.
* ([#364](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/364)) Added a second phone number "telephone2" to the thirdParty entity. Adapted twig templates and vuejs apps to handle this phone number
**Schema Change**: Add columns or tables
* Signature: add a button to go directly to the signature zone, even if there is only one
### Fixed
* Fixed wrong translations in the on-the-fly for creation of thirdParty
* Fixed update of phone number in on-the-fly edition of thirdParty
* Fixed closing of modal when editing thirdParty in accompanying course works
* Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day
* ([#102](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/102)) Fix display of title in document list
* When cleaning the old stored object versions, do not throw an error if the stored object is not found on disk
* Add consistent log prefix and key to logs when stale workflows are automatically canceled
* ([#380](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/380)) Remove the "not null" validation constraint on recently added properties on HouseholdComposition
### DX
* Add new chill-col style for displaying title and aside in a flex table

View File

@@ -220,7 +220,6 @@ framework:
- attenteModification
- attenteMiseEnForme
- attenteValidationMiseEnForme
- attenteSignature
- attenteVisa
- postSignature
- attenteTraitement

View File

@@ -0,0 +1,107 @@
Translations
************
One source of truth
===================
As of January 2025 we have opted to use one source of truth for translations in our backend as well as our frontend.
You will find translations still being present in i18ns files for our vue components, but these will slowly be replaced.
The goal is to only use the messages.{locale}.yaml files to create our translations and keys.
Each time we do `symfony console cache:clear` a javascript and typescript file are generated containing all the keys and the corresponding translations.
These can then be imported into our vue components together with the `trans` method, for use in the vue templates.
Vue import example
^^^^^^^^^^^^^^^^^^
. code-block:: js
import {
ACTIVITY_BLOC_PERSONS,
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
ACTIVITY_BLOC_THIRDPARTY,
ACTIVITY_BLOC_USERS,
ACTIVITY_ADD_PERSONS,
trans,
} from "translator";
Setup
=====
For development purposes we generally make use of the chill-bundles standalone project. Here the new translation setup will work out of the box.
However when working on a customer chill instance (with a root project and a chill-bundles implementation) it is required to execute the chill translations recipe
using the command
Translation key conventions
===========================
When adding new translation keys we have chosen to adhere to the following conventions as of April 2025.
Older translation keys will gradually be adapted to respect these conventions.
Conventions
^^^^^^^^^^^
Entity related messages
-----------------------
Translation keys will be structured as followed as follows:
`[bundle].[entity].[page or component].[action / message]`
. code-block:: yaml
person:
household:
index:
edit_comment: "Mettre à jour le commentaire"
So the key to be used will be `person.household.index.edit_comment` when used in a twig template
or
`PERSON_HOUSEHOLD_INDEX_EDIT_COMMENT` when used in a vue component.
Export related messages
-----------------------
Translation keys will be structured as followed as follows:
[bundle]
|
|__ export
|
|__ ['count | list' | 'filter' | 'aggregator']
|
|__ [export | filter | aggregator - name] OR [ properties (end of hierarchy) ]
|
|__ [action / message]
ex. Filter
. code-block:: yaml
activity:
export:
filter:
by_users_job:
title: Filtrer les échanges par type
'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%'
ex. Export type
. code-block:: yaml
activity:
export:
count:
count_persons_on_activity:
title: Nombre d'usagers concernés par les échanges
list:
activities_by_parcours:
title: Liste des échanges liés à un parcours
ex. Export properties shared by a certain export type
. code-block:: yaml
activity:
export:
list:
users ids: Identifiant des utilisateurs

View File

@@ -11,7 +11,6 @@
"@hotwired/stimulus": "^3.0.0",
"@luminateone/eslint-baseline": "^1.0.9",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5",

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@@ -24,30 +23,22 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(
protected Security $security,
protected TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
) {}
public function __construct(protected Security $security, protected TranslatorInterface $translator) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$period = $parameters['accompanyingCourse'];
$activities = $this->managerRegistry->getManager()->getRepository(Activity::class)->findBy(
['accompanyingPeriod' => $period]
);
if (
AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
&& $this->security->isGranted(ActivityVoter::SEE, $period)
) {
$menu->addChild($this->translator->trans('Activities'), [
$menu->addChild($this->translator->trans('Activity'), [
'route' => 'chill_activity_activity_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 40, 'counter' => count($activities) > 0 ? count($activities) : null]);
->setExtras(['order' => 40]);
}
}

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\Person;
@@ -24,20 +23,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(
private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
private AuthorizationCheckerInterface $authorizationChecker,
private TranslatorInterface $translator,
) {}
public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) {}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var Person $person */
$person = $parameters['person'];
$count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE);
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {
$menu->addChild(
$this->translator->trans('Activities'),
@@ -46,7 +38,7 @@ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => ['person_id' => $person->getId()],
]
)
->setExtras(['order' => 201, 'counter' => $count > 0 ? $count : null]);
->setExtra('order', 201);
}
}

View File

@@ -120,34 +120,3 @@ li.document-list-item {
vertical-align: baseline;
}
}
.badge-activity-type-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove #9acd32;
border-radius: $badge-border-radius;
color: black;
font-weight: normal;
font-size: unset;
max-width: 100%;
background-color: $gray-100;
overflow: hidden;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: #9acd32;
}
}

View File

@@ -11,7 +11,7 @@ import Location from "./components/Location.vue";
export default {
name: "App",
props: ["hasSocialIssues", "hasLocation", "hasPerson", "isSimpleEditor"],
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
components: {
ConcernedGroups,
SocialIssuesAcc,

View File

@@ -14,21 +14,18 @@ const i18n = _createI18n(activityMessages);
const hasSocialIssues = document.querySelector("#social-issues-acc") !== null;
const hasLocation = document.querySelector("#location") !== null;
const hasPerson = document.querySelector("#add-persons") !== null;
const isSimpleEditor = true;
const app = createApp({
template: `<app
:hasSocialIssues="hasSocialIssues"
:hasLocation="hasLocation"
:hasPerson="hasPerson"
:isSimpleEditor = "isSimpleEditor"
></app>`,
data() {
return {
hasSocialIssues,
hasLocation,
hasPerson,
isSimpleEditor
};
},
})

View File

@@ -13,8 +13,7 @@
{% endif %}
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.isFailure %}
@@ -22,13 +21,19 @@
{% endif %}
<div>
<div>
<div class="badge-activity-type-simple">
{% if activity.accompanyingPeriod is not null and context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<div class="badge-activity-type">
<span class="title_label"></span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
</div>
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</div>
</div>
<div class="denomination h2">
@@ -40,17 +45,12 @@
</div>
{% endif %}
</div>
<div class="aside">
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.createdAt|format_date('short') }}</span>
</div>
{% if activity.accompanyingPeriod is not null and context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -1,6 +1,5 @@
@import '~ChillMainAssets/module/bootstrap/shared';
@import '~ChillPersonAssets/chill/scss/mixins.scss';
@import 'bootstrap/scss/_badge.scss';
@import '~ChillMainAssets/module/bootstrap/shared';
.badge-calendar {
display: inline-block;
@@ -24,35 +23,3 @@
}
}
.badge-calendar-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove $chill-l-gray;
border-radius: $badge-border-radius;
max-width: 100%;
background-color: $gray-100;
color: black;
font-weight: normal;
overflow: hidden;
font-weight: normal;
font-size: unset;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: $chill-l-gray;
}
}

View File

@@ -16,7 +16,7 @@ div.calendar-list {
}
& > a.calendar-list__global {
display: inline-block;
display: inline-block;;
padding: 0.2rem;
min-width: 2rem;
border: 1px solid var(--bs-chill-blue);

View File

@@ -96,23 +96,23 @@
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="{ event }">
<span :class="eventClasses(event)">
<b v-if="event.extendedProps.is === 'remote'">{{
event.title
<template v-slot:eventContent="{ arg }: { arg: { event: EventApi } }">
<span :class="eventClasses(arg.event)">
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr) }} -
{{ event.extendedProps.locationName }}</b
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.event.startStr }} -
{{ arg.event.extendedProps.locationName }}</b
>
<b v-else-if="event.extendedProps.is === 'local'">{{
event.title
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else>no 'is'</b>
<a
v-if="event.extendedProps.is === 'range'"
v-if="arg.event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(event)"
@click.prevent="onClickDelete(arg.event)"
>
</a>
</span>
@@ -221,12 +221,13 @@ import type {
DatesSetArg,
EventInput,
} from "@fullcalendar/core";
import { computed, ref, onMounted } from "vue";
import { reactive, computed, ref, onMounted } from "vue";
import { useStore } from "vuex";
import { key } from "./store";
import FullCalendar from "@fullcalendar/vue3";
import frLocale from "@fullcalendar/core/locales/fr";
import interactionPlugin, {
DropArg,
EventResizeDoneArg,
} from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
@@ -236,13 +237,19 @@ import {
EventDropArg,
EventClickArg,
} from "@fullcalendar/core";
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
import {
dateToISO,
ISOToDate,
} from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
import VueMultiselect from "vue-multiselect";
import { Location } from "ChillMainAssets/types";
import { Location } from "../../../../../ChillMainBundle/Resources/public/types";
import EditLocation from "./Components/EditLocation.vue";
import { useI18n } from "vue-i18n";
const store = useStore(key);
const { t } = useI18n();
const showWeekends = ref(false);
const slotDuration = ref("00:15:00");
const slotMinTime = ref("09:00:00");
@@ -294,11 +301,6 @@ const nextWeeks = computed((): Weeks[] =>
}),
);
const formatDate = (datetime: string) => {
console.log(typeof datetime);
return ISOToDate(datetime);
};
const baseOptions = ref<CalendarOptions>({
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
@@ -351,7 +353,7 @@ const pickedLocation = computed<Location | null>({
* return the show classes for the event
* @param arg
*/
const eventClasses = function (): object {
const eventClasses = function (arg: EventApi): object {
return { calendarRangeItems: true };
};
@@ -429,6 +431,7 @@ function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
if (payload.event.extendedProps.is !== "range") {
return;
}
const changedEvent = payload.event;
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,

View File

@@ -6,8 +6,7 @@
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.storedObject.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.storedObject.isFailure %}
@@ -15,8 +14,15 @@
{% endif %}
<div>
{% if c.accompanyingPeriod is not null and context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<span class="badge-calendar-simple">
<span class="badge-calendar">
<span class="title_label"></span>
<span class="title_action">
{{ 'Calendar'|trans }}
{% if c.endDate.diff(c.startDate).days >= 1 %}
{{ c.startDate|format_datetime('short', 'short') }}
@@ -26,6 +32,7 @@
- {{ c.endDate|format_datetime('none', 'short') }}
{% endif %}
</span>
</span>
</div>
<div class="denomination h2">
@@ -37,17 +44,12 @@
</div>
{% endif %}
</div>
<div class="aside">
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
</div>
{% if c.accompanyingPeriod is not null and context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -10,9 +10,6 @@ const startApp = (
collectionEntry: null | HTMLLIElement,
): void => {
console.log("app started", divElement);
const inputTitle = collectionEntry?.querySelector("input[type='text']");
const input_stored_object: HTMLInputElement | null =
divElement.querySelector("input[data-stored-object]");
if (null === input_stored_object) {
@@ -29,10 +26,9 @@ const startApp = (
const app = createApp({
template:
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
data() {
data(vm) {
return {
existingDoc: existingDoc,
inputTitle: inputTitle,
};
},
components: {
@@ -42,13 +38,10 @@ const startApp = (
addDocument: function ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
stored_object.title = file_name;
console.log("object added", stored_object);
console.log("version added", stored_object_version);
this.$data.existingDoc = stored_object;
@@ -56,11 +49,6 @@ const startApp = (
input_stored_object.value = JSON.stringify(
this.$data.existingDoc,
);
if (this.$data.inputTitle) {
if (!this.$data.inputTitle?.value) {
this.$data.inputTitle.value = file_name;
}
}
},
removeDocument: function (object: StoredObject): void {
console.log("catch remove document", object);

View File

@@ -2,28 +2,26 @@
<teleport to="body">
<modal v-if="modalOpen" @close="modalOpen = false">
<template v-slot:header>
<h2>{{ trans(SIGNATURES_SIGNATURE_CONFIRMATION) }}</h2>
<h2>{{ $t("signature_confirmation") }}</h2>
</template>
<template v-slot:body>
<div class="signature-modal-body text-center" v-if="loading">
<p>
{{ trans(SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS) }}
</p>
<p>{{ $t("electronic_signature_in_progress") }}</p>
<div class="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-3x"
:title="trans(SIGNATURES_LOADING)"
:title="$t('loading')"
></i>
</div>
</div>
<div class="signature-modal-body text-center" v-else>
<p>{{ trans(SIGNATURES_YOU_ARE_GOING_TO_SIGN) }}</p>
<p>{{ trans(SIGNATURES_ARE_YOU_SURE) }}</p>
<p>{{ $t("you_are_going_to_sign") }}</p>
<p>{{ $t("are_you_sure") }}</p>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-action" @click.prevent="confirmSign">
{{ trans(SIGNATURES_YES) }}
{{ $t("yes") }}
</button>
</template>
</modal>
@@ -84,39 +82,28 @@
@change="toggleMultiPage"
/>
<label class="form-check-label" for="checkboxMulti">
{{ trans(SIGNATURES_ALL_PAGES) }}
{{ $t("all_pages") }}
</label>
</template>
</div>
<div
v-if="signature.zones.length === 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0"
class="col-5 p-0 text-center turnSignature"
>
<button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button>
</div>
<div
v-if="signature.zones.length > 1"
class="col-5 p-0 text-center turnSignature"
>
<button
:disabled="isFirstSignatureZone()"
:disabled="isFirstSignatureZone"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ trans(SIGNATURES_LAST_ZONE) }}
{{ $t("last_zone") }}
</button>
<span>|</span>
<button
:disabled="isLastSignatureZone()"
:disabled="isLastSignatureZone"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
{{ trans(SIGNATURES_NEXT_ZONE) }}
{{ $t("next_zone") }}
</button>
</div>
<div class="col text-end" v-if="signedState !== 'signed'">
@@ -125,9 +112,9 @@
:hidden="!userSignatureZone"
@click="undoSign"
v-if="signature.zones.length > 1"
:title="trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE)"
:title="$t('choose_another_signature')"
>
{{ trans(SIGNATURES_ANOTHER_ZONE) }}
{{ $t("another_zone") }}
</button>
<button
class="btn btn-misc btn-sm"
@@ -135,7 +122,7 @@
@click="undoSign"
v-else
>
{{ trans(SIGNATURES_CANCEL) }}
{{ $t("cancel") }}
</button>
<button
v-if="userSignatureZone === null"
@@ -147,7 +134,7 @@
active: canvasEvent === 'add',
}"
@click="toggleAddZone()"
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
:title="$t('add_sign_zone')"
>
<template v-if="canvasEvent === 'add'">
<div
@@ -199,70 +186,48 @@
@change="toggleMultiPage"
/>
<label class="form-check-label" for="checkboxMulti">
{{ trans(SIGNATURES_SEE_ALL_PAGES) }}
{{ $t("see_all_pages") }}
</label>
</template>
</div>
<div
v-if="signature.zones.length === 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0 && signedState !== 'signed'"
class="col-4 d-xl-none text-center turnSignature p-0"
>
<button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button>
</div>
<div
v-if="signature.zones.length > 1 && signedState !== 'signed'"
class="col-4 d-xl-none text-center turnSignature p-0"
>
<button
:disabled="isFirstSignatureZone()"
:disabled="!hasSignatureZoneSelected"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ trans(SIGNATURES_LAST_ZONE) }}
{{ $t("last_zone") }}
</button>
<span>|</span>
<button
:disabled="isLastSignatureZone()"
:disabled="isLastSignatureZone"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
{{ trans(SIGNATURES_NEXT_ZONE) }}
{{ $t("next_zone") }}
</button>
</div>
<div
v-if="signature.zones.length === 1 && signedState !== 'signed'"
v-if="signature.zones.length > 0 && signedState !== 'signed'"
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
>
<button
class="btn btn-light btn-sm"
@click="goToSignatureZoneUnique"
>
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
</button>
</div>
<div
v-if="signature.zones.length > 1 && signedState !== 'signed'"
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
>
<button
:disabled="isFirstSignatureZone()"
:disabled="isFirstSignatureZone"
class="btn btn-light btn-sm"
@click="turnSignature(-1)"
>
{{ trans(SIGNATURES_LAST_SIGN_ZONE) }}
{{ $t("last_sign_zone") }}
</button>
<span>|</span>
<button
:disabled="isLastSignatureZone()"
:disabled="isLastSignatureZone"
class="btn btn-light btn-sm"
@click="turnSignature(1)"
>
{{ trans(SIGNATURES_NEXT_SIGN_ZONE) }}
{{ $t("next_sign_zone") }}
</button>
</div>
<div class="col text-end" v-if="signedState !== 'signed'">
@@ -272,7 +237,7 @@
@click="undoSign"
v-if="signature.zones.length > 1"
>
{{ trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE) }}
{{ $t("choose_another_signature") }}
</button>
<button
class="btn btn-misc btn-sm"
@@ -280,7 +245,7 @@
@click="undoSign"
v-else
>
{{ trans(SIGNATURES_CANCEL) }}
{{ $t("cancel") }}
</button>
<button
v-if="userSignatureZone === null"
@@ -292,13 +257,13 @@
active: canvasEvent === 'add',
}"
@click="toggleAddZone()"
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
:title="$t('add_sign_zone')"
>
<template v-if="canvasEvent !== 'add'">
{{ trans(SIGNATURES_ADD_ZONE) }}
{{ $t("add_zone") }}
</template>
<template v-else>
{{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }}
{{ $t("click_on_document") }}
<div
class="spinner-border spinner-border-sm"
role="status"
@@ -332,10 +297,10 @@
v-if="signedState !== 'signed'"
:href="getReturnPath()"
>
{{ trans(SIGNATURES_CANCEL) }}
{{ $t("cancel") }}
</a>
<a class="btn btn-misc" v-else :href="getReturnPath()">
{{ trans(SIGNATURES_RETURN) }}
{{ $t("return") }}
</a>
</div>
<div class="col text-end" v-if="signedState !== 'signed'">
@@ -344,7 +309,7 @@
:disabled="!userSignatureZone"
@click="sign"
>
{{ trans(SIGNATURES_SIGN) }}
{{ $t("sign") }}
</button>
</div>
<div class="col-4" v-else></div>
@@ -353,7 +318,7 @@
</template>
<script setup lang="ts">
import { ref, Ref } from "vue";
import { ref, Ref, computed } from "vue";
import { useToast } from "vue-toast-notification";
import "vue-toast-notification/dist/theme-sugar.css";
import {
@@ -364,38 +329,13 @@ import {
SignedState,
ZoomLevel,
} from "../../types";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import * as pdfjsLib from "pdfjs-dist";
import {
PDFDocumentProxy,
PDFPageProxy,
} from "pdfjs-dist/types/src/display/api";
import {
SIGNATURES_YES,
SIGNATURES_ARE_YOU_SURE,
SIGNATURES_YOU_ARE_GOING_TO_SIGN,
SIGNATURES_SIGNATURE_CONFIRMATION,
SIGNATURES_SIGN,
SIGNATURES_CHOOSE_ANOTHER_SIGNATURE,
SIGNATURES_CANCEL,
SIGNATURES_LAST_SIGN_ZONE,
SIGNATURES_NEXT_SIGN_ZONE,
SIGNATURES_ADD_SIGN_ZONE,
SIGNATURES_CLICK_ON_DOCUMENT,
SIGNATURES_LAST_ZONE,
SIGNATURES_NEXT_ZONE,
SIGNATURES_ADD_ZONE,
SIGNATURES_ANOTHER_ZONE,
SIGNATURES_ELECTRONIC_SIGNATURE_IN_PROGRESS,
SIGNATURES_LOADING,
SIGNATURES_RETURN,
SIGNATURES_SEE_ALL_PAGES,
SIGNATURES_ALL_PAGES,
SIGNATURES_GO_TO_SIGNATURE_UNIQUE,
trans,
} from "translator";
// @ts-ignore incredible but the console.log is needed
import * as PdfWorker from "pdfjs-dist/build/pdf.worker.mjs";
console.log(PdfWorker);
@@ -476,15 +416,19 @@ const $toast = useToast();
const signature = window.signature;
const isFirstSignatureZone = () =>
userSignatureZone.value?.index != null
? userSignatureZone.value.index < 1
: false;
userSignatureZone.value?.index ? userSignatureZone.value.index < 1 : false;
const isLastSignatureZone = () =>
userSignatureZone.value?.index
? userSignatureZone.value.index >= signature.zones.length - 1
: false;
/**
* Return true if the user has selected a user zone (existing on the doc or created by the user)
*/
const hasSignatureZoneSelected = computed<boolean>(
() => userSignatureZone.value !== null,
);
const setZoomLevel = async (zoomLevel: string) => {
zoom.value = Number.parseFloat(zoomLevel);
await resetPages();
@@ -656,15 +600,6 @@ const turnPage = async (upOrDown: number) => {
}
};
const selectZoneInCanvas = (signatureZone: SignatureZone) => {
page.value = signatureZone.PDFPage.index + 1;
const canvas = getCanvas(signatureZone.PDFPage.index + 1);
selectZone(signatureZone, canvas);
canvas.scrollIntoView();
};
const goToSignatureZoneUnique = () => selectZoneInCanvas(signature.zones[0]);
const turnSignature = async (upOrDown: number) => {
let zoneIndex = userSignatureZone.value?.index ?? -1;
if (zoneIndex < -1) {
@@ -677,7 +612,10 @@ const turnSignature = async (upOrDown: number) => {
}
let currentZone = signature.zones[zoneIndex];
if (currentZone) {
selectZoneInCanvas(currentZone);
page.value = currentZone.PDFPage.index + 1;
const canvas = getCanvas(currentZone.PDFPage.index + 1);
selectZone(currentZone, canvas);
canvas.scrollIntoView();
}
};

View File

@@ -23,7 +23,6 @@ const emit =
{
stored_object_version: StoredObjectVersionCreated,
stored_object: StoredObject,
file_name: string,
},
) => void
>();
@@ -115,21 +114,7 @@ const handleFile = async (file: File): Promise<void> => {
persisted: false,
};
const fileName = file.name;
let file_name = "Nouveau document";
const file_name_split = fileName.split(".");
if (file_name_split.length > 1) {
const extension = file_name_split
? file_name_split[file_name_split.length - 1]
: "";
file_name = fileName.replace(extension, "").slice(0, -1);
}
emit("addDocument", {
stored_object,
stored_object_version,
file_name: file_name,
});
emit("addDocument", { stored_object, stored_object_version });
uploading.value = false;
};
</script>

View File

@@ -20,7 +20,6 @@ const emit = defineEmits<{
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
file_name: string,
},
): void;
(e: "removeDocument"): void;
@@ -43,16 +42,14 @@ const buttonState = computed<"add" | "replace">(() => {
function onAddDocument({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void {
const message =
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
$toast.success(message);
emit("addDocument", { stored_object_version, stored_object, file_name });
emit("addDocument", { stored_object_version, stored_object });
state.showModal = false;
}

View File

@@ -19,7 +19,6 @@ const emit = defineEmits<{
{
stored_object: StoredObject,
stored_object_version: StoredObjectVersion,
file_name: string,
},
): void;
(e: "removeDocument"): void;
@@ -54,13 +53,11 @@ const dav_link_href = computed<string | undefined>(() => {
const onAddDocument = ({
stored_object,
stored_object_version,
file_name,
}: {
stored_object: StoredObject;
stored_object_version: StoredObjectVersion;
file_name: string;
}): void => {
emit("addDocument", { stored_object, stored_object_version, file_name });
emit("addDocument", { stored_object, stored_object_version });
};
const onRemoveDocument = (e: Event): void => {

View File

@@ -53,7 +53,7 @@ const onRestored = ({
<template>
<template v-if="props.versions.length > 0">
<div class="container">
<template v-for="v in props.versions" :key="v.id">
<template v-for="v in props.versions">
<history-button-list-item
:version="v"
:can-edit="canEdit"

View File

@@ -32,17 +32,13 @@ const onRestore = ({
emit("restoreVersion", { newVersion });
};
const isKeptBeforeConversion = computed<boolean>(() => {
if ("point-in-times" in props.version) {
return props.version["point-in-times"].reduce(
const isKeptBeforeConversion = computed<boolean>(() =>
props.version["point-in-times"].reduce(
(accumulator: boolean, pit: StoredObjectPointInTime) =>
accumulator || "keep-before-conversion" === pit.reason,
false,
);
} else {
return false;
}
});
),
);
const isRestored = computed<boolean>(
() => props.version.version > 0 && null !== props.version["from-restored"],
@@ -94,11 +90,11 @@ const classes = computed<{
<div class="col-12">
<file-icon :type="version.type"></file-icon>
<span
><strong>&nbsp;#{{ version.version + 1 }}&nbsp;</strong></span
><strong>#{{ version.version + 1 }}</strong></span
>
<template
v-if="version.createdBy !== null && version.createdAt !== null"
><strong v-if="version.version == 0">créé par</strong
><strong v-if="version.version == 0">Créé par</strong
><strong v-else>modifié par</strong>
<span class="badge-user"
><UserRenderBoxBadge

View File

@@ -23,7 +23,7 @@ License * along with this program. If not, see <http://www.gnu.org/licenses/>.
{{ encore_entry_link_tags("mod_document_action_buttons_group") }}
{% endblock %} {% block content %}
<div class="document-list">
<div class="col-md-10 col-xxl">
<h1>
{{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}
</h1>

View File

@@ -3,15 +3,27 @@
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
<div class="item-row">
<!-- person document or accompanying course document -->
<div class="item-two-col-grid">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.object.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.object.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
{% if context == 'person' and accompanyingCourse is defined %}
<div>
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
</span>&nbsp;
</div>
{% elseif context == 'accompanying-period' and person is defined %}
<div>
<span class="badge bg-primary">
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
</span>&nbsp;
</div>
{% endif %}
<div class="denomination h2">
{{ document.title|chill_print_or_message("No title") }}
</div>
@@ -20,39 +32,25 @@
{{ mm.mimeIcon(document.object.type) }}
</div>
{% endif %}
{% if document.category %}
<div>
<p>{{ document.category.name|localize_translatable_string }}</p>
</div>
{% endif %}
{% if document.object.hasTemplate %}
<div>
<p>{{ document.object.template.name|localize_translatable_string }}</p>
</div>
{% endif %}
</div>
<div class="item-col">
<div class="container">
{% if document.date is not null %}
<div class="aside">
<div class="dates row text-end">
<span>{{ document.date|format_date('short') }}</span>
</div>
{% if context == 'person' and accompanyingCourse is defined %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
</span>&nbsp;
</div>
{% elseif context == 'accompanying-period' and person is defined %}
<div class="text-end">
<span class="badge bg-primary">
{{ document.person|chill_entity_render_string }}
</span>&nbsp;
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% if document.description is not empty %}
<div class="item-row">

View File

@@ -62,15 +62,7 @@ final readonly class RemoveOldVersionMessageHandler implements MessageHandlerInt
$storedObject = $storedObjectVersion->getStoredObject();
if ($this->storedObjectManager->exists($storedObjectVersion)) {
$this->storedObjectManager->delete($storedObjectVersion);
} else {
$this->logger->notice(
self::LOG_PREFIX.'Stored object version does not exists any more.',
['storedObjectVersionName' => $storedObjectVersion->getFilename()],
);
}
// to ensure an immediate deletion
$this->entityManager->remove($storedObjectVersion);

View File

@@ -44,7 +44,6 @@ class RemoveOldVersionMessageHandlerTest extends TestCase
$entityManager->expects($this->once())->method('clear');
$storedObjectManager = $this->createMock(StoredObjectManagerInterface::class);
$storedObjectManager->expects($this->once())->method('exists')->willReturn(true);
$storedObjectManager->expects($this->once())->method('delete')->with($this->identicalTo($version));
$handler = new RemoveOldVersionMessageHandler($storedObjectVersionRepository, new NullLogger(), $entityManager, $storedObjectManager, new MockClock());
@@ -52,29 +51,6 @@ class RemoveOldVersionMessageHandlerTest extends TestCase
$handler(new RemoveOldVersionMessage(1));
}
public function testInvokeForVersionNotExisting(): void
{
$object = new StoredObject();
$version = $object->registerVersion();
$storedObjectVersionRepository = $this->createMock(StoredObjectVersionRepository::class);
$storedObjectVersionRepository->expects($this->once())->method('find')
->with($this->identicalTo(1))
->willReturn($version);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects($this->once())->method('remove')->with($this->identicalTo($version));
$entityManager->expects($this->once())->method('flush');
$entityManager->expects($this->once())->method('clear');
$storedObjectManager = $this->createMock(StoredObjectManagerInterface::class);
$storedObjectManager->expects($this->once())->method('exists')->willReturn(false);
$storedObjectManager->expects($this->never())->method('delete')->with($this->identicalTo($version));
$handler = new RemoveOldVersionMessageHandler($storedObjectVersionRepository, new NullLogger(), $entityManager, $storedObjectManager, new MockClock());
$handler(new RemoveOldVersionMessage(1));
}
public function testInvokeWithStoredObjectToDelete(): void
{
$object = new StoredObject();
@@ -147,6 +123,6 @@ class DummyStoredObjectManager implements StoredObjectManagerInterface
public function exists(StoredObject|StoredObjectVersion $document): bool
{
return true;
throw new \RuntimeException();
}
}

View File

@@ -99,30 +99,3 @@ CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE: Modifier un document
entity_display_title:
Document (n°%doc%): "Document (n°%doc%)"
Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval%
# SIGNATURES
signatures:
yes: Oui
are_you_sure: Êtes-vous sûr·e?
you_are_going_to_sign: Vous allez signer le document
signature_confirmation: Confirmation de la signature
sign: Signer
choose_another_signature: Choisir une autre zone
cancel: Annuler
last_sign_zone: Zone de signature précédente
next_sign_zone: Zone de signature suivante
add_sign_zone: Ajouter une zone de signature
click_on_document: Cliquer sur le document
last_zone: Zone précédente
next_zone: Zone suivante
add_zone: Ajouter une zone
another_zone: Autre zone
electronic_signature_in_progress: Signature électronique en cours...
loading: Chargement...
remove_sign_zone: Enlever la zone
return: Retour
see_all_pages: Voir toutes les pages
all_pages: Toutes les pages
go_to_signature_unique: Aller à la zone de signature

View File

@@ -33,8 +33,6 @@
@import './scss/hover.scss';
@import './scss/comment-editor.scss';
/*
* BASE LAYOUT POSITION
*/

View File

@@ -1,39 +0,0 @@
.comment-container {
margin-top: 1.5rem;
}
.toggle-button {
background-color: white;
font-size: .8rem;
text-decoration: none;
position: absolute;
bottom: -10px;
left: 20px;
padding: 2px 6px;
cursor: pointer;
z-index: 10;
transition: left 0.1s ease-in-out;
}
.rich-editor-active .toggle-button {
bottom: 0;
}
.editor-wrapper textarea {
resize: vertical;
min-height: 100px;
}
.editor-container {
position: relative;
display: flex;
flex-direction: column;
}
.editor-wrapper {
position: relative;
}
.hidden-textarea {
display: none;
}

View File

@@ -25,34 +25,7 @@ div.flex-table {
div.item-col:last-child {
display: flex;
}
div.item-two-col-grid {
display: grid;
width: 100%;
justify-content: stretch;
@include media-breakpoint-up(lg) {
grid-template-areas:
"title aside";
grid-template-columns: 1fr minmax(8rem, 1fr);
column-gap: 0.5em;
}
@include media-breakpoint-down(lg) {
grid-template-areas:
"aside"
"title";
}
& > div.title {
grid-area: title;
}
& > div.aside {
grid-area: aside;
}
}
}
}
h2, h3, h4, dl, p {

View File

@@ -8,10 +8,10 @@ import {
Heading,
Link,
List,
} from 'ckeditor5';
import coreTranslations from 'ckeditor5/translations/fr.js';
} from "ckeditor5";
import coreTranslations from "ckeditor5/translations/fr.js";
import 'ckeditor5/ckeditor5.css';
import "ckeditor5/ckeditor5.css";
import "./index.scss";
@@ -41,8 +41,6 @@ export default {
"redo",
],
},
translations: [
coreTranslations
],
translations: [coreTranslations],
licenseKey: "GPL",
} ;
};

View File

@@ -1,23 +1,12 @@
import App from "../../vuejs/CommentEditor/App.vue"
import { createApp, reactive } from "vue";
import config from "./editor_config";
import { ClassicEditor } from "ckeditor5";
const ckeditorFields: NodeListOf<HTMLTextAreaElement> =
document.querySelectorAll("[id^='comment-app']");
const globalState = reactive({
isSimple: localStorage.getItem('editorMode') === 'simple'
});
window.addEventListener('storage', () => {
globalState.isSimple = localStorage.getItem('editorMode') === 'simple';
});
document.querySelectorAll("textarea[ckeditor]");
ckeditorFields.forEach((field: HTMLTextAreaElement): void => {
const app = createApp(App,{
fieldName: field.dataset.fieldName,
template: `<app></app>`
ClassicEditor.create(field, config).catch((error) => {
console.error(error.stack);
throw error;
});
app.provide('globalState', globalState)
.component("app", App)
.mount(field);
});
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));

View File

@@ -10,10 +10,6 @@ let appsPerInput = new Map();
function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
let suggested;
let as_id;
let submit_on_adding_new_entity;
let label;
apps.forEach(function (el) {
const isMultiple = parseInt(el.dataset.multiple) === 1,

View File

@@ -0,0 +1,45 @@
import { createApp } from "vue";
import OpenWopiLink from "ChillMainAssets/vuejs/_components/OpenWopiLink";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({});
//TODO move to chillDocStore or ChillWopi
/*
tags to load module:
<span data-module="wopi-link"
data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.uuid}) }}"
data-doc-type="{{ document.type|e('html_attr') }}"
data-options="{{ options|json_encode }}"
></span>
*/
window.addEventListener("DOMContentLoaded", function (e) {
document
.querySelectorAll('span[data-module="wopi-link"]')
.forEach(function (el) {
createApp({
template:
'<open-wopi-link :wopiUrl="wopiUrl" :type="type" :options="options"></open-wopi-link>',
components: {
OpenWopiLink,
},
data() {
return {
wopiUrl: el.dataset.wopiUrl,
type: el.dataset.docType,
options:
el.dataset.options !== "null"
? JSON.parse(el.dataset.options)
: {},
};
},
})
.use(i18n)
.mount(el);
});
});

View File

@@ -1,36 +0,0 @@
<template>
<div>
<div>
<comment-editor
:isSimple="globalState.isSimple"
:fieldName="fieldName"
@toggle="toggleEditorMode"
></comment-editor>
</div>
</div>
</template>
<script>
import { defineComponent, inject } from 'vue';
import CommentEditor from "../CommentEditor/component/CommentEditor.vue";
export default defineComponent({
name: "App",
components: { CommentEditor },
props: {
fieldName: String
},
setup() {
const globalState = inject('globalState');
const toggleEditorMode = () => {
globalState.isSimple = !globalState.isSimple;
localStorage.setItem('editorMode', globalState.isSimple ? 'simple' : 'rich');
};
return {
globalState,
toggleEditorMode,
};
}
});
</script>

View File

@@ -1,58 +0,0 @@
<template>
<div :class="{'editor-container': true, 'rich-editor-active': !isSimple}">
<div v-if="!isSimple" class="editor-wrapper">
<ckeditor
:name="fieldName"
:editor="classicEditor"
:config="editorConfig"
v-model.lazy="content"
tag-name="textarea"
/>
</div>
<div v-else class="editor-wrapper">
<textarea
v-model.lazy="content"
:name="fieldName"
class="form-control"
></textarea>
</div>
<a @click="toggleSimpleEditor" class="toggle-button">{{ isSimple ? "rich" : "simple" }}</a>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, toRefs } from 'vue';
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import { ClassicEditor } from "ckeditor5";
export default defineComponent({
name: "CommentEditor",
components: {
ckeditor: Ckeditor,
},
props: {
type: String,
isSimple: Boolean,
fieldName: String,
},
setup(props, { emit }) {
const { isSimple } = toRefs(props);
const content = ref("");
const classicEditor = ClassicEditor;
const editorConfig = classicEditorConfig;
const toggleSimpleEditor = () => {
emit("toggle");
};
return {
isSimple,
content,
classicEditor,
editorConfig,
toggleSimpleEditor,
};
}
});
</script>

View File

@@ -1,14 +0,0 @@
import {personMessages} from "ChillPersonAssets/vuejs/_js/i18n";
import {calendarUserSelectorMessages} from "ChillCalendarAssets/vuejs/_components/CalendarUserSelector/js/i18n";
import {activityMessages} from "ChillActivityAssets/vuejs/Activity/i18n";
const appMessages = {
fr: {
mode: {
simple: "Editeur simple",
rich: "Editeur riche"
}
},
};
export { appMessages };

View File

@@ -26,9 +26,9 @@
trans(THIRDPARTY_CONTACT_OF)
}}</span>
<span v-else-if="props.entity.kind === 'company'">{{
trans(THIRDPARTY_A_COMPANY)
trans(THIRDPARTY_A_CONTACT)
}}</span>
<span v-else>{{ trans(THIRDPARTY_A_CONTACT) }}</span>
<span v-else>{{ $t("thirdparty.contact") }}</span>
</template>
</span>
@@ -54,7 +54,6 @@ import {
ACCEPTED_USERS,
THIRDPARTY_A_CONTACT,
THIRDPARTY_CONTACT_OF,
THIRDPARTY_A_COMPANY,
PERSON,
THIRDPARTY,
} from "translator";

View File

@@ -0,0 +1,214 @@
<template>
<a
v-if="isOpenDocument"
class="btn"
:class="[
isChangeIcon ? 'change-icon' : '',
isChangeClass ? options.changeClass : 'btn-wopilink',
]"
@click="openModal"
>
<i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon"></i>
<span v-if="!noText">
{{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
</span>
</a>
<teleport to="body">
<div class="wopi-frame" v-if="isOpenDocument">
<modal
v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
:hideFooter="true"
@close="modal.showModal = false"
>
<template #header>
<img class="logo" :src="logo" height="45" />
<span class="ms-auto me-3">
<span v-if="options.title">{{ options.title }}</span>
</span>
</template>
<template #body>
<div v-if="loading" class="loading">
<i
class="fa fa-circle-o-notch fa-spin fa-3x"
:title="trans(WOPI_LOADING)"
></i>
</div>
<iframe :src="this.wopiUrl" @load="loaded"></iframe>
</template>
</modal>
</div>
<div v-else>
<Modal
v-if="modal.showModal"
modalDialogClass="modal-sm"
@close="modal.showModal = false"
>
<template v-slot:header>
<h3>{{ trans(WOPI_INVALID_TITLE) }}</h3>
</template>
<template v-slot:body>
<div class="alert alert-warning">
{{ trans(WOPI_ONLINE_EDIT_DOCUMENT) }}
</div>
</template>
</Modal>
</div>
</teleport>
</template>
<script setup>
import { ref, computed } from "vue";
import {
trans,
WOPI_ONLINE_EDIT_DOCUMENT,
WOPI_INVALID_TITLE,
WOPI_LOADING,
} from "translator";
import Modal from "ChillMainAssets/vuejs/_components/Modal";
import logo from "ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png";
// Props
const props = defineProps({
wopiUrl: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
options: {
type: Object,
required: false,
},
});
// data
const modal = ref({
showModal: false,
modalDialogClass: "modal-fullscreen",
});
const loading = ref(false);
// MIME types
const mime = [
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
"application/clarisworks",
"application/coreldraw",
"application/macwriteii",
"application/msword",
"application/pdf",
"application/vnd.lotus-1-2-3",
"application/vnd.ms-excel",
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
"application/vnd.ms-excel.sheet.macroEnabled.12",
"application/vnd.ms-excel.template.macroEnabled.12",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
"application/vnd.ms-powerpoint.template.macroEnabled.12",
"application/vnd.ms-visio.drawing",
"application/vnd.ms-word.document.macroEnabled.12",
"application/vnd.ms-word.template.macroEnabled.12",
"application/vnd.ms-works",
"application/vnd.oasis.opendocument.chart",
"application/vnd.oasis.opendocument.formula",
"application/vnd.oasis.opendocument.graphics",
"application/vnd.oasis.opendocument.graphics-flat-xml",
"application/vnd.oasis.opendocument.graphics-template",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.presentation-flat-xml",
"application/vnd.oasis.opendocument.presentation-template",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
"application/vnd.oasis.opendocument.spreadsheet-template",
"application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.text-flat-xml",
"application/vnd.oasis.opendocument.text-master",
"application/vnd.oasis.opendocument.text-master-template",
"application/vnd.oasis.opendocument.text-template",
"application/vnd.oasis.opendocument.text-web",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.sun.xml.calc",
"application/vnd.sun.xml.calc.template",
"application/vnd.sun.xml.chart",
"application/vnd.sun.xml.draw",
"application/vnd.sun.xml.draw.template",
"application/vnd.sun.xml.impress",
"application/vnd.sun.xml.impress.template",
"application/vnd.sun.xml.math",
"application/vnd.sun.xml.writer",
"application/vnd.sun.xml.writer.global",
"application/vnd.sun.xml.writer.template",
"application/vnd.visio",
"application/vnd.visio2013",
"application/vnd.wordperfect",
"application/x-abiword",
"application/x-aportisdoc",
"application/x-dbase",
"application/x-dif-document",
"application/x-fictionbook+xml",
"application/x-gnumeric",
"application/x-hwp",
"application/x-iwork-keynote-sffkey",
"application/x-iwork-numbers-sffnumbers",
"application/x-iwork-pages-sffpages",
"application/x-mspublisher",
"application/x-mswrite",
"application/x-pagemaker",
"application/x-sony-bbeb",
"application/x-t602",
];
// Computed
const isOpenDocument = computed(() => mime.includes(props.type));
const noText = computed(() => props.options?.noText === true);
const isChangeIcon = computed(() => !!props.options?.changeIcon);
const isChangeClass = computed(() => !!props.options?.changeClass);
// Methods
const openModal = () => {
loading.value = true;
modal.value.showModal = true;
};
const loaded = () => {
loading.value = false;
};
</script>
<style lang="scss">
div.wopi-frame {
div.modal-header {
border-bottom: 0;
background-color: var(--bs-primary);
color: white;
}
div.modal-body {
padding: 0;
overflow-y: unset !important;
iframe {
height: 100%;
width: 100%;
}
div.loading {
position: absolute;
color: var(--bs-chill-gray);
top: calc(50% - 30px);
left: calc(50% - 30px);
}
}
}
</style>

View File

@@ -54,11 +54,6 @@ const messages = {
residential_address: "Adresse de résidence",
located_at: "réside chez",
},
comment: {
label: "Commentaire",
editor_simple: "Simple",
editor_rich: "Riche"
}
},
};

View File

@@ -136,59 +136,6 @@
</div>
</div>
<h2>Fix the title in the flex table</h2>
<p>This will fix the layout of the row, with a "title" element, and an aside element. Using <code>css grid</code>, this is quite safe and won't overflow</p>
<xmp>
<div class="flex-table">
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">This is my title</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div>
<div>This is a second line</div>
</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
</div>
</xmp>
<p>will render:</p>
<div class="flex-table">
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">This is my title</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div>
<div>This is a second line</div>
</div>
<div class="aside">Aside value</div>
</div>
</div>
</div>
</div>
<h2>Wrap-list</h2>
<p>Une liste inline qui s'aligne, puis glisse sous son titre.</p>
<div class="wrap-list debug">
@@ -445,12 +392,4 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles
</div>
</div>
<div class="row">
<h1>Badges</h1>
<span class="badge-accompanying-work-type-simple">Action d'accompagnement</span>
<span class="badge-activity-type-simple">Type d'échange</span>
<span class="badge-calendar-simple">Rendez-vous</span>
</div>
{% endblock %}

View File

@@ -214,9 +214,7 @@
{% block private_comment_widget %}
{% for entry in form %}
<div id="comment-app-{{ form.vars.id }}" data-field-name="{{ form.vars.full_name }}">
{{ form_widget(entry, { attr: { ckeditor: 'true' } }) }}
</div>
{{ form_widget(entry) }}
{% endfor %}
{% endblock %}
@@ -226,9 +224,7 @@
{% block comment_widget %}
{% for entry in form %}
<div id="comment-app-{{ form.vars.id }}" data-field-name="{{ form.vars.full_name }}">
{{ form_widget(entry, { attr: { ckeditor: 'true' } }) }}
</div>
{{ form_widget(entry) }}
{% endfor %}
{% endblock comment_widget %}

View File

@@ -9,6 +9,7 @@
{{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('mod_entity_workflow_subscribe') }}
{{ encore_entry_script_tags('page_workflow_show') }}
{{ encore_entry_script_tags('mod_wopi_link') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
{{ encore_entry_script_tags('mod_workflow_attachment') }}
{% endblock %}
@@ -18,6 +19,7 @@
{{ encore_entry_link_tags('mod_pickentity_type') }}
{{ encore_entry_link_tags('mod_entity_workflow_subscribe') }}
{{ encore_entry_link_tags('page_workflow_show') }}
{{ encore_entry_link_tags('mod_wopi_link') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
{{ encore_entry_link_tags('mod_workflow_attachment') }}
{% endblock %}

View File

@@ -25,8 +25,6 @@ use Symfony\Component\Workflow\Transition;
#[AsMessageHandler]
final readonly class CancelStaleWorkflowHandler
{
private const LOG_PREFIX = '[CancelStaleWorkflowHandler] ';
public function __construct(
private EntityWorkflowRepository $workflowRepository,
private Registry $registry,
@@ -42,13 +40,13 @@ final readonly class CancelStaleWorkflowHandler
$workflow = $this->workflowRepository->find($message->getWorkflowId());
if (null === $workflow) {
$this->logger->alert(self::LOG_PREFIX.'Workflow was not found!', ['entityWorkflowId' => $workflowId]);
$this->logger->alert('Workflow was not found!', [$workflowId]);
return;
}
if (false === $workflow->isStaledAt($olderThanDate)) {
$this->logger->alert(self::LOG_PREFIX.'Workflow has transitioned in the meantime.', ['entityWorkflowId' => $workflowId]);
$this->logger->alert('Workflow has transitioned in the meantime.', [$workflowId]);
throw new UnrecoverableMessageHandlingException('the workflow is not staled any more');
}
@@ -69,14 +67,14 @@ final readonly class CancelStaleWorkflowHandler
'transitionAt' => $this->clock->now(),
'transition' => $transition->getName(),
]);
$this->logger->info(self::LOG_PREFIX.'EntityWorkflow has been cancelled automatically.', ['entityWorkflowId' => $workflowId]);
$this->logger->info('EntityWorkflow has been cancelled automatically.', [$workflowId]);
$transitionApplied = true;
break;
}
}
if (!$transitionApplied) {
$this->logger->error(self::LOG_PREFIX.'No valid transition found for EntityWorkflow.', ['entityWorkflowId' => $workflowId]);
$this->logger->error('No valid transition found for EntityWorkflow.', [$workflowId]);
throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId));
}

View File

@@ -86,6 +86,10 @@ module.exports = function (encore, entries) {
"mod_entity_workflow_pick",
__dirname + "/Resources/public/module/entity-workflow-pick/index.js",
);
encore.addEntry(
"mod_wopi_link",
__dirname + "/Resources/public/module/wopi-link/index.js",
);
encore.addEntry(
"mod_pick_postal_code",
__dirname + "/Resources/public/module/pick-postal-code/index.js",

View File

@@ -59,7 +59,6 @@ user_group:
inactive: Inactif
with_users: Membres
no_users: Aucun utilisateur associé
no_user_groups: Aucune groupe d'utilisateurs
no_admin_users: Aucun administrateur
Label: Nom du groupe
BackgroundColor: Couleur de fond du badge
@@ -113,8 +112,6 @@ Any comment: Aucun commentaire
# comment embeddable
No comment associated: Aucun commentaire
private comment: Notes privées
comment_public: Note
comment_private: Note privée
#pagination
Previous: Précédent
@@ -742,7 +739,6 @@ export:
id: Identifiant de l'action
social_issue_id: Identifiant de la problématique sociale
social_issue: Problématique sociale
desactivation_date: Date de désactivation
social_issue_ordering: Ordre de la problématique sociale
action_label: Action d'accompagnement
action_ordering: Ordre

View File

@@ -26,7 +26,7 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface
{
$now = $this->clock->now();
if (null !== $cronJobExecution && $now->sub(new \DateInterval('PT23H45M')) < $cronJobExecution->getLastStart()) {
if (null !== $cronJobExecution && $now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) {
return false;
}

View File

@@ -204,24 +204,20 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
['date' => 'DESC', 'id' => 'DESC'],
);
$activities = \array_slice($activities, 0, 3);
$works = $this->workRepository->findByAccompanyingPeriod(
$accompanyingCourse,
['startDate' => 'DESC', 'endDate' => 'DESC'],
3
);
$counters = [
'activities' => count($activities),
'openWorks' => count($accompanyingCourse->getOpenWorks()),
'works' => count($works),
];
return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [
'accompanyingCourse' => $accompanyingCourse,
'withoutHousehold' => $withoutHousehold,
'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(),
'works' => \array_slice($works, 0, 3),
'activities' => \array_slice($activities, 0, 3),
'counters' => $counters,
'works' => $works,
'activities' => $activities,
]);
}

View File

@@ -511,14 +511,6 @@ class AccompanyingPeriod implements
return $this->getParticipationsContainsPerson($person)->count() > 0;
}
public function getOpenWorks(): Collection
{
return $this->getWorks()->filter(
static fn (AccompanyingPeriodWork $work): bool => null === $work->getEndDate()
or $work->getEndDate() > new \DateTimeImmutable('today')
);
}
/**
* Open a new participation for a person.
*/

View File

@@ -58,11 +58,13 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
private ?int $numberOfChildren = null;
#[Assert\NotNull]
#[Assert\GreaterThanOrEqual(0, groups: ['Default', 'household_composition'])]
#[Serializer\Groups(['docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
private ?int $numberOfDependents = null;
#[Assert\NotNull]
#[Assert\GreaterThanOrEqual(0, groups: ['Default', 'household_composition'])]
#[Serializer\Groups(['docgen:read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]

View File

@@ -22,7 +22,7 @@ use Doctrine\ORM\Mapping as ORM;
class MaritalStatus
{
#[ORM\Id]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 15)]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 7)]
private ?string $id;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]

View File

@@ -71,7 +71,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
->setExtras(['order' => 30]);
*/
$menu->addChild($this->translator->trans('Accompanying Course Comments'), [
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [
'route' => 'chill_person_accompanying_period_comment_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
@@ -80,15 +80,12 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
}
if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Accompanying Course Actions'), [
$menu->addChild($this->translator->trans('Accompanying Course Action'), [
'route' => 'chill_person_accompanying_period_work_list',
'routeParameters' => [
'id' => $period->getId(),
], ])
->setExtras([
'order' => 40,
'counter' => count($period->getWorks()) > 0 ? count($period->getWorks()) : null,
]);
->setExtras(['order' => 40]);
}
$workflow = $this->registry->get($period, 'accompanying_period_lifecycle');

View File

@@ -304,14 +304,5 @@ div#dashboards {
margin: 0;
}
}
div.count-item {
font-size: 3rem;
text-align: center;
}
div.count-item-label {
font-size: 90%;
font-variant: all-small-caps;
text-align: center;
}
}
}

View File

@@ -20,36 +20,6 @@
}
}
.badge-accompanying-work-type-simple {
@extend .badge;
display: inline-block;
margin: 0.2rem 0;
padding-left: 0;
padding-right: 0.5rem;
border-left: 20px groove $orange;
border-radius: $badge-border-radius;
max-width: 100%;
background-color: $gray-100;
color: black;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
text-indent: 5px hanging;
text-align: left;
&::before {
margin-right: 3px;
position: relative;
left: -0.5px;
font-family: ForkAwesome;
content: '\f04b';
color: #e2793d;
}
}
/// AccompanyingCourse Work Pages
div.accompanying-course-work {

View File

@@ -61,15 +61,15 @@
</template>
<script>
import {ClassicEditor} from "ckeditor5";
import {Ckeditor} from "@ckeditor/ckeditor5-vue";
import { ClassicEditor } from "ckeditor5";
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import { mapState } from "vuex";
export default {
name: "Comment",
components: {
ckeditor: Ckeditor
ckeditor: Ckeditor,
},
data() {
return {

View File

@@ -204,8 +204,7 @@ export default {
} else if (payload.type === "thirdparty") {
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.telephone;
body.telephone2 = payload.data.telephone2;
body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
makeFetch(

View File

@@ -385,8 +385,7 @@ export default {
} else if (payload.type === "thirdparty") {
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.telephone;
body.telephone2 = payload.data.telephone2;
body.telephone = payload.data.phonenumber;
if (payload.data.address) {
body.address = { id: payload.data.address.address_id };
}

View File

@@ -194,7 +194,6 @@ export default {
body.name = payload.data.name;
body.email = payload.data.email;
body.telephone = payload.data.telephone;
body.telephone2 = payload.data.telephone2;
body.address = payload.data.address
? { id: payload.data.address.address_id }
: null;

View File

@@ -39,15 +39,15 @@
<script>
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { Ckeditor }from "@ckeditor/ckeditor5-vue";
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import {ClassicEditor} from "ckeditor5";
import { ClassicEditor } from "ckeditor5";
export default {
name: "WriteComment",
components: {
Modal,
ckeditor: Ckeditor
ckeditor: Ckeditor,
},
props: ["resource"],
emits: ["updateComment"],

View File

@@ -46,7 +46,8 @@
<label class="col-form-label">{{ $t("comments") }}</label>
<ckeditor
v-model="note"
:editor="classicEditor" :config="editorConfig"
:editor="classicEditor"
:config="editorConfig"
tag-name="textarea"
></ckeditor>
</div>
@@ -207,29 +208,6 @@
</label>
</div>
</li>
<li
v-for="p in getPreviousPersons"
:key="p.id"
class="alert alert-danger"
>
<div class="form-check">
<input
v-model="personsPicked"
:value="p.id"
type="checkbox"
class="me-2 form-check-input"
:id="'person_check' + p.id"
/>
<label :for="'person_check' + p.id" class="form-check-label">
<person-text :person="p"></person-text>
</label>
</div>
<span
><i class="fa fa-warning"></i>&nbsp;{{
$t("warning_previous_persons")
}}</span
>
</li>
</ul>
</div>
@@ -462,7 +440,7 @@
import { mapState, mapGetters } from "vuex";
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import {ClassicEditor} from "ckeditor5";
import { ClassicEditor } from "ckeditor5";
import AddResult from "./components/AddResult.vue";
import AddEvaluation from "./components/AddEvaluation.vue";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
@@ -519,8 +497,6 @@ const i18n = {
notification_notify_referrer: "Notifier le référent",
notification_notify_any: "Notifier d'autres utilisateurs",
notification_send: "Envoyer une notification",
warning_previous_persons:
"Cet usager n'est désormais plus concerné par le parcours, bien qu'il ait été associé à l'action par le passé.",
},
},
};
@@ -607,7 +583,6 @@ export default {
"hasHandlingThirdParty",
"hasThirdParties",
"hasReferrers",
"getPreviousPersons",
]),
classicEditor: () => ClassicEditor,
editorConfig: () => classicEditorConfig,
@@ -770,8 +745,7 @@ export default {
let body = { type: payload.type };
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.telephone;
body.telephone2 = payload.data.telephone2;
body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
makeFetch(
@@ -781,9 +755,7 @@ export default {
)
.then((response) => {
this.$store.dispatch("updateThirdParty", response);
for (let otf of this.$refs.onTheFly) {
otf.closeModal();
}
this.$refs.onTheFly.closeModal();
})
.catch((error) => {
if (error.name === "ValidationException") {

View File

@@ -273,7 +273,7 @@
<script>
import { ISOToDatetime } from "ChillMainAssets/chill/js/date";
import {Ckeditor} from "@ckeditor/ckeditor5-vue";
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import { ClassicEditor } from "ckeditor5";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import { mapState } from "vuex";
@@ -535,11 +535,11 @@ export default {
title: title,
});
},
addDocument({ stored_object, stored_object_version, file_name }) {
addDocument({ stored_object, stored_object_version }) {
let document = {
type: "accompanying_period_work_evaluation_document",
storedObject: stored_object,
title: file_name,
title: "Nouveau document",
};
this.$store.commit("addDocument", {
key: this.evaluation.key,

View File

@@ -87,11 +87,6 @@ const store = createStore({
return [];
},
getPreviousPersons(state) {
return state.personsPicked.filter(
(p) => !state.personsReachables.map((pr) => pr.id).includes(p.id),
);
},
buildPayload(state) {
return {
type: "accompanying_period_work",
@@ -612,7 +607,8 @@ const store = createStore({
submit({ getters, state, commit }, callback) {
let payload = getters.buildPayload,
params = new URLSearchParams({ entity_version: state.version }),
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json?${params}`;
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json?${params}`,
errors = [];
commit("setIsPosting", true);
// console.log('the social action', payload);

View File

@@ -30,7 +30,8 @@
<div class="item-row comment">
<ckeditor
:editor="classicEditor" :config="editorConfig"
:editor="classicEditor"
:config="editorConfig"
v-model="comment"
tag-name="textarea"
/>
@@ -102,9 +103,9 @@ div.participation-details {
<script>
import { mapGetters } from "vuex";
import PersonRenderBox from "ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue";
import {Ckeditor} from "@ckeditor/ckeditor5-vue";
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import {ClassicEditor} from "ckeditor5";
import { ClassicEditor } from "ckeditor5";
export default {
name: "MemberDetails",

View File

@@ -14,12 +14,12 @@
<script>
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
import {ClassicEditor} from "ckeditor5";
import { ClassicEditor } from "ckeditor5";
export default {
name: "PersonComment.vue",
components: {
ckeditor: Ckeditor
ckeditor: Ckeditor,
},
props: ["conc"],
computed: {

View File

@@ -201,7 +201,7 @@
{% endif %}
{% if accompanyingCourse.step != 'DRAFT' %}
<div class="mbloc col col-sm-6 col-lg-8 col-xxl-4">
<div class="mbloc col col-sm-6 col-lg-4">
<div class="notification-counter">
<h4 class="item-key">{{ 'notification.Notifications'|trans }}</h4>
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %}
@@ -238,31 +238,6 @@
</div>
</div>
{% endif %}
{% if counters.activities > 0 %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="count-activities">
<div class="count-item">{{ counters.activities }}</div>
<div class="count-item-label">
{% if counters.activities == 1 %}
{{ 'Activity'|trans }}
{% else %}
{{ 'Activities'|trans }}
{% endif %}
</div>
</div>
</div>
{% endif %}
{% if counters.works > 0 %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="count-works">
<div class="count-item">{{ counters.openWorks }} / {{ counters.works }}</div>
<div class="count-item-label">{{ 'accompanying_course_work.On-going works over total'|trans }}</div>
</div>
</div>
{% endif %}
</div>
<div class="social-actions my-4">

View File

@@ -8,7 +8,7 @@ L'usager {{ oldPersonLocation|chill_entity_render_string }} a déménagé.
Son adresse était utilisée pour localiser le parcours n°{{ period.id }}, dont vous êtes
le référent.
En conséquence de ce déménagement, le parcours est toujours localisé à cette adresse, mais à l'aide d'une
En conséquence de ce déménage, le parcours est toujours localisé à cette adresse, mais à l'aide d'une
adresse temporaire.
Si vous continuez à suivre le parcours, vous pouvez le localiser à nouveau auprès de l'adresse de

View File

@@ -5,19 +5,21 @@
{% set w = document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork %}
<div class="item-row">
<!-- evaluation document -->
<div class="item-two-col-grid" style="width: unset">
<div class="title">
<div class="item-col" style="width: unset">
{% if document.storedObject.isPending %}
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
{% elseif document.storedObject.isFailure %}
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
{% endif %}
<div>
<div>
<div class="badge-accompanying-work-type-simple">
{{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }}
</div>
{% if context == 'person' %}
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ w.accompanyingPeriod.id }}
</span>&nbsp;
{% endif %}
<div class="badge-accompanying-work-type">
<span class="title_label"></span>
<span class="title_action">{{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }}</span>
</div>
</div>
<div class="denomination h2">
@@ -34,20 +36,13 @@
</div>
{% endif %}
</div>
{% if document.storedObject.createdAt is not null %}
<div class="aside">
<div class="item-col">
<div class="container">
<div class="dates row text-end">
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
</div>
{% if context == 'person' %}
<div class="text-end">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ w.accompanyingPeriod.id }}
</span>&nbsp;
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>

View File

@@ -30,6 +30,9 @@ final readonly class SocialActionCSVExportService
private TranslatorInterface $translator,
) {}
/**
* @param list<SocialAction> $actions
*/
public function generateCsv(array $actions): Writer
{
// CSV headers
@@ -81,8 +84,7 @@ final readonly class SocialActionCSVExportService
'action_id' => $action->getId(),
'social_issue_id' => $action->getIssue()?->getId(),
'problematique_label' => null !== $action->getIssue() ? $this->socialIssueRender->renderString($action->getIssue(), []) : null,
'desactivation_date' => $action->getDesactivationDate()?->format('Y-m-d'),
'social_issue_ordering' => $action->getIssue()?->getOrdering(),
'social_issue_ordering' => null !== $action->getIssue() ? $action->getIssue()->getOrdering() : null,
'action_label' => $this->socialActionRender->renderString($action, []),
'action_ordering' => $action->getOrdering(),
'goal_label' => null !== $goal ? $this->stringHelper->localize($goal->getTitle()) : null,

View File

@@ -44,7 +44,6 @@ readonly class SocialIssueCSVExportService
'Id',
'Label',
'Social issue',
'export.social_action_list.desactivation_date',
'socialIssue.isParent?',
'socialIssue.Parent id',
]
@@ -67,7 +66,6 @@ readonly class SocialIssueCSVExportService
'id' => $issue->getId(),
'label' => $this->stringHelper->localize($issue->getTitle()),
'title' => $this->socialIssueRender->renderString($issue, []),
'export.social_action_list.desactivation_date' => $issue->getDesactivationDate()?->format('Y-m-d'),
'isParent' => $issue->hasChildren() ? 'X' : '',
'parent_id' => null !== $issue->getParent() ? $issue->getParent()->getId() : '',
];

View File

@@ -52,7 +52,6 @@ class SocialActionCsvExporterTest extends TestCase
// Création d'une instance réelle de SocialAction sans objectifs ni résultats
$actionWithoutGoalsOrResults = new SocialAction();
$actionWithoutGoalsOrResults->setIssue($socialIssue);
$actionWithoutGoalsOrResults->setDesactivationDate(new \DateTime('2025-05-21'));
$actionWithoutGoalsOrResults->setTitle(['fr' => 'Action without goals or results']);
// Création d'une instance réelle de SocialAction avec des objectifs et des résultats
@@ -62,7 +61,6 @@ class SocialActionCsvExporterTest extends TestCase
$actionWithGoalsAndResults = new SocialAction();
$actionWithGoalsAndResults->setIssue($socialIssue);
$actionWithGoalsAndResults->setDesactivationDate(new \DateTime('2025-05-21'));
$actionWithGoalsAndResults->setTitle(['fr' => 'Action with goals and results']);
$actionWithGoalsAndResults->addGoal($goalWithResult);
@@ -70,7 +68,6 @@ class SocialActionCsvExporterTest extends TestCase
$goalWithoutResult = new Goal();
$actionWithGoalsNoResults = new SocialAction();
$actionWithGoalsNoResults->setIssue($socialIssue);
$actionWithGoalsNoResults->setDesactivationDate(new \DateTime('2025-05-21'));
$actionWithGoalsNoResults->setTitle(['fr' => 'Action with goals and no results']);
$actionWithGoalsNoResults->addGoal($goalWithoutResult);
@@ -79,7 +76,6 @@ class SocialActionCsvExporterTest extends TestCase
$resultWithNoAction->setTitle(['fr' => 'Result without objectives']);
$actionWithResultsNoGoals = new SocialAction();
$actionWithResultsNoGoals->setIssue($socialIssue);
$actionWithResultsNoGoals->setDesactivationDate(new \DateTime('2025-05-21'));
$actionWithResultsNoGoals->setTitle(['fr' => 'Action with results and no goals']);
$actionWithResultsNoGoals->addResult($resultWithNoAction);
@@ -95,11 +91,11 @@ class SocialActionCsvExporterTest extends TestCase
$this->assertStringContainsString('Action with results and no goals', $content);
self::assertEquals(<<<'CSV'
export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.desactivation_date,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day
,,"Issue Title",2025-05-21,0,"Action with goals and results",0,"not found",,"not found",,,,,,,,,
,,"Issue Title",2025-05-21,0,"Action without goals or results",0,,,,,,,,,,,,
,,"Issue Title",2025-05-21,0,"Action with goals and no results",0,"not found",,,,,,,,,,,
,,"Issue Title",2025-05-21,0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,,
export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day
,,"Issue Title",0,"Action with goals and results",0,"not found",,"not found",,,,,,,,,
,,"Issue Title",0,"Action without goals or results",0,,,,,,,,,,,,
,,"Issue Title",0,"Action with goals and no results",0,"not found",,,,,,,,,,,
,,"Issue Title",0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,,
CSV, $content);
}

View File

@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250514115009 extends AbstractMigration
{
public function getDescription(): string
{
return 'Allow more characters for maritalstatus id';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(15)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_person_person ALTER maritalstatus_id TYPE VARCHAR(15)
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE chill_person_person ALTER maritalStatus_id TYPE VARCHAR(7)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(7)
SQL);
}
}

View File

@@ -804,7 +804,7 @@ person_admin:
# specific to accompanying period
accompanying_period:
deleted: Parcours d'accompagnement supprimé
deleted: Parcours d'accompagnment supprimé
dates: Période
dates_from_%opening_date%: Ouvert depuis le %opening_date%
dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date%
@@ -843,7 +843,6 @@ accompanying_course:
administrative_location: Localisation administrative
comment is pinned: Le commentaire est épinglé
comment is unpinned: Le commentaire est désépinglé
show: Montrer
hide: Masquer
closed periods: parcours clôturés
@@ -852,7 +851,6 @@ Social work configuration: Gestion des actions d'accompagnement social
# Accompanying Course comments
Accompanying Course Comment: Commentaire
Accompanying Course Comments: Commentaires
Accompanying Course Comment list: Commentaires du parcours
pinned: épinglé
Pin comment: Épingler
@@ -921,7 +919,6 @@ accompanying_course_work:
date_filter: Filtrer par date
types_filter: Filtrer par type d'action
user_filter: Filtrer par intervenant
On-going works over total: Actions en cours / Actions du parcours
#

View File

@@ -624,7 +624,7 @@ final class SingleTaskController extends AbstractController
->addCheckbox('status', $statuses, $statuses, $statusTrans);
$states = $this->singleTaskStateRepository->findAllExistingStates();
$checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['in_progress', 'closed', 'canceled', 'validated'], true)));
$checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['closed', 'canceled', 'validated'], true)));
if ([] !== $states) {
$filterBuilder

View File

@@ -65,7 +65,6 @@ class ThirdpartyCSVExportController extends AbstractController
'Name',
'Profession',
'Telephone',
'Telephone2',
'Email',
'Address',
'Comment',
@@ -77,7 +76,6 @@ class ThirdpartyCSVExportController extends AbstractController
'Contact name',
'Contact firstname',
'Contact phone',
'Contact phone2',
'Contact email',
'Contact address',
'Contact profession',

View File

@@ -209,11 +209,6 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Strin
#[PhonenumberConstraint(type: 'any')]
private ?PhoneNumber $telephone = null;
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'telephone2', type: 'phone_number', nullable: true)]
#[PhonenumberConstraint(type: 'any')]
private ?PhoneNumber $telephone2 = null;
#[ORM\Column(name: 'types', type: \Doctrine\DBAL\Types\Types::JSON, nullable: true)]
private ?array $thirdPartyTypes = [];
@@ -434,11 +429,6 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Strin
return $this->telephone;
}
public function getTelephone2(): ?PhoneNumber
{
return $this->telephone2;
}
/**
* Get type.
*/
@@ -722,13 +712,6 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Strin
return $this;
}
public function setTelephone2(?PhoneNumber $telephone2 = null): self
{
$this->telephone2 = $telephone2;
return $this;
}
/**
* Set type.
*

View File

@@ -59,10 +59,6 @@ class ThirdPartyType extends AbstractType
'label' => 'Phonenumber',
'required' => false,
])
->add('telephone2', ChillPhoneNumberType::class, [
'label' => 'telephone2',
'required' => false,
])
->add('email', EmailType::class, [
'required' => false,
])

View File

@@ -42,7 +42,6 @@ class ThirdPartyRepository implements ObjectRepository
parent.name AS name,
parent.profession AS profession,
parent.telephone AS telephone,
parent.telephone2 AS telephone2,
parent.email AS email,
CONCAT_WS(' ', parent_address.street, parent_address.streetnumber, parent_postal.code, parent_postal.label) AS address,
parent.comment AS comment,
@@ -56,7 +55,6 @@ class ThirdPartyRepository implements ObjectRepository
contact.name AS contact_name,
contact.firstname AS contact_firstname,
contact.telephone AS contact_phone,
contact.telephone2 AS contact_phone2,
contact.email AS contact_email,
contact.profession AS contact_profession,
CONCAT_WS(' ', contact_address.street, contact_address.streetnumber, contact_postal.code, contact_postal.label) AS contact_address

View File

@@ -91,18 +91,6 @@
}}</a
>
</li>
<li v-if="thirdparty.telephone2">
<i class="fa fa-li fa-mobile" />
<a
:href="
'tel: ' +
thirdparty.telephone2
"
>{{
thirdparty.telephone2
}}</a
>
</li>
<li v-if="thirdparty.email">
<i
class="fa fa-li fa-envelope-o"
@@ -133,12 +121,6 @@
thirdparty.telephone
}}</a>
</li>
<li v-if="thirdparty.telephone2">
<i class="fa fa-li fa-mobile" />
<a :href="'tel: ' + thirdparty.telephone2"
>{{ thirdparty.telephone2 }}
</a>
</li>
<li v-if="thirdparty.email">
<i class="fa fa-li fa-envelope-o" />
<a :href="'mailto: ' + thirdparty.email">{{

View File

@@ -223,19 +223,6 @@
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="phonenumber2"
><i class="fa fa-fw fa-phone"
/></span>
<input
class="form-control form-control-lg"
v-model="thirdparty.telephone2"
:placeholder="$t('thirdparty.phonenumber2')"
:aria-label="$t('thirdparty.phonenumber2')"
aria-describedby="phonenumber2"
/>
</div>
<div v-if="parent">
<div class="input-group mb-3">
<span class="input-group-text" id="comment"
@@ -276,7 +263,6 @@ export default {
firstname: "",
name: "",
telephone: "",
telephone2: "",
civility: null,
profession: "",
},
@@ -382,11 +368,9 @@ export default {
addQueryItem(field, queryItem) {
switch (field) {
case "name":
if (this.thirdparty.name) {
this.thirdparty.name += ` ${queryItem}`;
} else {
this.thirdparty.name = queryItem;
}
this.thirdparty.name
? (this.thirdparty.name += ` ${queryItem}`)
: (this.thirdparty.name = queryItem);
break;
case "firstName":
this.thirdparty.firstname = queryItem;

View File

@@ -6,7 +6,6 @@ const thirdpartyMessages = {
name: "Dénomination",
email: "Courriel",
phonenumber: "Téléphone",
phonenumber2: "Autre numéro de téléphone",
comment: "Commentaire",
profession: "Qualité",
civility: "Civilité",

View File

@@ -115,10 +115,6 @@
{% else %}
<span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span>
{% endif %}
{% if thirdparty.telephone2 is not null %}
{% if thirdparty.telephone is not null %}, {% endif %}
<a href="{{ 'tel:' ~ thirdparty.telephone2|phone_number_format('E164') }}">{{ thirdparty.telephone2|chill_format_phonenumber }}</a>
{% endif %}
</li>
<li><i class="fa fa-li fa-envelope-o"></i>
<a href="{{ 'mailto:' ~ thirdparty.email }}">
@@ -139,14 +135,8 @@
}) }}
</li>
<li><i class="fa fa-li fa-phone"></i>
{% if thirdparty.telephone or thirdparty.telephone2 %}
{% if thirdparty.telephone is not null %}
{% if thirdparty.telephone %}
<a href="{{ 'tel:' ~ thirdparty.telephone|phone_number_format('E164') }}">{{ thirdparty.telephone|chill_format_phonenumber }}</a>
{% endif %}
{% if thirdparty.telephone2 is not null %}
{% if thirdparty.telephone is not null %}, {% endif %}
<a href="{{ 'tel:' ~ thirdparty.telephone2|phone_number_format('E164') }}">{{ thirdparty.telephone2|chill_format_phonenumber }}</a>
{% endif %}
{% else %}
<span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span>
{% endif %}

View File

@@ -22,7 +22,6 @@
{{ form_row(form.typesAndCategories) }}
{{ form_row(form.telephone) }}
{{ form_row(form.telephone2) }}
{{ form_row(form.email) }}
{% if form.contactDataAnonymous is defined %}

View File

@@ -24,24 +24,17 @@
</div>
</div>
<div class="row">
<div class="form-group col-md-6 mb-3">
<div class="form-group col-md-5 mb-3">
{{ form_widget(form.telephone) }}
{{ form_errors(form.telephone) }}
{{ form_label(form.telephone) }}
</div>
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.telephone2) }}
{{ form_errors(form.telephone2) }}
{{ form_label(form.telephone2) }}
</div>
</div>
<div class="row">
<div class="form-group col-md-6 mb-3">
<div class="form-group col-md-5 mb-3">
{{ form_widget(form.email) }}
{{ form_errors(form.email) }}
{{ form_label(form.email) }}
</div>
<div class="form-group col-md-6 mb-3">
<div class="form-group col-md-2 mb-3">
{{ form_widget(form.contactDataAnonymous) }}
{{ form_label(form.contactDataAnonymous) }}
{{ form_errors(form.contactDataAnonymous) }}

View File

@@ -76,18 +76,6 @@
{% endif %}
</dd>
<dt>{{ 'Phonenumber2'|trans }}</dt>
<dd>
{% if thirdParty.telephone2 == null %}
<span class="chill-no-data-statement">{{ 'thirdparty.No_phonenumber'|trans }}</span>
{% else %}
<a href="{{ 'tel:' ~ thirdParty.telephone2|phone_number_format('E164') }}">
{{ thirdParty.telephone2|chill_format_phonenumber }}
</a>
{% endif %}
</dd>
<dt>{{ 'email'|trans }}<dt>
<dd>
{% if thirdParty.email == null %}

View File

@@ -55,7 +55,6 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf
'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context),
'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']),
'telephone' => $this->normalizer->normalize($thirdParty->getTelephone(), $format, $context),
'telephone2' => $this->normalizer->normalize($thirdParty->getTelephone2(), $format, $context),
'email' => $thirdParty->getEmail(),
'isChild' => $thirdParty->isChild(),
'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context),

View File

@@ -28,8 +28,6 @@ components:
type: string
telephone:
type: string
telephone2:
type: string
address:
$ref: "#/components/schemas/Address"
Address:

View File

@@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\ThirdParty;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250325085950 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add a second telephone number to ThirdParty';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_3party.third_party ADD telephone2 VARCHAR(35) DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN chill_3party.third_party.telephone2 IS \'(DC2Type:phone_number)\'');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_3party.third_party DROP telephone2');
}
}

View File

@@ -4,7 +4,6 @@ third parties: tiers
firstname: Prénom
name: Nom
telephone: Téléphone
telephone2: Autre numéro de téléphone
adress: Adresse
email: Courriel
comment: Commentaire
@@ -40,7 +39,7 @@ thirdparty.A contact: Une personne physique
thirdparty.contact: Personne physique
thirdparty.Contact of: Contact de
thirdparty.a_company_explanation: >-
Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'institution. Il est également possible de
Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'instution. Il est également possible de
leur associer un acronyme, et le nom d'un service.
thirdparty.a_contact_explanation: >-
Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. Il est possible de leur
@@ -150,8 +149,6 @@ Contact id: Identifiant du contact
Contact name: Nom du contact
Contact firstname: Prénom du contact
Contact phone: Téléphone du contact
Contact phone2: Autre téléphone du contact
Telephone2: Autre téléphone
Contact email: Courrier électronique du contact
Contact address: Adresse du contact
Contact profession: Profession du contact