Merge branch 'master' into issue345_internal_thirdparty_contact

This commit is contained in:
nobohan 2022-02-18 11:22:51 +01:00
commit 8c7da5f80c
34 changed files with 448 additions and 110 deletions

View File

@ -10,15 +10,22 @@ and this project adheres to
## Unreleased ## Unreleased
* AddPersons: remove ul-li html tags from AddPersons (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/419) * AddPersons: remove ul-li html tags from AddPersons (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/419)
<!-- write down unreleased development here -->
* workflow: fix sending notifications
* [thirdparty] Extend the thirdparty search to thirdparty children (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/448)
* [person]: AddPersons: allow creation of person or thirdparty only (no users) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422) * [person]: AddPersons: allow creation of person or thirdparty only (no users) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422)
* [person]: AddPersons: allow creation of person or thirdparty depending on allowed types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422) * [person]: AddPersons: allow creation of person or thirdparty depending on allowed types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422)
* [person]: AddPersons: add suggestion of name when creating new person or thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422) * [person]: AddPersons: add suggestion of name when creating new person or thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422)
* [main] Address: fix small bug: when modifying an address without street (isNoAddress), also check errors if street is an empty string as back-end change null value to empty string for street (and streetNumber) * [main] Address: fix small bug: when modifying an address without street (isNoAddress), also check errors if street is an empty string as back-end change null value to empty string for street (and streetNumber)
* [main] Address: stronger client-side validation of addresses (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/449) * [main] Address: stronger client-side validation of addresses (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/449)
<!-- write down unreleased development here -->
* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345) * [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
* [person] accompanying course: filter suggested entities by open participations (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/415)
[activity] can click through the cross icon for removing person in concerned group (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/476)
[activity] correct associated persons by considering only open participations (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/476)
* [person_resources]: Renderboxes used to display person/thirdparty info (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/465)
* [Household]: Add end date in HouseholdMember form for 'enfant hors menage' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/434)
* [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435)
## Test releases ## Test releases
@ -46,7 +53,7 @@ and this project adheres to
* [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459) * [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459)
* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414) * [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
* [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450) * [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450)
* [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435)
### test release 2021-02-01 ### test release 2021-02-01

View File

@ -357,7 +357,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
if (null !== $this->accompanyingPeriod) { if (null !== $this->accompanyingPeriod) {
$personsAssociated = []; $personsAssociated = [];
foreach ($this->accompanyingPeriod->getParticipations() as $participation) { foreach ($this->accompanyingPeriod->getOpenParticipations() as $participation) {
if ($this->persons->contains($participation->getPerson())) { if ($this->persons->contains($participation->getPerson())) {
$personsAssociated[] = $participation->getPerson(); $personsAssociated[] = $participation->getPerson();
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<li> <li>
<span :title="person.text"> <span :title="person.text" @click.prevent="$emit('remove', person)">
<span class="chill_denomination" @click.prevent="$emit('remove', person)"> <span class="chill_denomination">
<person-text :person="person" :isCut="true"></person-text> <person-text :person="person" :isCut="true"></person-text>
</span> </span>
</span> </span>

View File

@ -14,19 +14,46 @@ namespace Chill\DocStoreBundle\Workflow;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandlerInterface class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandlerInterface
{ {
private EntityRepository $repository; private EntityRepository $repository;
private TranslatorInterface $translator;
/** /**
* TODO: injecter le repository directement. * TODO: injecter le repository directement.
*/ */
public function __construct(EntityManagerInterface $em) public function __construct(EntityManagerInterface $em, TranslatorInterface $translator)
{ {
$this->repository = $em->getRepository(AccompanyingCourseDocument::class); $this->repository = $em->getRepository(AccompanyingCourseDocument::class);
$this->translator = $translator;
}
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{
$course = $this->getRelatedEntity($entityWorkflow)
->getCourse();
$persons = [];
if (null !== $course) {
$persons = $course->getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $participation) {
return $participation->getPerson();
})->toArray();
}
return [
'persons' => array_values($persons),
];
}
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string
{
return $this->translator->trans('workflow.Document (n°%doc%)', ['%doc%' => $entityWorkflow->getRelatedEntityId()]);
} }
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingCourseDocument public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingCourseDocument

View File

@ -11,7 +11,12 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller; namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Serializer\Model\Counter;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use LogicException; use LogicException;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@ -21,17 +26,71 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
class WorkflowApiController class WorkflowApiController
{ {
private EntityManagerInterface $entityManager; private EntityManagerInterface $entityManager;
private EntityWorkflowRepository $entityWorkflowRepository;
private PaginatorFactory $paginatorFactory;
private Security $security; private Security $security;
public function __construct(Security $security, EntityManagerInterface $entityManager) private SerializerInterface $serializer;
{
public function __construct(
EntityManagerInterface $entityManager,
EntityWorkflowRepository $entityWorkflowRepository,
PaginatorFactory $paginatorFactory,
Security $security,
SerializerInterface $serializer
) {
$this->entityManager = $entityManager; $this->entityManager = $entityManager;
$this->entityWorkflowRepository = $entityWorkflowRepository;
$this->paginatorFactory = $paginatorFactory;
$this->security = $security; $this->security = $security;
$this->serializer = $serializer;
}
/**
* Return a list of workflow which are waiting an action for the user.
*
* @Route("/api/1.0/main/workflow/my", methods={"GET"})
*/
public function myWorkflow(Request $request): JsonResponse
{
if (!$this->security->isGranted('ROLE_USER') || !$this->security->getUser() instanceof User) {
throw new AccessDeniedException();
}
$total = $this->entityWorkflowRepository->countByDest($this->security->getUser());
if ($request->query->getBoolean('countOnly', false)) {
return new JsonResponse(
$this->serializer->serialize(new Counter($total), 'json'),
JsonResponse::HTTP_OK,
[],
true
);
}
$paginator = $this->paginatorFactory->create($total);
$workflows = $this->entityWorkflowRepository->findByDest(
$this->security->getUser(),
['id' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
return new JsonResponse(
$this->serializer->serialize(new Collection($workflows, $paginator), 'json', ['groups' => ['read']]),
JsonResponse::HTTP_OK,
[],
true
);
} }
/** /**

View File

@ -197,6 +197,8 @@ class WorkflowController extends AbstractController
); );
} }
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
$workflow->apply($entityWorkflow, $transition); $workflow->apply($entityWorkflow, $transition);
foreach ($transitionForm['future_dest_users']->getData() as $user) { foreach ($transitionForm['future_dest_users']->getData() as $user) {

View File

@ -139,12 +139,10 @@ class Address
private $point; private $point;
/** /**
* @var PostalCode
*
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
* @Groups({"write"}) * @Groups({"write"})
*/ */
private $postcode; private ?PostalCode $postcode;
/** /**
* @var string|null * @var string|null

View File

@ -41,6 +41,17 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
use TrackUpdateTrait; use TrackUpdateTrait;
/**
* a list of future dest users for the next steps.
*
* This is in used in order to let controller inform who will be the future users which will validate
* the next step. This is necessary to perform some computation about the next users, before they are
* associated to the entity EntityWorkflowStep.
*
* @var array|User[]
*/
public array $futureDestUsers = [];
/** /**
* @ORM\OneToMany(targetEntity=EntityWorkflowComment::class, mappedBy="entityWorkflow", orphanRemoval=true) * @ORM\OneToMany(targetEntity=EntityWorkflowComment::class, mappedBy="entityWorkflow", orphanRemoval=true)
* *

View File

@ -23,7 +23,6 @@
:class="{'active': activeTab === 'MyAccompanyingCourses'}" :class="{'active': activeTab === 'MyAccompanyingCourses'}"
@click="selectTab('MyAccompanyingCourses')"> @click="selectTab('MyAccompanyingCourses')">
{{ $t('my_accompanying_courses.tab') }} {{ $t('my_accompanying_courses.tab') }}
<tab-counter :count="state.accompanyingCourses.count"></tab-counter>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -51,6 +50,14 @@
<tab-counter :count="state.tasks.alert.count"></tab-counter> <tab-counter :count="state.tasks.alert.count"></tab-counter>
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link"
:class="{'active': activeTab === 'MyWorkflows'}"
@click="selectTab('MyWorkflows')">
{{ $t('my_workflows.tab') }}
<tab-counter :count="state.workflows.count"></tab-counter>
</a>
</li>
<li class="nav-item loading ms-auto py-2" v-if="loading"> <li class="nav-item loading ms-auto py-2" v-if="loading">
<i class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray" :title="$t('loading')"></i> <i class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray" :title="$t('loading')"></i>
</li> </li>
@ -75,6 +82,9 @@
<my-notifications <my-notifications
v-else-if="activeTab === 'MyNotifications'"> v-else-if="activeTab === 'MyNotifications'">
</my-notifications> </my-notifications>
<my-workflows
v-else-if="activeTab === 'MyWorkflows'">
</my-workflows>
</div> </div>
</template> </template>
@ -85,6 +95,7 @@ import MyEvaluations from './MyEvaluations';
import MyTasks from './MyTasks'; import MyTasks from './MyTasks';
import MyAccompanyingCourses from './MyAccompanyingCourses'; import MyAccompanyingCourses from './MyAccompanyingCourses';
import MyNotifications from './MyNotifications'; import MyNotifications from './MyNotifications';
import MyWorkflows from './MyWorkflows.vue';
import TabCounter from './TabCounter'; import TabCounter from './TabCounter';
import { mapState } from "vuex"; import { mapState } from "vuex";
@ -95,6 +106,7 @@ export default {
MyWorks, MyWorks,
MyEvaluations, MyEvaluations,
MyTasks, MyTasks,
MyWorkflows,
MyAccompanyingCourses, MyAccompanyingCourses,
MyNotifications, MyNotifications,
TabCounter, TabCounter,
@ -126,6 +138,7 @@ export default {
'MyWorks', 'MyWorks',
'MyEvaluations', 'MyEvaluations',
'MyTasks', 'MyTasks',
'MyWorkflows',
]) { ]) {
this.$store.dispatch('getByTab', { tab: m, param: "countOnly=1" }); this.$store.dispatch('getByTab', { tab: m, param: "countOnly=1" });
} }

View File

@ -66,6 +66,8 @@ export default {
return appMessages.fr.the_activity; return appMessages.fr.the_activity;
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod': case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
return appMessages.fr.the_course; return appMessages.fr.the_course;
case 'Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow':
return appMessages.fr.the_workflow;
default: default:
throw 'notification type unknown'; throw 'notification type unknown';
} }
@ -76,6 +78,8 @@ export default {
return `/fr/activity/${n.relatedEntityId}/show` return `/fr/activity/${n.relatedEntityId}/show`
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod': case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
return `/fr/parcours/${n.relatedEntityId}` return `/fr/parcours/${n.relatedEntityId}`
case 'Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow':
return `/fr/main/workflow/${n.relatedEntityId}/show`
default: default:
throw 'notification type unknown'; throw 'notification type unknown';
} }

View File

@ -0,0 +1,88 @@
<template>
<div class="alert alert-light">{{ $t('my_workflows.description') }}</div>
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">{{ $t('Object_workflow') }}</th>
<th scope="col">{{ $t('Step') }}</th>
<th scope="col">{{ $t('concerned_users') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(w, i) in workflows.results" :key="`workflow-${i}`">
<td>{{ w.title }}</td>
<td>
<div class="workflow">
<div class="breadcrumb">
<i class="fa fa-circle me-1 text-chill-yellow mx-2"></i>
<span class="mx-2">{{ getStep(w) }}</span>
</div>
</div>
</td>
<td v-if="w.datas.persons !== null">
<span v-for="p in w.datas.persons" class="me-1" :key="p.id">
<on-the-fly
:type="p.type"
:id="p.id"
:buttonText="p.textAge"
:displayBadge="'true' === 'true'"
action="show">
</on-the-fly>
</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(w)">
{{ $t('show_entity', { entity: $t('the_workflow') }) }}
</a>
</td>
</tr>
</template>
</tab-table>
</template>
<script>
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
export default {
name: "MyWorkflows",
components: {
TabTable,
OnTheFly
},
computed: {
...mapState([
'workflows',
]),
...mapGetters([
'isWorkflowsLoaded',
]),
noResults() {
if (!this.isWorkflowsLoaded) {
return false;
} else {
return this.workflows.count === 0;
}
},
},
methods: {
getUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
},
getStep(w) {
const lastStep = w.steps.length - 1
return w.steps[lastStep].currentStep.text;
}
},
}
</script>
<style scoped>
span.outdated {
font-weight: bold;
color: var(--bs-warning);
}
</style>

View File

@ -22,6 +22,10 @@ const appMessages = {
tab: "Mes notifications", tab: "Mes notifications",
description: "Liste des notifications reçues et non lues.", description: "Liste des notifications reçues et non lues.",
}, },
my_workflows: {
tab: "Mes workflows",
description: "Liste des workflows en attente d'une action."
},
opening_date: "Date d'ouverture", opening_date: "Date d'ouverture",
social_issues: "Problématiques sociales", social_issues: "Problématiques sociales",
concerned_persons: "Usagers concernés", concerned_persons: "Usagers concernés",
@ -33,12 +37,16 @@ const appMessages = {
From: "Expéditeur", From: "Expéditeur",
Subject: "Objet", Subject: "Objet",
Entity: "Associé à", Entity: "Associé à",
Step: "Étape",
concerned_users: "Usagers concernés",
Object_workflow: "Objet du workflow",
show_entity: "Voir {entity}", show_entity: "Voir {entity}",
the_activity: "l'échange", the_activity: "l'échange",
the_course: "le parcours", the_course: "le parcours",
the_action: "l'action", the_action: "l'action",
the_evaluation: "l'évaluation", the_evaluation: "l'évaluation",
the_task: "la tâche", the_task: "la tâche",
the_workflow: "le workflow",
StartDate: "Date d'ouverture", StartDate: "Date d'ouverture",
SocialAction: "Action d'accompagnement", SocialAction: "Action d'accompagnement",
no_data: "Aucun résultats", no_data: "Aucun résultats",

View File

@ -1,12 +1,6 @@
import 'es6-promise/auto'; import 'es6-promise/auto';
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import MyCustoms from "../MyCustoms";
import MyWorks from "../MyWorks";
import MyEvaluations from "../MyEvaluations";
import MyTasks from "../MyTasks";
import MyAccompanyingCourses from "../MyAccompanyingCourses";
import MyNotifications from "../MyNotifications";
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== 'production';
@ -27,6 +21,7 @@ const store = createStore({
}, },
accompanyingCourses: {}, accompanyingCourses: {},
notifications: {}, notifications: {},
workflows: {},
errorMsg: [], errorMsg: [],
loading: false loading: false
}, },
@ -49,6 +44,9 @@ const store = createStore({
isNotificationsLoaded(state) { isNotificationsLoaded(state) {
return !isEmpty(state.notifications); return !isEmpty(state.notifications);
}, },
isWorkflowsLoaded(state) {
return !isEmpty(state.workflows);
},
counter(state) { counter(state) {
return { return {
works: state.works.count, works: state.works.count,
@ -57,6 +55,7 @@ const store = createStore({
tasksAlert: state.tasks.alert.count, tasksAlert: state.tasks.alert.count,
accompanyingCourses: state.accompanyingCourses.count, accompanyingCourses: state.accompanyingCourses.count,
notifications: state.notifications.count, notifications: state.notifications.count,
workflows: state.workflows.count
} }
} }
}, },
@ -85,6 +84,9 @@ const store = createStore({
//console.log('addNotifications', notifications); //console.log('addNotifications', notifications);
state.notifications = notifications; state.notifications = notifications;
}, },
addWorkflows(state, workflows) {
state.workflows = workflows;
},
setLoading(state, bool) { setLoading(state, bool) {
state.loading = bool; state.loading = bool;
}, },
@ -180,14 +182,30 @@ const store = createStore({
const url = `/api/1.0/main/notification/my/unread${'?'+ param}`; const url = `/api/1.0/main/notification/my/unread${'?'+ param}`;
makeFetch('GET', url) makeFetch('GET', url)
.then((response) => { .then((response) => {
console.log('notifications', response)
commit('addNotifications', response); commit('addNotifications', response);
commit('setLoading', false); commit('setLoading', false);
}) })
.catch((error) => { .catch((error) => {
commit('catchError', error); commit('catchError', error);
throw error; throw error;
}) });
; }
break;
case 'MyWorkflows':
if (!getters.isWorflowsLoaded) {
commit('setLoading', true);
const url = '/api/1.0/main/workflow/my';
makeFetch('GET', url)
.then((response) => {
console.log('workflows', response)
commit('addWorkflows', response);
commit('setLoading', false);
})
.catch((error) => {
commit('catchError', error);
throw error;
});
} }
break; break;
default: default:

View File

@ -78,7 +78,7 @@ export default {
case 'thirdparty': case 'thirdparty':
let data = this.$refs.castThirdparty.$data.thirdparty; let data = this.$refs.castThirdparty.$data.thirdparty;
data.name = data.text; data.name = data.text;
if (data.address !== undefined) { if (data.address !== null) {
data.address = { id: data.address.address_id } data.address = { id: data.address.address_id }
} else { } else {
data.address = null; data.address = null;

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Serializer\Normalizer; namespace Chill\MainBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor; use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
@ -22,12 +23,18 @@ class EntityWorkflowNormalizer implements NormalizerInterface, NormalizerAwareIn
{ {
use NormalizerAwareTrait; use NormalizerAwareTrait;
private EntityWorkflowManager $entityWorkflowManager;
private MetadataExtractor $metadataExtractor; private MetadataExtractor $metadataExtractor;
private Registry $registry; private Registry $registry;
public function __construct(MetadataExtractor $metadataExtractor, Registry $registry) public function __construct(
{ EntityWorkflowManager $entityWorkflowManager,
MetadataExtractor $metadataExtractor,
Registry $registry
) {
$this->entityWorkflowManager = $entityWorkflowManager;
$this->metadataExtractor = $metadataExtractor; $this->metadataExtractor = $metadataExtractor;
$this->registry = $registry; $this->registry = $registry;
} }
@ -40,6 +47,7 @@ class EntityWorkflowNormalizer implements NormalizerInterface, NormalizerAwareIn
public function normalize($object, ?string $format = null, array $context = []) public function normalize($object, ?string $format = null, array $context = [])
{ {
$workflow = $this->registry->get($object, $object->getWorkflowName()); $workflow = $this->registry->get($object, $object->getWorkflowName());
$handler = $this->entityWorkflowManager->getHandler($object);
return [ return [
'type' => 'entity_workflow', 'type' => 'entity_workflow',
@ -49,6 +57,8 @@ class EntityWorkflowNormalizer implements NormalizerInterface, NormalizerAwareIn
'workflow' => $this->metadataExtractor->buildArrayPresentationForWorkflow($workflow), 'workflow' => $this->metadataExtractor->buildArrayPresentationForWorkflow($workflow),
'currentStep' => $this->normalizer->normalize($object->getCurrentStep(), $format, $context), 'currentStep' => $this->normalizer->normalize($object->getCurrentStep(), $format, $context),
'steps' => $this->normalizer->normalize($object->getStepsChained(), $format, $context), 'steps' => $this->normalizer->normalize($object->getStepsChained(), $format, $context),
'datas' => $this->normalizer->normalize($handler->getEntityData($object), $format, $context),
'title' => $handler->getEntityTitle($object),
]; ];
} }

View File

@ -15,6 +15,10 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
interface EntityWorkflowHandlerInterface interface EntityWorkflowHandlerInterface
{ {
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array;
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string;
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object; public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object;
/** /**

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Workflow\EventSubscriber; namespace Chill\MainBundle\Workflow\EventSubscriber;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor; use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -34,8 +35,13 @@ class NotificationOnTransition implements EventSubscriberInterface
private Security $security; private Security $security;
public function __construct(EntityManagerInterface $entityManager, EngineInterface $engine, MetadataExtractor $metadataExtractor, Security $security, Registry $registry) public function __construct(
{ EntityManagerInterface $entityManager,
EngineInterface $engine,
MetadataExtractor $metadataExtractor,
Security $security,
Registry $registry
) {
$this->entityManager = $entityManager; $this->entityManager = $entityManager;
$this->engine = $engine; $this->engine = $engine;
$this->metadataExtractor = $metadataExtractor; $this->metadataExtractor = $metadataExtractor;
@ -62,7 +68,7 @@ class NotificationOnTransition implements EventSubscriberInterface
$dests = array_merge( $dests = array_merge(
$entityWorkflow->getSubscriberToStep()->toArray(), $entityWorkflow->getSubscriberToStep()->toArray(),
$entityWorkflow->isFinal() ? $entityWorkflow->getSubscriberToFinal()->toArray() : [], $entityWorkflow->isFinal() ? $entityWorkflow->getSubscriberToFinal()->toArray() : [],
$entityWorkflow->getCurrentStep()->getDestUser()->toArray() $entityWorkflow->getCurrentStepChained()->getPrevious()->getDestUser()->toArray()
); );
$place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow); $place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow);
@ -85,7 +91,7 @@ class NotificationOnTransition implements EventSubscriberInterface
'dest' => $subscriber, 'dest' => $subscriber,
'place' => $place, 'place' => $place,
'workflow' => $workflow, 'workflow' => $workflow,
'is_dest' => $entityWorkflow->getCurrentStep()->getDestUser()->contains($subscriber), 'is_dest' => in_array($subscriber->getId(), array_map(static function (User $u) { return $u->getId(); }, $entityWorkflow->futureDestUsers), true),
]; ];
$notification = new Notification(); $notification = new Notification();

View File

@ -125,6 +125,11 @@ components:
type: object type: object
type: type:
type: string type: string
Workflow:
type: object
properties:
id:
type: integer
paths: paths:
/1.0/search.json: /1.0/search.json:
@ -165,13 +170,6 @@ paths:
200: 200:
description: "OK" description: "OK"
/1.0/main/address.json: /1.0/main/address.json:
get:
tags:
- address
summary: Return a list of all Chill addresses
responses:
200:
description: "ok"
post: post:
tags: tags:
- address - address
@ -222,10 +220,44 @@ paths:
description: "Unprocessable entity (validation errors)" description: "Unprocessable entity (validation errors)"
400: 400:
description: "transition cannot be applyed" description: "transition cannot be applyed"
/1.0/main/address/{id}.json:
get:
tags:
- address
summary: Return an address by id
parameters:
- name: id
in: path
required: true
description: The address id
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "ok"
content:
application/json:
schema:
$ref: '#/components/schemas/Address'
404:
description: "not found"
401:
description: "Unauthorized"
patch: patch:
tags: tags:
- address - address
summary: patch an address summary: patch an address
parameters:
- name: id
in: path
required: true
description: The address id
schema:
type: integer
format: integer
minimum: 1
requestBody: requestBody:
required: true required: true
content: content:
@ -277,31 +309,6 @@ paths:
400: 400:
description: "transition cannot be applyed" description: "transition cannot be applyed"
/1.0/main/address/{id}.json:
get:
tags:
- address
summary: Return an address by id
parameters:
- name: id
in: path
required: true
description: The address id
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "ok"
content:
application/json:
schema:
$ref: '#/components/schemas/Address'
404:
description: "not found"
401:
description: "Unauthorized"
/1.0/main/address/{id}/duplicate.json: /1.0/main/address/{id}/duplicate.json:
post: post:
@ -803,4 +810,20 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/UserJob' $ref: '#/components/schemas/UserJob'
/1.0/main/workflow/my:
get:
tags:
- workflow
summary: Return a list of workflows awaiting for user's action
responses:
200:
description: "ok"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Workflow'
403:
description: "Unauthorized"

View File

@ -60,6 +60,7 @@ class UserRefEventSubscriber implements EventSubscriberInterface
{ {
if ($period->hasPreviousUser() if ($period->hasPreviousUser()
&& $period->getUser() !== $this->security->getUser() && $period->getUser() !== $this->security->getUser()
&& null !== $period->getUser()
&& $period->getStep() !== AccompanyingPeriod::STEP_DRAFT && $period->getStep() !== AccompanyingPeriod::STEP_DRAFT
) { ) {
$this->generateNotificationToUser($period); $this->generateNotificationToUser($period);

View File

@ -30,7 +30,7 @@ class UserAccompanyingPeriodController extends AbstractController
} }
/** /**
* @Route("/{_locale}/accompanying-periods", name="chill_person_accompanying_period_user") * @Route("/{_locale}/person/accompanying-periods/my", name="chill_person_accompanying_period_user")
*/ */
public function listAction(Request $request) public function listAction(Request $request)
{ {
@ -44,13 +44,13 @@ class UserAccompanyingPeriodController extends AbstractController
); );
return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [ return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [
'accompanyingds' => $accompanyingPeriods, 'accompanyingPeriods' => $accompanyingPeriods,
'pagination' => $pagination, 'pagination' => $pagination,
]); ]);
} }
/** /**
* @Route("/{_locale}/accompanying-periods/drafts", name="chill_person_accompanying_period_draft_user") * @Route("/{_locale}/person/accompanying-periods/my/drafts", name="chill_person_accompanying_period_draft_user")
*/ */
public function listDraftsAction(Request $request) public function listDraftsAction(Request $request)
{ {

View File

@ -25,6 +25,13 @@ class HouseholdMemberType extends AbstractType
'label' => 'household.Start date', 'label' => 'household.Start date',
'input' => 'datetime_immutable', 'input' => 'datetime_immutable',
]); ]);
if (!$options['data']->getPosition()->getShareHousehold()) {
$builder->add('endDate', ChillDateType::class, [
'label' => 'household.End date',
'input' => 'datetime_immutable',
]);
}
$builder $builder
->add('comment', ChillTextareaType::class, [ ->add('comment', ChillTextareaType::class, [
'label' => 'household.Comment', 'label' => 'household.Comment',

View File

@ -71,7 +71,7 @@ final class AccompanyingPeriodRepository implements ObjectRepository
$qb = $this->buildQueryByRecentUserHistory($user, $since); $qb = $this->buildQueryByRecentUserHistory($user, $since);
return $qb->select('a') return $qb->select('a')
->distinct(true) ->addOrderBy('userHistory.startDate', 'DESC')
->getQuery() ->getQuery()
->setMaxResults($limit) ->setMaxResults($limit)
->setFirstResult($offset) ->setFirstResult($offset)
@ -95,6 +95,7 @@ final class AccompanyingPeriodRepository implements ObjectRepository
$qb $qb
->join('a.userHistories', 'userHistory') ->join('a.userHistories', 'userHistory')
->where($qb->expr()->eq('a.user', ':user')) ->where($qb->expr()->eq('a.user', ':user'))
->andWhere($qb->expr()->neq('a.step', "'" . AccompanyingPeriod::STEP_DRAFT . "'"))
->andWhere($qb->expr()->gte('userHistory.startDate', ':since')) ->andWhere($qb->expr()->gte('userHistory.startDate', ':since'))
->andWhere($qb->expr()->isNull('userHistory.endDate')) ->andWhere($qb->expr()->isNull('userHistory.endDate'))
->setParameter('user', $user) ->setParameter('user', $user)

View File

@ -199,7 +199,7 @@ export default {
...mapState({ ...mapState({
suggestedEntities: state => { suggestedEntities: state => {
return [ return [
...state.accompanyingCourse.participations.map(p => p.person), ...state.accompanyingCourse.participations.filter((p) => p.endDate === null).map((p) => p.person),
...state.accompanyingCourse.resources.map(r => r.resource) ...state.accompanyingCourse.resources.map(r => r.resource)
] ]
.filter((e) => e !== null) .filter((e) => e !== null)

View File

@ -77,7 +77,7 @@ export default {
counter: state => state.accompanyingCourse.resources.length, counter: state => state.accompanyingCourse.resources.length,
suggestedEntities: state => [ suggestedEntities: state => [
state.accompanyingCourse.requestor, state.accompanyingCourse.requestor,
...state.accompanyingCourse.participations.map(p => p.person), ...state.accompanyingCourse.participations.filter((p) => p.endDate === null).map((p) => p.person),
] ]
.filter((e) => e !== null) .filter((e) => e !== null)
.filter( .filter(

View File

@ -20,6 +20,8 @@
<div class="flex-table accompanyingcourse-list"> <div class="flex-table accompanyingcourse-list">
{% for period in accompanyingPeriods %} {% for period in accompanyingPeriods %}
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period, 'recordAction': _self.recordAction(period)} %} {% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period, 'recordAction': _self.recordAction(period)} %}
{% else %}
<p class="chill-no-data-statement">{{ 'Any accompanying period'|trans }}</p>
{% endfor %} {% endfor %}
</div> </div>

View File

@ -172,7 +172,7 @@
{%- if options['customButtons']['replace'] is defined -%} {%- if options['customButtons']['replace'] is defined -%}
{{ options['customButtons']['replace'] }} {{ options['customButtons']['replace'] }}
{%- elseif is_granted('CHILL_PERSON_SEE', person) -%} {%- elseif is_granted('CHILL_PERSON_SEE', person) and options['addLink'] -%}
<li> <li>
<a class="btn btn-sm btn-show" title="{{ 'Show person'|trans }}" <a class="btn btn-sm btn-show" title="{{ 'Show person'|trans }}"
href="{{ path('chill_person_view', { person_id: person.id }) }}"></a> href="{{ path('chill_person_view', { person_id: person.id }) }}"></a>

View File

@ -56,6 +56,11 @@
{% if action is defined %} {% if action is defined %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ path('chill_person_resource_list', { 'person_id': resource.personOwner.id } ) }}" class="btn btn-cancel">
{{ 'Cancel'|trans }}
</a>
</li>
<li class="edit"> <li class="edit">
<button class="btn btn-edit" <button class="btn btn-edit"
type="submit" id="newPersonResource"> type="submit" id="newPersonResource">

View File

@ -22,41 +22,33 @@
<div class="flex-table"> <div class="flex-table">
{% for resource in personResources %} {% for resource in personResources %}
<div class="item-bloc"> <div class="item-bloc">
<div class="item-row"> {% if resource.kind is not null %}
<div class="item-col" style="width: 50%"> <div class="item-row">
<section>
<p>{{ resource.kind.title.fr|capitalize }}</p>
</section>
</div>
{% endif %}
<div class="{% if resource.kind is not null %} separator {% endif %}">
{% if resource.person is not null %} {% if resource.person is not null %}
<div class="denomination h3"> {{ resource.person|chill_entity_render_box({
<span> 'render': 'bloc',
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { 'addLink': true,
action: 'show', displayBadge: true, 'addInfo': true,
targetEntity: { name: 'person', id: resource.person.id }, 'addAge': true
buttonText: resource.person|chill_entity_render_string, }) }}
isDead: resource.person.deathdate is not null
} %}
</span>
</div>
{% elseif resource.thirdparty is not null %} {% elseif resource.thirdparty is not null %}
<div class="denomination h3"> {{ resource.thirdparty|chill_entity_render_box({
<span> 'render': 'bloc',
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { 'showContacts': false,
action: 'show', displayBadge: true, 'addLink': true,
targetEntity: { name: 'thirdparty', id: resource.thirdparty.id }, 'isConfidential': (resource.thirdparty.contactDataAnonymous ? true : false)
buttonText: resource.thirdParty|chill_entity_render_string, }) }}
parent: resource.thirdparty.parent
} %}
</span>
</div>
{% else %} {% else %}
<div class="denomination h3"> <div class="denomination h3">
<span>{{ resource.freetext }}</span> <span>{{ resource.freetext }}</span>
</div> </div>
{% endif %} {% endif %}
</div>
<div class="item-col" style="justify-content: flex-end; ">
{% if resource.kind %}
<span>{{ resource.kind.title.fr|capitalize }}</span>
{% endif %}
</div>
</div> </div>
{% if resource.comment.comment is not empty %} {% if resource.comment.comment is not empty %}
<div class="item-row separator"> <div class="item-row separator">

View File

@ -16,14 +16,32 @@ use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationVoter;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowHandlerInterface class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowHandlerInterface
{ {
private AccompanyingPeriodWorkEvaluationRepository $repository; private AccompanyingPeriodWorkEvaluationRepository $repository;
public function __construct(AccompanyingPeriodWorkEvaluationRepository $repository) private TranslatorInterface $translator;
public function __construct(AccompanyingPeriodWorkEvaluationRepository $repository, TranslatorInterface $translator)
{ {
$this->repository = $repository; $this->repository = $repository;
$this->translator = $translator;
}
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{
$evaluation = $this->getRelatedEntity($entityWorkflow);
return [
'persons' => $evaluation->getAccompanyingPeriodWork()->getPersons(),
];
}
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string
{
return $this->translator->trans('workflow.Evaluation (n°%eval%)', ['%eval%' => $entityWorkflow->getRelatedEntityId()]);
} }
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingPeriodWorkEvaluation public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingPeriodWorkEvaluation

View File

@ -15,17 +15,34 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface
{ {
private AccompanyingPeriodWorkRepository $repository; private AccompanyingPeriodWorkRepository $repository;
public function __construct(AccompanyingPeriodWorkRepository $repository) private TranslatorInterface $translator;
public function __construct(AccompanyingPeriodWorkRepository $repository, TranslatorInterface $translator)
{ {
$this->repository = $repository; $this->repository = $repository;
$this->translator = $translator;
} }
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{
return [
'persons' => $this->getRelatedEntity($entityWorkflow)
->getPersons(),
];
}
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string
{
return $this->translator->trans('workflow.Work (n°%w%)', ['%w%' => $entityWorkflow->getRelatedEntityId()]);
}
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingPeriodWork
{ {
return $this->repository->find($entityWorkflow->getRelatedEntityId()); return $this->repository->find($entityWorkflow->getRelatedEntityId());
} }

View File

@ -240,6 +240,7 @@ Select a type: "Choisissez un type"
Select a person: "Choisissez un usager" Select a person: "Choisissez un usager"
Select a thirdparty: "Choisissez un tiers" Select a thirdparty: "Choisissez un tiers"
Contact person: "Personne de contact" Contact person: "Personne de contact"
Kind: "Type"
# pickAPersonType # pickAPersonType

View File

@ -180,6 +180,8 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
* @var string * @var string
* @ORM\Column(name="name", type="string", length=255) * @ORM\Column(name="name", type="string", length=255)
* @Assert\Length(min="2") * @Assert\Length(min="2")
* @Assert\NotNull
* @Assert\NotBlank
* @Groups({"read", "write", "docgen:read", "docgen:read:3party:parent"}) * @Groups({"read", "write", "docgen:read", "docgen:read:3party:parent"})
*/ */
private ?string $name = ''; private ?string $name = '';

View File

@ -118,7 +118,7 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<span class="input-group-text" id="phonenumber"><i class="fa fa-fw fa-phone"></i></span> <span class="input-group-text" id="phonenumber"><i class="fa fa-fw fa-phone"></i></span>
<input class="form-control form-control-lg" <input class="form-control form-control-lg"
v-model="thirdparty.phonenumber" v-model="thirdparty.telephone"
v-bind:placeholder="$t('thirdparty.phonenumber')" v-bind:placeholder="$t('thirdparty.phonenumber')"
v-bind:aria-label="$t('thirdparty.phonenumber')" v-bind:aria-label="$t('thirdparty.phonenumber')"
aria-describedby="phonenumber" /> aria-describedby="phonenumber" />
@ -155,7 +155,11 @@ export default {
return { return {
//context: {}, <-- //context: {}, <--
thirdparty: { thirdparty: {
type: 'thirdparty' type: 'thirdparty',
address: null,
kind: 'company',
name: '',
telephone: '',
}, },
professions: [], professions: [],
civilities: [], civilities: [],

View File

@ -87,14 +87,24 @@ class ThirdPartyApiSearch implements SearchApiInterface
foreach ($strs as $str) { foreach ($strs as $str) {
if (!empty($str)) { if (!empty($str)) {
$wheres[] = "(LOWER(UNACCENT(?)) <<% tparty.canonicalized OR $wheres[] = "(LOWER(UNACCENT(?)) <<% tparty.canonicalized OR
tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')"; tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')
$whereArgs[] = [$str, $str]; OR
$pertinence[] = 'STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized) + ' . (LOWER(UNACCENT(?)) <<% parent.canonicalized OR
"(tparty.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int + " . parent.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')
";
$whereArgs[] = [$str, $str, $str, $str];
$pertinence[] = 'GREATEST(
STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized),
STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), parent.canonicalized)
) + ' .
"GREATEST(
(tparty.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int,
(parent.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int
) + " .
// take postcode label into account, but lower than the canonicalized field // take postcode label into account, but lower than the canonicalized field
"COALESCE((LOWER(UNACCENT(cmpc.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0) + " . "COALESCE((LOWER(UNACCENT(cmpc.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0) + " .
"COALESCE((LOWER(UNACCENT(cmpc_p.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0)"; "COALESCE((LOWER(UNACCENT(cmpc_p.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0)";
$pertinenceArgs[] = [$str, $str, $str, $str]; $pertinenceArgs[] = [$str, $str, $str, $str, $str, $str];
} }
} }