Merge remote-tracking branch 'origin/master' into issue447_pickCivilityType

This commit is contained in:
2022-02-21 17:19:21 +01:00
135 changed files with 2135 additions and 887 deletions

View File

@@ -25,7 +25,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_merge;

View File

@@ -45,6 +45,40 @@ use function spl_object_hash;
* all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this
* center.
*
* A ThirdParty may have:
*
* * 0, one or more categories;
* * 0, one or more type
* * 1 kind.
*
* ## Kind
*
* The kind indicate if a thirdparty is a:
*
* * company ("personne morale");
* * a contact ("personne morale")
* * a child inside a company ("contact" in French). Only companies may have childs
*
* **take care** the french translation may lead to misinterpretation, as the string "contact" is the translation of
* kind "child".
*
* ## Categories and types
*
* ThirdParty may have zero, one or more categories and/or type.
*
* Categories are managed in the database. The Chill administrator may create or desactivate categories.
*
* Types are also a way to categorize the thirdparties, but this is done **into the code**, through
* @link{Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeProviderInterface}. The type is stored into the
* database by a Php array, mapped by a jsonb into the database. This has one advantage: it is easily searchable.
*
* As the list of type is hardcoded into database, it is more easily searchable. (for chill 2.0, the
* @link{Chill\ThirdPartyBundle\Form\Type\PickThirdPartyDynamicType} does not support it yet, but
* the legacy @link{Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType} does.
*
* The difference between categories and types is transparent for user: they choose the same fields into the UI, without
* noticing a difference.
*
* @ORM\Entity
* @ORM\Table(name="chill_3party.third_party")
* @DiscriminatorMap(typeProperty="type", mapping={
@@ -179,6 +213,8 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
* @var string
* @ORM\Column(name="name", type="string", length=255)
* @Assert\Length(min="2")
* @Assert\NotNull
* @Assert\NotBlank
* @Groups({"read", "write", "docgen:read", "docgen:read:3party:parent"})
*/
private ?string $name = '';

View File

@@ -22,7 +22,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_diff;
use function array_merge;

View File

@@ -15,7 +15,7 @@ use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\ThirdPartyBundle\Security\Voter\ThirdPartyVoter;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Add an entry in section to go to third party index page.

View File

@@ -21,57 +21,64 @@
</div>
<div v-else-if="action === 'edit' || action === 'create'">
<div class="form-floating mb-3" v-if="thirdparty.kind !== 'child'">
<div class="form-check">
<input class="form-check-input mt-0" type="radio" v-model="kind" value="company" id="tpartyKindInstitution">
<label for="tpartyKindInstitution" class="required">
<badge-entity
:entity="{ type: 'thirdparty', kind: 'company' }"
:options="{ displayLong: true }">
</badge-entity>
</label>
</div>
<div class="form-check">
<input class="form-check-input mt-0" type="radio" v-model="kind" value="contact" id="tpartyKindContact">
<label for="tpartyKindContact" class="required">
<badge-entity
:entity="{ type: 'thirdparty', kind: 'contact' }"
:options="{ displayLong: true }">
</badge-entity>
</label>
</div>
</div>
<div v-else>
<p>Contact de&nbsp;:</p>
<third-party-render-box :thirdparty="thirdparty.parent"
:options="{
addInfo: true,
addEntity: false,
addAltNames: true,
addId: false,
addLink: false,
addAge: false,
hLevel: 4,
addCenter: false,
addNoData: true,
isMultiline: false
}"></third-party-render-box>
</div>
<div class="form-floating mb-3" v-if="thirdparty.kind !== 'child'">
<div class="form-check">
<input class="form-check-input mt-0" type="radio" v-model="kind" value="company" id="tpartyKindInstitution">
<label for="tpartyKindInstitution" class="required">
<badge-entity
:entity="{ type: 'thirdparty', kind: 'company' }"
:options="{ displayLong: true }">
</badge-entity>
</label>
</div>
<div class="form-check">
<input class="form-check-input mt-0" type="radio" v-model="kind" value="contact" id="tpartyKindContact">
<label for="tpartyKindContact" class="required">
<badge-entity
:entity="{ type: 'thirdparty', kind: 'contact' }"
:options="{ displayLong: true }">
</badge-entity>
</label>
</div>
</div>
<div v-else>
<p>Contact de&nbsp;:</p>
<third-party-render-box :thirdparty="thirdparty.parent"
:options="{
addInfo: true,
addEntity: false,
addAltNames: true,
addId: false,
addLink: false,
addAge: false,
hLevel: 4,
addCenter: false,
addNoData: true,
isMultiline: false
}"></third-party-render-box>
</div>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="thirdparty.text" 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>
<template
v-if="thirdparty.kind !== 'child'">
<add-address
key="thirdparty"
:context="context"
:options="addAddress.options"
:address-changed-callback="submitAddress"
ref="addAddress">
</add-address>
</template>
<template
v-if="thirdparty.kind !== 'child'">
<add-address
key="thirdparty"
:context="context"
:options="addAddress.options"
:address-changed-callback="submitAddress"
ref="addAddress">
</add-address>
</template>
<div class="input-group mb-3">
<span class="input-group-text" id="email"><i class="fa fa-fw fa-envelope"></i></span>
@@ -102,7 +109,7 @@ import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue';
export default {
name: "OnTheFlyThirdParty",
props: ['id', 'type', 'action'],
props: ['id', 'type', 'action', 'query'],
components: {
ThirdPartyRenderBox,
AddAddress,
@@ -113,6 +120,11 @@ export default {
//context: {}, <--
thirdparty: {
type: 'thirdparty',
address: null,
kind: 'company',
name: '',
telephone: '',
},
addAddress: {
options: {
@@ -122,27 +134,27 @@ export default {
size: 'btn-sm'
},
title: {
create: 'add_an_address_title',
edit: 'edit_address'
create: 'add_an_address_title',
edit: 'edit_address'
}
}
}
}
},
computed: {
kind: {
get() {
// note: there are also default to 'institution' set in the "mounted" method
if (this.$data.thirdparty.kind !== undefined) {
return this.$data.thirdparty.kind;
} else {
return 'company';
}
},
set(v) {
this.$data.thirdparty.kind = v;
}
},
kind: {
get() {
// note: there are also default to 'institution' set in the "mounted" method
if (this.$data.thirdparty.kind !== undefined) {
return this.$data.thirdparty.kind;
} else {
return 'company';
}
},
set(v) {
this.$data.thirdparty.kind = v;
}
},
context() {
let context = {
target: {
@@ -153,10 +165,10 @@ export default {
addressId: null
};
if ( !(this.thirdparty.address === undefined || this.thirdparty.address === null)
&& this.thirdparty.address.address_id !== null
&& this.thirdparty.address.address_id !== null
) { // to complete
context.addressId = this.thirdparty.address.address_id;
context.edit = true;
context.addressId = this.thirdparty.address.address_id;
context.edit = true;
}
console.log('context', context);
//this.context = context; <--
@@ -170,10 +182,10 @@ export default {
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
this.$refs.addAddress.getInitialAddress(thirdparty.address.address_id);
}
if (thirdparty.address !== null) {
// bof! we force getInitialAddress because addressId not available when mounted
this.$refs.addAddress.getInitialAddress(thirdparty.address.address_id);
}
}
resolve();
}));
@@ -181,11 +193,14 @@ export default {
submitAddress(payload) {
console.log('submitAddress', payload);
if (typeof payload.addressId !== 'undefined') { // <--
this.context.edit = true;
this.context.addressId = payload.addressId; // bof! use legacy and not legacy in payload
this.thirdparty.address = payload.address; // <--
console.log('switch address to edit mode', this.context);
}
this.context.edit = true;
this.context.addressId = payload.addressId; // bof! use legacy and not legacy in payload
this.thirdparty.address = payload.address; // <--
console.log('switch address to edit mode', this.context);
}
},
addQuery(query) {
this.thirdparty.text = query;
}
},
mounted() {
@@ -193,7 +208,7 @@ export default {
if (this.action !== 'create') {
this.loadData();
} else {
this.thirdparty.kind = 'company';
this.thirdparty.kind = 'company';
}
},
}

View File

@@ -87,14 +87,24 @@ class ThirdPartyApiSearch implements SearchApiInterface
foreach ($strs as $str) {
if (!empty($str)) {
$wheres[] = "(LOWER(UNACCENT(?)) <<% tparty.canonicalized OR
tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')";
$whereArgs[] = [$str, $str];
$pertinence[] = 'STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized) + ' .
"(tparty.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int + " .
tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')
OR
(LOWER(UNACCENT(?)) <<% parent.canonicalized OR
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
"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)";
$pertinenceArgs[] = [$str, $str, $str, $str];
$pertinenceArgs[] = [$str, $str, $str, $str, $str, $str];
}
}

View File

@@ -1,7 +1,7 @@
services:
Chill\ThirdPartyBundle\Menu\MenuBuilder:
arguments:
$translator: '@Symfony\Component\Translation\TranslatorInterface'
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
tags:
- { name: 'chill.menu_builder' }