Merge branch 'issue491_thirdparty_edit_modal' into 'master'

Thirdparty: fix edit modal + add firstname

See merge request Chill-Projet/chill-bundles!379
This commit is contained in:
Julien Fastré 2022-03-24 18:33:31 +00:00
commit 367188b03d
23 changed files with 350 additions and 96 deletions

View File

@ -56,6 +56,10 @@ and this project adheres to
* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)
* [person_resource] separate create page created to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/504)
* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506)
* [thirdparty] For contacts show current civility/profession in edit form + fix saving of edited information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/491)
* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
* [thirdparty] add firstname field to thirdparty 'child' or 'contact' types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/508)
* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
* [parcours] List of parcours for a specific user so they can be reassigned in case of absence (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/509)

View File

@ -21,14 +21,12 @@ const makeFetch = (method, url, body, options) => {
}
if (response.status === 422) {
console.log('422 error')
return response.json().then(response => {
throw ValidationException(response)
});
}
if (response.status === 403) {
console.log('403 error')
throw AccessException(response);
}

View File

@ -223,23 +223,26 @@ export default {
default:
if (typeof this.type === 'undefined') { // action=create or addContact
console.log('will rewrite data');
// console.log('will rewrite data');
if (this.action === 'addContact') {
type = 'thirdparty'
data = this.$refs.castThirdparty.$data.thirdparty;
console.log('data original', data);
// console.log('data original', data);
data.parent = {type: "thirdparty", id: this.parent.id};
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
data.profession = data.profession !== null ? {type: 'third_party_profession', id: data.profession.id} : null;
} else {
type = this.$refs.castNew.radioType;
data = this.$refs.castNew.castDataByType();
console.log(data)
// console.log('type', type);
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
data.profession = data.profession !== null ? {type: 'third_party_profession', id: data.profession.id} : null;
// console.log('onthefly data', data);
}
} else {
throw 'error with object type';
}
}
console.log('type', type);
console.log('data', data);
// pass datas to parent
this.$emit('saveFormOnTheFly', { type: type, data: data });
},

View File

@ -89,11 +89,9 @@ export default {
return this.isMultiline === true ? "div" : "span";
},
multiline() {
//console.log(this.isMultiline, typeof this.isMultiline);
return this.isMultiline === true ? "multiline" : "";
},
isConfidential() {
console.log(this.address.confidential)
return this.address.confidential;
}
}

View File

@ -137,7 +137,7 @@ export default {
},
methods: {
saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
// console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'resource';
let body = { type: payload.type };
@ -167,13 +167,19 @@ export default {
})
}
else if (payload.type === 'thirdparty') {
console.log('data', payload.data)
// console.log('data', payload.data)
body.firstname = payload.data.firstname;
body.name = payload.data.name;
body.email = payload.data.email;
body.telephone = payload.data.telephone;
body.civility = payload.data.civility;
body.profession = payload.data.profession;
body.address = payload.data.address ? { id: payload.data.address.address_id } : null;
if (null !== payload.data.civility) {
body.civility = {type: 'chill_main_civility', id: payload.data.civility.id};
}
if (null !== payload.data.profession) {
body.profession = {type: 'third_party_profession', id: payload.data.profession.id};
}
// console.log('body', body);
makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)
.then(response => {

View File

@ -24,11 +24,11 @@
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="lastname"
v-model="lastName"
:placeholder="$t('person.lastname')"
@change="checkErrors"
class="form-control form-control-lg"
id="lastname"
v-model="lastName"
:placeholder="$t('person.lastname')"
@change="checkErrors"
/>
<label for="lastname">{{ $t('person.lastname') }}</label>
</div>
@ -43,11 +43,11 @@
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="firstname"
v-model="firstName"
:placeholder="$t('person.firstname')"
@change="checkErrors"
class="form-control form-control-lg"
id="firstname"
v-model="firstName"
:placeholder="$t('person.firstname')"
@change="checkErrors"
/>
<label for="firstname">{{ $t('person.firstname') }}</label>
</div>
@ -62,10 +62,10 @@
<div v-for="(a, i) in config.altNames" :key="a.key" class="form-floating mb-3">
<input
class="form-control form-control-lg"
:id="a.key"
:value="personAltNamesLabels[i]"
@input="onAltNameInput"
class="form-control form-control-lg"
:id="a.key"
:value="personAltNamesLabels[i]"
@input="onAltNameInput"
/>
<label :for="a.key">{{ a.labels.fr }}</label>
</div>
@ -125,9 +125,9 @@
</div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</div>
@ -238,13 +238,13 @@ export default {
checkErrors(e) {
this.errors = [];
if (!this.person.lastName) {
this.errors.push("Le nom ne doit pas être vide.");
this.errors.push("Le nom ne doit pas être vide.");
}
if (!this.person.firstName) {
this.errors.push("Le prénom ne doit pas être vide.");
this.errors.push("Le prénom ne doit pas être vide.");
}
if (!this.person.gender) {
this.errors.push("Le genre doit être renseigné");
this.errors.push("Le genre doit être renseigné");
}
},
loadData() {

View File

@ -46,6 +46,7 @@ class ChillThirdPartyExtension extends Extension implements PrependExtensionInte
$loader->load('services/fixtures.yaml');
$loader->load('services/serializer.yaml');
$loader->load('services/repository.yaml');
$loader->load('services/doctrineEventListener.yaml');
}
public function prepend(ContainerBuilder $container)

View File

@ -197,6 +197,12 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
*/
private ?string $email = null;
/**
* @ORM\Column(name="firstname", type="text", options={"default":""})
* @Groups({"read", "write", "docgen:read", "docgen:read:3party:parent"})
*/
private string $firstname = '';
/**
* @var int
* @ORM\Column(name="id", type="integer")
@ -454,12 +460,12 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
return $this->email;
}
/**
* Get id.
*
* @return int
*/
public function getId()
public function getFirstname(): string
{
return $this->firstname;
}
public function getId(): ?int
{
return $this->id;
}
@ -469,12 +475,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
return $this->kind;
}
/**
* Get name.
*
* @return string
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@ -766,6 +767,13 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
return $this;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function setKind(?string $kind): ThirdParty
{
$this->kind = $kind;
@ -773,14 +781,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
return $this;
}
/**
* Set name.
*
* @param string $name
*
* @return ThirdParty
*/
public function setName($name)
public function setName($name): self
{
$this->name = $name;

View File

@ -0,0 +1,31 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\ThirdPartyBundle\EventListener;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use const MB_CASE_TITLE;
class ThirdPartyEventListener
{
public function prePersistThirdParty(ThirdParty $thirdparty): void
{
if ($thirdparty->getKind() !== 'company') {
$firstnameCaps = mb_convert_case(mb_strtolower($thirdparty->getFirstname()), MB_CASE_TITLE, 'UTF-8');
$firstnameCaps = ucwords(strtolower($firstnameCaps), " \t\r\n\f\v'-");
$thirdparty->setFirstName($firstnameCaps);
$lastnameCaps = mb_strtoupper($thirdparty->getName(), 'UTF-8');
$thirdparty->setName($lastnameCaps);
}
}
}

View File

@ -102,6 +102,10 @@ class ThirdPartyType extends AbstractType
// Contact Person ThirdParty (child)
if (ThirdParty::KIND_CONTACT === $options['kind'] || ThirdParty::KIND_CHILD === $options['kind']) {
$builder
->add('firstname', TextType::class, [
'label' => 'firstname',
'required' => false,
])
->add('civility', PickCivilityType::class, [
'label' => 'thirdparty.Civility',
'placeholder' => 'thirdparty.choose civility',

View File

@ -65,34 +65,64 @@
</div>
<div v-if="thirdparty.kind === 'child' || thirdparty.kind === 'contact'">
<div id="child-info">
<div class="input-group mb-3">
<select class="form-select form-select-lg" id="profession"
<div class="child-info">
<div class="input-group mb-3 input-section">
<select class="form-select form-select-lg" id="civility"
v-model="thirdparty.civility">
<option selected disabled :value="null" >{{ $t('thirdparty.civility') }}</option>
<option v-for="civility in civilities" :key="civility.id" :value="{type: 'chill_main_civility', id: civility.id }">{{ civility.name.fr }}</option>
<option v-for="civility in civilities" :key="civility.id" :value="civility">{{ civility.name.fr }}</option>
</select>
</div>
<div class="input-group mb-3">
<select class="form-select form-select-lg" id="civility"
<div class="input-group mb-3 input-section">
<select class="form-select form-select-lg" id="profession"
v-model="thirdparty.profession">
<option selected disabled :value="null">{{ $t('thirdparty.profession') }}</option>
<option v-for="profession in professions" :key="profession.id" :value="{type: 'third_party_profession', id: profession.id }">{{ profession.name.fr }}</option>
<option v-for="profession in professions" :key="profession.id" :value="profession">{{ profession.name.fr }}</option>
</select>
</div>
</div>
<div class="child-info">
<div class="input-section">
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="firstname" v-model="thirdparty.firstname" v-bind:placeholder="$t('thirdparty.firstname')" />
<label for="firstname">{{ $t('thirdparty.firstname') }}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li v-for="(qi, i) in queryItems" :key="i" @click="addQueryItem('firstName', qi)">
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
</div>
<div class="input-section">
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="thirdparty.name" v-bind:placeholder="$t('thirdparty.lastname')" />
<label for="name">{{ $t('thirdparty.lastname') }}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li v-for="(qi, i) in queryItems" :key="i" @click="addQueryItem('name', qi)">
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="thirdparty.name" v-bind:placeholder="$t('thirdparty.name')" />
<label for="name">{{ $t('thirdparty.name') }}</label>
</div>
<div v-if="query">
<ul class="list-suggest add-items inline">
<li @click="addQuery(query)">
<span class="person-text">{{ query }}</span>
</li>
</ul>
<div v-if="thirdparty.kind === 'company'">
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="thirdparty.name" v-bind:placeholder="$t('thirdparty.name')" />
<label for="name">{{ $t('thirdparty.name') }}</label>
</div>
<div v-if="query">
<ul class="list-suggest add-items inline">
<li @click="addQuery(query)">
<span class="person-text">{{ query }}</span>
</li>
</ul>
</div>
</div>
<template
@ -158,6 +188,7 @@ export default {
type: 'thirdparty',
address: null,
kind: 'company',
firstname: '',
name: '',
telephone: '',
civility: null,
@ -209,17 +240,18 @@ export default {
context.addressId = this.thirdparty.address.address_id;
context.edit = true;
}
console.log('context', context);
//this.context = context; <--
return context;
},
queryItems() {
return this.query ? this.query.split(' ') : null;
},
},
methods: {
loadData(){
return getThirdparty(this.id).then(thirdparty => new Promise((resolve, reject) => {
this.thirdparty = thirdparty;
this.thirdparty.kind = thirdparty.kind;
console.log('get thirdparty', thirdparty);
if (this.action !== 'show') {
if (thirdparty.address !== null) {
// bof! we force getInitialAddress because addressId not available when mounted
@ -262,14 +294,24 @@ export default {
console.log('switch address to edit mode', this.context);
}
},
addQueryItem(field, queryItem) {
switch (field) {
case 'name':
this.thirdparty.name = queryItem;
break;
case 'firstName':
this.thirdparty.firstname = queryItem;
break;
}
},
addQuery(query) {
this.thirdparty.name = query;
},
},
mounted() {
let dependencies = [];
dependencies.push(this.loadProfessions());
dependencies.push(this.loadCivilities());
mounted() {
let dependencies = [];
dependencies.push(this.loadProfessions());
dependencies.push(this.loadCivilities());
if (this.action !== 'create') {
if (this.id) {
dependencies.push(this.loadData());
@ -307,12 +349,13 @@ dl {
margin-bottom: 1rem;
}
#child-info {
.child-info {
display: flex;
justify-content: space-between;
div {
.input-section {
width: 49%;
}
}
</style>

View File

@ -1,6 +1,8 @@
const thirdpartyMessages = {
fr: {
thirdparty: {
firstname: "Prénom",
lastname: "Nom",
name: "Dénomination",
email: "Courriel",
phonenumber: "Téléphone",

View File

@ -5,6 +5,11 @@
{{ form_row(form.name) }}
{% if form.firstname is defined %}
{{ form_row(form.firstname) }}
{% endif %}
{% if form.nameCompany is defined %}
{{ form_row(form.nameCompany) }}
{{ form_row(form.acronym) }}

View File

@ -2,20 +2,25 @@
{% block _third_party_active_children_entry_widget %}
<div class="container">
<div class="row">
<div class="form-group col-md-3 mb-3">
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.civility) }}
{{ form_errors(form.civility) }}
{{ form_label(form.civility) }}
</div>
<div class="form-group col-md-5 mb-3">
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.profession) }}
{{ form_errors(form.profession) }}
{{ form_label(form.profession) }}
</div>
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.name) }}
{{ form_errors(form.name) }}
{{ form_label(form.name) }}
</div>
<div class="form-group col-md-4 mb-3">
{{ form_widget(form.profession) }}
{{ form_errors(form.profession) }}
{{ form_label(form.profession) }}
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.firstname) }}
{{ form_errors(form.firstname) }}
{{ form_label(form.firstname) }}
</div>
</div>
<div class="row">

View File

@ -1,7 +1,8 @@
{% extends "@ChillMain/layout.html.twig" %}
{% set thirdParty = entity %}
{% set title_ = 'Show third party %name%'|trans({'%name%' : thirdParty.name }) %}
{% set name = thirdParty.firstname is not empty ? thirdParty.firstname ~ ' ' ~ thirdParty.name : thirdParty.name %}
{% set title_ = 'Show third party %name%'|trans({'%name%' : name }) %}
{% block title title_ %}
@ -25,7 +26,7 @@
<dt>{{ 'Name'|trans }}</dt>
<dd>
{% if thirdParty.isLeaf == true %}{{ thirdParty.civility }}{% endif %}
{{ thirdParty.name }}
{{ thirdParty.firstname ~ ' ' ~ thirdParty.name }}
</dd>
{% if thirdParty.kind == 'company' %}

View File

@ -43,6 +43,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf
{
return [
'type' => 'thirdparty',
'firstname' => $thirdParty->getFirstname(),
'name' => $thirdParty->getName(),
'text' => $this->thirdPartyRender->renderString($thirdParty, []),
'id' => $thirdParty->getId(),

View File

@ -77,7 +77,9 @@ class ThirdPartyRender extends AbstractChillEntityRender
$acronym = '';
}
return $civility . $entity->getName() . $acronym;
$firstname = empty($entity->getFirstname()) ? '' : $entity->getFirstname();
return $civility . $firstname . ' ' . $entity->getName() . $acronym;
}
public function supports($entity, array $options): bool

View File

@ -20,6 +20,8 @@ components:
type: string
enum:
- "thirdparty"
firstname:
type: string
name:
type: string
email:

View File

@ -0,0 +1,9 @@
services:
Chill\ThirdPartyBundle\EventListener\ThirdPartyEventListener:
autoconfigure: true
tags:
-
name: 'doctrine.orm.entity_listener'
event: 'prePersist'
entity: 'Chill\ThirdPartyBundle\Entity\ThirdParty'
method: 'prePersistThirdParty'

View File

@ -39,15 +39,15 @@ final class Version20211007165001 extends AbstractMigration
$this->addSql("
UPDATE chill_3party.third_party
SET canonicalized =
UNACCENT(
LOWER(
name ||
CASE WHEN COALESCE(name_company, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(name_company, '') ||
CASE WHEN COALESCE(acronym, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(acronym, '')
)
)
UNACCENT(
LOWER(
name ||
CASE WHEN COALESCE(name_company, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(name_company, '') ||
CASE WHEN COALESCE(acronym, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(acronym, '')
)
)
");
$this->addSql("
CREATE OR REPLACE FUNCTION chill_3party.canonicalize() RETURNS TRIGGER

View File

@ -0,0 +1,38 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\Migrations\ThirdParty;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220322095659 extends AbstractMigration
{
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE chill_3party.third_party DROP firstname');
}
public function getDescription(): string
{
return 'Add firstname to thirdparty';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE chill_3party.third_party ADD firstname TEXT DEFAULT \'\'');
}
}

View File

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\ThirdParty;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220324175549 extends AbstractMigration
{
public function getDescription(): string
{
return 'indexing of firstname on third parties';
}
public function up(Schema $schema): void
{
$this->addSql("
UPDATE chill_3party.third_party
SET canonicalized =
UNACCENT(
LOWER(
name ||
CASE WHEN firstname <> '' THEN ' ' ELSE '' END ||
firstname ||
CASE WHEN COALESCE(name_company, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(name_company, '') ||
CASE WHEN COALESCE(acronym, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(acronym, '')
)
)
");
$this->addSql("
CREATE OR REPLACE FUNCTION chill_3party.canonicalize() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
NEW.canonicalized =
UNACCENT(
LOWER(
NEW.name ||
CASE WHEN NEW.firstname <> '' THEN ' ' ELSE '' END ||
NEW.firstname ||
CASE WHEN COALESCE(NEW.name_company, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(NEW.name_company, '') ||
CASE WHEN COALESCE(NEW.acronym, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(NEW.acronym, '')
)
)
;
return NEW;
END
$$
");
}
public function down(Schema $schema): void
{
$this->addSql("
CREATE OR REPLACE FUNCTION chill_3party.canonicalize() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
NEW.canonicalized =
UNACCENT(
LOWER(
NEW.name ||
CASE WHEN COALESCE(NEW.name_company, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(NEW.name_company, '') ||
CASE WHEN COALESCE(NEW.acronym, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(NEW.acronym, '')
)
)
;
return NEW;
END
$$
");
$this->addSql("
UPDATE chill_3party.third_party
SET canonicalized =
UNACCENT(
LOWER(
name ||
CASE WHEN COALESCE(name_company, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(name_company, '') ||
CASE WHEN COALESCE(acronym, '') <> '' THEN ' ' ELSE '' END ||
COALESCE(acronym, '')
)
)
");
}
}

View File

@ -1,6 +1,7 @@
Third party: Tiers
Third parties: Tiers
third parties: tiers
firstname: Prénom
name: Nom
telephone: Téléphone
adress: Adresse