mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch 'features/thirdparty-improve-acl-collection' into 'master'
3party, suite See merge request Chill-Projet/chill-bundles!173
This commit is contained in:
commit
a75f4015c7
@ -10,6 +10,10 @@ and this project adheres to
|
||||
|
||||
## Unreleased
|
||||
|
||||
* [3party]: french translation of contact and company
|
||||
* [3party]: show parent in list
|
||||
* [3party]: change color for badge "child"
|
||||
|
||||
|
||||
## Test releases
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
@ -33,7 +33,7 @@
|
||||
{# CFChoice : render the different elements in a choice list #}
|
||||
{% block cf_choices_row %}
|
||||
<h3>{{ 'Choices'|trans }}</h3>
|
||||
|
||||
|
||||
<div id="{{ form.vars.id }}" data-prototype="{{- form_row(form.vars.prototype.children.name)
|
||||
~ form_row(form.vars.prototype.children.active)
|
||||
~ form_row(form.vars.prototype.children.slug) -}}">
|
||||
@ -47,8 +47,8 @@
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #}
|
||||
<script type="text/javascript">
|
||||
function addElementInDiv(div_id) {
|
||||
@ -109,3 +109,13 @@
|
||||
|
||||
{# The choice_with_other_widget widget is defined in the main bundle #}
|
||||
|
||||
{% block pick_address_row %}
|
||||
{{ form_label(form) }}
|
||||
{{ form_errors(form) }}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block pick_address_widget %}
|
||||
{{ form_widget(form) }}
|
||||
<div data-input-address-container="{{ form.vars.uniqid }}"></div>
|
||||
{% endblock %}
|
||||
|
@ -17,20 +17,21 @@ class LoadCivility extends Fixture implements FixtureGroupInterface
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$civilities = [
|
||||
['name' => ['fr' => "Monsieur" ]],
|
||||
['name' => ['fr' => "Madame" ]],
|
||||
['name' => ['fr' => "Docteur" ]],
|
||||
['name' => ['fr' => "Professeur" ]],
|
||||
['name' => ['fr' => "Madame la Directrice" ]],
|
||||
['name' => ['fr' => "Monsieur le Directeur" ]],
|
||||
['name' => ['fr' => "Monsieur" ], 'abbrev' => ['fr' => 'M.']],
|
||||
['name' => ['fr' => "Madame" ], 'abbrev' => ['fr' => 'Mme']],
|
||||
['name' => ['fr' => "Docteur" ], 'abbrev' => ['fr' => 'Dr']],
|
||||
['name' => ['fr' => "Professeur" ], 'abbrev' => ['fr' => 'Pr']],
|
||||
['name' => ['fr' => "Madame la Directrice" ], 'abbrev' => ['fr' => 'Mme']],
|
||||
['name' => ['fr' => "Monsieur le Directeur" ], 'abbrev' => ['fr' => 'M.']],
|
||||
['name' => ['fr' => "Madame la Maire" ]],
|
||||
['name' => ['fr' => "Monsieur le Maire" ]],
|
||||
['name' => ['fr' => "Maître" ]],
|
||||
['name' => ['fr' => "Maître" ], 'abbrev' => ['fr' => 'Me']],
|
||||
];
|
||||
|
||||
foreach ( $civilities as $val) {
|
||||
$civility = (new Civility())
|
||||
->setName($val['name'])
|
||||
->setAbbreviation($val['abbrev'] ?? [])
|
||||
->setActive(true);
|
||||
$manager->persist($civility);
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Form\Type\DataTransformer;
|
||||
|
||||
use Chill\MainBundle\Repository\AddressRepository;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
final class AddressToIdDataTransformer implements DataTransformerInterface
|
||||
{
|
||||
private AddressRepository $addressRepository;
|
||||
|
||||
public function __construct(AddressRepository $addressRepository)
|
||||
{
|
||||
$this->addressRepository = $addressRepository;
|
||||
}
|
||||
|
||||
public function reverseTransform($value)
|
||||
{
|
||||
if (NULL === $value || '' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$address = $this->addressRepository->find($value);
|
||||
|
||||
if (NULL === $address) {
|
||||
$failure = new TransformationFailedException(sprintf("Address with id %s does not exists", $value));
|
||||
$failure
|
||||
->setInvalidMessage("The given {{ value }} is not a valid address id", [ '{{ value }}' => $value]);
|
||||
|
||||
throw $failure;
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
public function transform($value)
|
||||
{
|
||||
if (NULL === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $value->getId();
|
||||
}
|
||||
}
|
79
src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php
Normal file
79
src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\MainBundle\Form\Type\DataTransformer\AddressToIdDataTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Form type for picking an address.
|
||||
*
|
||||
* In the UI, this resolve to a vuejs component which will insert the created address id into the
|
||||
* hidden's value. It will also allow to edit existing addresses without changing the id.
|
||||
*
|
||||
* In every page where this component is shown, you must include the required module:
|
||||
*
|
||||
* ```twig
|
||||
* {% block js %}
|
||||
* {{ encore_entry_script_tags('mod_input_address') }}
|
||||
* {% endblock %}
|
||||
*
|
||||
* {% block css %}
|
||||
* {{ encore_entry_link_tags('mod_input_address') }}
|
||||
* {% endblock %}
|
||||
* ```
|
||||
*/
|
||||
final class PickAddressType extends AbstractType
|
||||
{
|
||||
private AddressToIdDataTransformer $addressToIdDataTransformer;
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(
|
||||
AddressToIdDataTransformer $addressToIdDataTransformer,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->addressToIdDataTransformer = $addressToIdDataTransformer;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->addModelTransformer($this->addressToIdDataTransformer);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['uniqid'] = $view->vars['attr']['data-input-address'] =\uniqid('input_address_');
|
||||
$view->vars['attr']['data-use-valid-from'] = (int) $options['use_valid_from'];
|
||||
$view->vars['attr']['data-use-valid-to'] = (int) $options['use_valid_to'];
|
||||
$view->vars['attr']['data-button-text-create'] = $this->translator->trans($options['button_text_create']);
|
||||
$view->vars['attr']['data-button-text-update'] = $this->translator->trans($options['button_text_update']);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => Address::class,
|
||||
'use_valid_to' => false,
|
||||
'use_valid_from' => false,
|
||||
'button_text_create' => 'Create an address',
|
||||
'button_text_update' => 'Update address',
|
||||
|
||||
// reset default from hidden type
|
||||
'required' => true,
|
||||
'error_bubbling' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return HiddenType::class;
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@ export default {
|
||||
components: {
|
||||
AddAddress
|
||||
},
|
||||
props: ['addAddress'],
|
||||
props: ['addAddress', 'callback'],
|
||||
emits: ['addressEdited', 'addressCreated'],
|
||||
computed: {
|
||||
context() {
|
||||
return this.addAddress.context;
|
||||
@ -46,6 +47,7 @@ export default {
|
||||
|
||||
// address is already linked, just finish !
|
||||
this.$refs.addAddress.afterLastPaneAction({});
|
||||
this.$emit('addressEdited', payload);
|
||||
|
||||
// New created address
|
||||
} else {
|
||||
@ -57,6 +59,8 @@ export default {
|
||||
* Post new created address to targetEntity
|
||||
*/
|
||||
postAddressTo(payload) {
|
||||
this.$emit('addressCreated', payload);
|
||||
|
||||
console.log('postAddress', payload.addressId, 'To', payload.target, payload.targetId);
|
||||
switch (payload.target) {
|
||||
case 'household':
|
||||
|
@ -267,6 +267,9 @@ export default {
|
||||
title: { create: 'add_an_address_title', edit: 'edit_address' },
|
||||
openPanesInModal: true,
|
||||
stickyActions: false,
|
||||
// show a message when no address.
|
||||
// if set to undefined, the value will be equivalent to false if stickyActions is false, true otherwise.
|
||||
showMessageWhenNoAddress: undefined,
|
||||
useDate: {
|
||||
validFrom: false,
|
||||
validTo: false
|
||||
|
@ -15,7 +15,7 @@
|
||||
<span v-if="forceRedirect">{{ $t('wait_redirection') }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="noAddressWithStickyActions" class="mt-5">
|
||||
<div v-if="showMessageWhenNoAddress" class="mt-5">
|
||||
<p class="chill-no-data-statement">
|
||||
{{ $t('not_yet_address') }}
|
||||
</p>
|
||||
@ -50,8 +50,8 @@ export default {
|
||||
},
|
||||
props: [
|
||||
'context',
|
||||
'options',
|
||||
'defaultz',
|
||||
'options',
|
||||
'flag',
|
||||
'entity',
|
||||
'errorMsg',
|
||||
@ -91,7 +91,11 @@ export default {
|
||||
forceRedirect() {
|
||||
return (!(this.context.backUrl === null || typeof this.context.backUrl === 'undefined'));
|
||||
},
|
||||
noAddressWithStickyActions() {
|
||||
showMessageWhenNoAddress() {
|
||||
let showMessageWhenNoAddress = this.options.showMessageWhenNoAddress === undefined ? this.defaultz.showMessageWhenNoAddress : this.options.showMessageWhenNoAddress;
|
||||
if (showMessageWhenNoAddress === true || showMessageWhenNoAddress === false) {
|
||||
return !this.context.edit && !this.address.id && showMessageWhenNoAddress;
|
||||
}
|
||||
return !this.context.edit && !this.address.id && this.options.stickyActions;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
import {createApp} from 'vue';
|
||||
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n';
|
||||
import {addressMessages} from './i18n';
|
||||
import App from './App.vue';
|
||||
|
||||
const i18n = _createI18n(addressMessages);
|
||||
|
||||
let inputs = document.querySelectorAll('input[type="hidden"][data-input-address]');
|
||||
|
||||
const isNumeric = function(v) { return !isNaN(v); };
|
||||
|
||||
inputs.forEach(el => {
|
||||
let
|
||||
addressId = el.value,
|
||||
uniqid = el.dataset.inputAddress,
|
||||
container = document.querySelector('div[data-input-address-container="' + uniqid + '"]'),
|
||||
isEdit = addressId !== '',
|
||||
addressIdInt = addressId !== '' ? parseInt(addressId) : null
|
||||
;
|
||||
|
||||
if (container === null) {
|
||||
throw Error("no container");
|
||||
}
|
||||
console.log('useValidFrom', el.dataset.useValidFrom === '1');
|
||||
|
||||
const app = createApp({
|
||||
template: `<app v-bind:addAddress="this.addAddress" @address-created="associateToInput"></app>`,
|
||||
data() {
|
||||
return {
|
||||
addAddress: {
|
||||
context: {
|
||||
// for legacy ? can be remove ?
|
||||
target: {
|
||||
name: 'input-address',
|
||||
id: addressIdInt,
|
||||
},
|
||||
edit: isEdit,
|
||||
addressId: addressIdInt,
|
||||
},
|
||||
options: {
|
||||
/// Options override default.
|
||||
/// null value take default component value defined in AddAddress data()
|
||||
button: {
|
||||
text: {
|
||||
create: el.dataset.buttonTextCreate || null,
|
||||
edit: el.dataset.buttonTextUpdate || null,
|
||||
},
|
||||
size: null,
|
||||
displayText: true
|
||||
},
|
||||
|
||||
/// Modal title text if create or edit address (trans chain, see i18n)
|
||||
title: {
|
||||
create: null,
|
||||
edit: null,
|
||||
},
|
||||
|
||||
/// Display panes in Modal for step123
|
||||
openPanesInModal: true,
|
||||
|
||||
/// Display actions buttons of panes in a sticky-form-button navbar
|
||||
stickyActions: false,
|
||||
showMessageWhenNoAddress: true,
|
||||
|
||||
/// Use Date fields
|
||||
useDate: {
|
||||
validFrom: el.dataset.useValidFrom === '1' || false, //boolean, default: false
|
||||
validTo: el.dataset.useValidTo === '1' || false, //boolean, default: false
|
||||
},
|
||||
|
||||
/// Don't display show renderbox Address: showPane display only a button
|
||||
onlyButton: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
associateToInput(payload) {
|
||||
el.value = payload.addressId;
|
||||
}
|
||||
}
|
||||
})
|
||||
.use(i18n)
|
||||
.component('app', App)
|
||||
.mount(container);
|
||||
});
|
@ -27,7 +27,7 @@ class SearchUserApiProvider implements SearchApiInterface
|
||||
->setSelectPertinence("GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical),
|
||||
SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))", [ $pattern, $pattern ])
|
||||
->setFromClause("users AS u")
|
||||
->setWhereClause("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15
|
||||
->setWhereClauses("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15
|
||||
OR
|
||||
SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15
|
||||
", [ $pattern, $pattern ]);
|
||||
|
@ -12,8 +12,8 @@ class SearchApiQuery
|
||||
private array $pertinenceParams = [];
|
||||
private ?string $fromClause = null;
|
||||
private array $fromClauseParams = [];
|
||||
private ?string $whereClause = null;
|
||||
private array $whereClauseParams = [];
|
||||
private array $whereClauses = [];
|
||||
private array $whereClausesParams = [];
|
||||
|
||||
public function setSelectKey(string $selectKey, array $params = []): self
|
||||
{
|
||||
@ -47,16 +47,39 @@ class SearchApiQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setWhereClause(string $whereClause, array $params = []): self
|
||||
/**
|
||||
* Set the where clause and replace all existing ones.
|
||||
*
|
||||
*/
|
||||
public function setWhereClauses(string $whereClause, array $params = []): self
|
||||
{
|
||||
$this->whereClause = $whereClause;
|
||||
$this->whereClauseParams = $params;
|
||||
$this->whereClauses = [$whereClause];
|
||||
$this->whereClausesParams = [$params];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where clause.
|
||||
*
|
||||
* This will add to previous where clauses with and `AND` join
|
||||
*
|
||||
* @param string $whereClause
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function andWhereClause(string $whereClause, array $params = []): self
|
||||
{
|
||||
$this->whereClauses[] = $whereClause;
|
||||
$this->whereClausesParams[] = $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function buildQuery(): string
|
||||
{
|
||||
$where = \implode(' AND ', $this->whereClauses);
|
||||
|
||||
return \strtr("SELECT
|
||||
'{key}' AS key,
|
||||
{metadata} AS metadata,
|
||||
@ -68,7 +91,7 @@ class SearchApiQuery
|
||||
'{metadata}' => $this->jsonbMetadata,
|
||||
'{pertinence}' => $this->pertinence,
|
||||
'{from}' => $this->fromClause,
|
||||
'{where}' => $this->whereClause,
|
||||
'{where}' => $where,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -79,7 +102,7 @@ class SearchApiQuery
|
||||
$this->jsonbMetadataParams,
|
||||
$this->pertinenceParams,
|
||||
$this->fromClauseParams,
|
||||
$this->whereClauseParams,
|
||||
\array_merge([], ...$this->whereClausesParams),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Search;
|
||||
|
||||
use Chill\MainBundle\Search\SearchApiQuery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SearchApiQueryTest extends TestCase
|
||||
{
|
||||
public function testMultipleWhereClauses()
|
||||
{
|
||||
$q = new SearchApiQuery();
|
||||
$q->setSelectJsonbMetadata('boum')
|
||||
->setSelectKey('bim')
|
||||
->setSelectPertinence('1')
|
||||
->setFromClause('badaboum')
|
||||
->andWhereClause('foo', [ 'alpha' ])
|
||||
->andWhereClause('bar', [ 'beta' ])
|
||||
;
|
||||
|
||||
$query = $q->buildQuery();
|
||||
|
||||
$this->assertStringContainsString('foo AND bar', $query);
|
||||
$this->assertEquals(['alpha', 'beta'], $q->buildParameters());
|
||||
}
|
||||
|
||||
public function testWithoutWhereClause()
|
||||
{
|
||||
$q = new SearchApiQuery();
|
||||
$q->setSelectJsonbMetadata('boum')
|
||||
->setSelectKey('bim')
|
||||
->setSelectPertinence('1')
|
||||
->setFromClause('badaboum')
|
||||
;
|
||||
|
||||
$this->assertTrue(\is_string($q->buildQuery()));
|
||||
$this->assertEquals([], $q->buildParameters());
|
||||
}
|
||||
|
||||
}
|
@ -60,6 +60,7 @@ module.exports = function(encore, entries)
|
||||
encore.addEntry('mod_ckeditor5', __dirname + '/Resources/public/module/ckeditor5/index.js');
|
||||
encore.addEntry('mod_disablebuttons', __dirname + '/Resources/public/module/disable-buttons/index.js');
|
||||
|
||||
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
||||
// Vue entrypoints
|
||||
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
|
||||
encore.addEntry('vue_onthefly', __dirname + '/Resources/public/vuejs/OnTheFly/index.js');
|
||||
|
@ -1,4 +1,5 @@
|
||||
services:
|
||||
|
||||
chill.main.form.type.translatable.string:
|
||||
class: Chill\MainBundle\Form\Type\TranslatableStringFormType
|
||||
arguments:
|
||||
@ -128,3 +129,11 @@ services:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
|
||||
Chill\MainBundle\Form\Type\PickAddressType:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
||||
Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
@ -84,6 +84,8 @@ address more:
|
||||
extra: ""
|
||||
distribution: cedex
|
||||
Create a new address: Créer une nouvelle adresse
|
||||
Create an address: Créer une adresse
|
||||
Update address: Modifier l'adresse
|
||||
|
||||
#serach
|
||||
Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche.
|
||||
|
@ -13,19 +13,19 @@
|
||||
</div>
|
||||
<div class="tpartyparent" v-if="hasParent">
|
||||
<span class="name">
|
||||
{{ item.result.parent.text }}
|
||||
> {{ item.result.parent.text }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right_actions">
|
||||
|
||||
<span class="badge bg-chill-red" v-if="item.result.kind == 'child'">
|
||||
{{ $t('thirdparty.contact')}}
|
||||
<span class="badge bg-thirdparty-child" v-if="item.result.kind == 'child'">
|
||||
{{ $t('thirdparty.child')}}
|
||||
</span>
|
||||
<span class="badge bg-info" v-else-if="item.result.kind == 'company'">
|
||||
<span class="badge bg-thirdparty-company" v-else-if="item.result.kind == 'company'">
|
||||
{{ $t('thirdparty.company')}}
|
||||
</span>
|
||||
<span class="badge bg-secondary" v-else="item.result.kind == 'contact'">
|
||||
<span class="badge bg-thirdparty-contact" v-else="item.result.kind == 'contact'">
|
||||
{{ $t('thirdparty.contact')}}
|
||||
</span>
|
||||
|
||||
@ -49,8 +49,8 @@ const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
thirdparty: {
|
||||
contact: "Contact",
|
||||
company: "Institution",
|
||||
contact: "Personne physique",
|
||||
company: "Personne morale",
|
||||
child: "Personne de contact"
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class SearchPersonApiProvider implements SearchApiInterface
|
||||
"(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int".
|
||||
")", [ $pattern, $pattern ])
|
||||
->setFromClause("chill_person_person AS person")
|
||||
->setWhereClause("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ".
|
||||
->setWhereClauses("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ".
|
||||
"person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' ", [ $pattern, $pattern ])
|
||||
;
|
||||
|
||||
|
@ -3,17 +3,17 @@
|
||||
/*
|
||||
* Chill is a suite of a modules, Chill is a software for social workers
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@ -38,43 +38,43 @@ use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
|
||||
class LoadReports extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
|
||||
{
|
||||
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Faker\Generator
|
||||
* @var \Faker\Generator
|
||||
*/
|
||||
private $faker;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->faker = FakerFactory::create('fr_FR');
|
||||
}
|
||||
|
||||
|
||||
public function getOrder()
|
||||
{
|
||||
return 15002;
|
||||
}
|
||||
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$this->createExpected($manager);
|
||||
|
||||
|
||||
//create random 2 times, to allow multiple report on some people
|
||||
$this->createRandom($manager, 90);
|
||||
$this->createRandom($manager, 30);
|
||||
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
|
||||
private function createRandom(ObjectManager $manager, $percentage)
|
||||
{
|
||||
$people = $this->getPeopleRandom($percentage);
|
||||
|
||||
|
||||
foreach ($people as $person) {
|
||||
//create a report, set logement or education report
|
||||
$report = (new Report())
|
||||
->setPerson($person)
|
||||
->setCFGroup(rand(0,10) > 5 ?
|
||||
->setCFGroup(rand(0,10) > 5 ?
|
||||
$this->getReference('cf_group_report_logement') :
|
||||
$this->getReference('cf_group_report_education')
|
||||
)
|
||||
@ -84,14 +84,14 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
$manager->persist($report);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function createExpected(ObjectManager $manager)
|
||||
{
|
||||
$charline = $this->container->get('doctrine.orm.entity_manager')
|
||||
->getRepository('ChillPersonBundle:Person')
|
||||
->findOneBy(array('firstName' => 'Charline', 'lastName' => 'Depardieu'))
|
||||
->findOneBy(array('firstName' => 'Charline', 'lastName' => 'DEPARDIEU'))
|
||||
;
|
||||
|
||||
|
||||
$report = (new Report())
|
||||
->setPerson($charline)
|
||||
->setCFGroup($this->getReference('cf_group_report_logement'))
|
||||
@ -99,12 +99,12 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
->setScope($this->getReference('scope_social'))
|
||||
;
|
||||
$this->fillReport($report);
|
||||
|
||||
|
||||
$manager->persist($report);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return \Chill\MainBundle\Entity\Scope
|
||||
*/
|
||||
private function getScopeRandom()
|
||||
@ -112,14 +112,14 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
$ref = LoadScopes::$references[array_rand(LoadScopes::$references)];
|
||||
return $this->getReference($ref);
|
||||
}
|
||||
|
||||
|
||||
private function getPeopleRandom($percentage)
|
||||
{
|
||||
$people = $this->container->get('doctrine.orm.entity_manager')
|
||||
->getRepository('ChillPersonBundle:Person')
|
||||
->findAll()
|
||||
;
|
||||
|
||||
|
||||
//keep only a part ($percentage) of the people
|
||||
$selectedPeople = array();
|
||||
foreach($people as $person) {
|
||||
@ -127,10 +127,10 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
$selectedPeople[] = $person;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $selectedPeople;
|
||||
}
|
||||
|
||||
|
||||
private function fillReport(Report $report)
|
||||
{
|
||||
//setUser
|
||||
@ -138,7 +138,7 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
$report->setUser(
|
||||
$this->getReference($usernameRef)
|
||||
);
|
||||
|
||||
|
||||
//set date if null
|
||||
if ($report->getDate() === NULL) {
|
||||
//set date. 30% of the dates are 2015-05-01
|
||||
@ -148,9 +148,9 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
} else {
|
||||
$report->setDate($this->faker->dateTimeBetween('-1 year', 'now')
|
||||
->setTime(0, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//fill data
|
||||
$datas = array();
|
||||
foreach ($report->getCFGroup()->getCustomFields() as $field) {
|
||||
@ -167,66 +167,66 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
}
|
||||
}
|
||||
$report->setCFData($datas);
|
||||
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* pick a random choice
|
||||
*
|
||||
*
|
||||
* @param CustomField $field
|
||||
* @return string[]|string the array of slug if multiple, a single slug otherwise
|
||||
*/
|
||||
private function getRandomChoice(CustomField $field)
|
||||
private function getRandomChoice(CustomField $field)
|
||||
{
|
||||
$choices = $field->getOptions()['choices'];
|
||||
$multiple = $field->getOptions()['multiple'];
|
||||
$other = $field->getOptions()['other'];
|
||||
|
||||
|
||||
//add other if allowed
|
||||
if($other) {
|
||||
$choices[] = array('slug' => '_other');
|
||||
}
|
||||
|
||||
|
||||
//initialize results
|
||||
$picked = array();
|
||||
|
||||
|
||||
if ($multiple) {
|
||||
$numberSelected = rand(1, count($choices) -1);
|
||||
for ($i = 0; $i < $numberSelected; $i++) {
|
||||
$picked[] = $this->pickChoice($choices);
|
||||
}
|
||||
|
||||
|
||||
if ($other) {
|
||||
$result = array("_other" => NULL, "_choices" => $picked);
|
||||
|
||||
|
||||
if (in_array('_other', $picked)) {
|
||||
$result['_other'] = $this->faker->realText(70);
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
} else {
|
||||
$picked = $this->pickChoice($choices);
|
||||
|
||||
|
||||
if ($other) {
|
||||
$result = array('_other' => NULL, '_choices' => $picked);
|
||||
|
||||
|
||||
if ($picked === '_other') {
|
||||
$result['_other'] = $this->faker->realText(70);
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* pick a choice within a 'choices' options (for choice type)
|
||||
*
|
||||
*
|
||||
* @param array $choices
|
||||
* @return the slug of the selected choice
|
||||
*/
|
||||
@ -234,7 +234,7 @@ class LoadReports extends AbstractFixture implements OrderedFixtureInterface, Co
|
||||
{
|
||||
return $choices[array_rand($choices)]['slug'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\ThirdPartyBundle;
|
||||
|
||||
use Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeProviderInterface;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Chill\ThirdPartyBundle\DependencyInjection\CompilerPass\ThirdPartyTypeCompilerPass;
|
||||
|
||||
@ -10,6 +11,8 @@ class ChillThirdPartyBundle extends Bundle
|
||||
public function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
$container->registerForAutoconfiguration(ThirdPartyTypeProviderInterface::class)
|
||||
->addTag('chill_3party.provider');
|
||||
$container->addCompilerPass(new ThirdPartyTypeCompilerPass());
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,8 @@ final class ThirdPartyController extends CRUDController
|
||||
return $this->getFilterOrderHelperFactory()
|
||||
->create(self::class)
|
||||
->addSearchBox(['name', 'company_name', 'acronym'])
|
||||
//->addToggle('only-active', [])
|
||||
// ->addOrderBy()
|
||||
->build();
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
public function setTypes(array $type = null)
|
||||
{
|
||||
// remove all keys from the input data
|
||||
$this->type = \array_values($type);
|
||||
$this->types = \array_values($type);
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->setTypes($type);
|
||||
@ -387,6 +387,40 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
public function addType(?string $type): self
|
||||
{
|
||||
if (NULL === $type) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!\in_array($type, $this->types ?? [])) {
|
||||
$this->types[] = $type;
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->addType($type);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeType(?string $type): self
|
||||
{
|
||||
if (NULL === $type) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (\in_array($type, $this->types ?? [])) {
|
||||
$this->types = \array_filter($this->types, fn($e) => !\in_array($e, $this->types));
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->removeType($type);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@ -460,6 +494,10 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
*/
|
||||
public function getAddress(): ?Address
|
||||
{
|
||||
if ($this->isChild()) {
|
||||
return $this->getParent()->getAddress();
|
||||
}
|
||||
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
@ -512,9 +550,9 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
* @param string $acronym
|
||||
* @return $this
|
||||
*/
|
||||
public function setAcronym(string $acronym): ThirdParty
|
||||
public function setAcronym(?string $acronym = null): ThirdParty
|
||||
{
|
||||
$this->acronym = $acronym;
|
||||
$this->acronym = (string) $acronym;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -537,7 +575,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->addCategory($child);
|
||||
$child->addCategory($category);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -552,7 +590,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
$this->categories->removeElement($category);
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->removeCategory($child);
|
||||
$child->removeCategory($category);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -627,6 +665,72 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addTypesAndCategories($typeAndCategory): self
|
||||
{
|
||||
if ($typeAndCategory instanceof ThirdPartyCategory) {
|
||||
$this->addCategory($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($typeAndCategory)) {
|
||||
$this->addType($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
"typeAndCategory should be a string or a %s", ThirdPartyCategory::class));
|
||||
}
|
||||
|
||||
public function removeTypesAndCategories($typeAndCategory): self
|
||||
{
|
||||
if ($typeAndCategory instanceof ThirdPartyCategory) {
|
||||
$this->removeCategory($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($typeAndCategory)) {
|
||||
$this->removeType($typeAndCategory);
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
"typeAndCategory should be a string or a %s", ThirdPartyCategory::class));
|
||||
}
|
||||
|
||||
public function getTypesAndCategories(): array
|
||||
{
|
||||
return \array_merge(
|
||||
$this->getCategories()->toArray(),
|
||||
$this->getTypes() ?? []
|
||||
);
|
||||
}
|
||||
|
||||
public function setTypesAndCategories(array $typesAndCategories): self
|
||||
{
|
||||
$types = \array_filter($typesAndCategories, fn($item) => !$item instanceof ThirdPartyCategory);
|
||||
$this->setTypes($types);
|
||||
|
||||
// handle categories
|
||||
foreach ($typesAndCategories as $t) {
|
||||
$this->addTypesAndCategories($t);
|
||||
}
|
||||
|
||||
$categories = \array_filter($typesAndCategories, fn($item) => $item instanceof ThirdPartyCategory);
|
||||
$categoriesHashes = \array_map(fn(ThirdPartyCategory $c) => \spl_object_hash($c), $categories);
|
||||
|
||||
foreach ($categories as $c) {
|
||||
$this->addCategory($c);
|
||||
}
|
||||
|
||||
foreach ($this->getCategories() as $t) {
|
||||
if (!\in_array(\spl_object_hash($t), $categoriesHashes)) {
|
||||
$this->removeCategory($t);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ThirdParty $child
|
||||
|
@ -5,15 +5,19 @@ namespace Chill\ThirdPartyBundle\Form;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\PickAddressType;
|
||||
use Chill\MainBundle\Form\Type\PickCenterType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyProfession;
|
||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType;
|
||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdPartyTypeCategoryType;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
@ -40,14 +44,14 @@ class ThirdPartyType extends AbstractType
|
||||
|
||||
protected TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
protected ObjectManager $om;
|
||||
protected EntityManagerInterface $om;
|
||||
|
||||
public function __construct(
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
ThirdPartyTypeManager $typesManager,
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
ObjectManager $om
|
||||
EntityManagerInterface $om
|
||||
) {
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
@ -85,27 +89,6 @@ class ThirdPartyType extends AbstractType
|
||||
])
|
||||
;
|
||||
|
||||
$builder
|
||||
->add('address', HiddenType::class)
|
||||
->get('address')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
function (?Address $address): string {
|
||||
if (null === $address) {
|
||||
return '';
|
||||
}
|
||||
return $address->getId();
|
||||
},
|
||||
function (?string $addressId): ?Address {
|
||||
if (null === $addressId) {
|
||||
return null;
|
||||
}
|
||||
return $this->om
|
||||
->getRepository(Address::class)
|
||||
->findOneBy(['id' => (int) $addressId]);
|
||||
}
|
||||
))
|
||||
;
|
||||
|
||||
// Contact Person ThirdParty (child)
|
||||
if (ThirdParty::KIND_CONTACT === $options['kind'] || ThirdParty::KIND_CHILD === $options['kind']) {
|
||||
$builder
|
||||
@ -144,6 +127,15 @@ class ThirdPartyType extends AbstractType
|
||||
// Institutional ThirdParty (parent)
|
||||
} else {
|
||||
$builder
|
||||
->add('address', PickAddressType::class, [
|
||||
'label' => 'Address'
|
||||
])
|
||||
->add('address2', PickAddressType::class, [
|
||||
'label' => 'Address',
|
||||
'use_valid_from' => true,
|
||||
'use_valid_to' => true,
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('nameCompany', TextType::class, [
|
||||
'label' => 'thirdparty.NameCompany',
|
||||
'required' => false
|
||||
@ -171,21 +163,9 @@ class ThirdPartyType extends AbstractType
|
||||
}
|
||||
|
||||
if (ThirdParty::KIND_CHILD !== $options['kind']) {
|
||||
|
||||
$builder
|
||||
->add('categories', EntityType::class, [
|
||||
'label' => 'thirdparty.Categories',
|
||||
'class' => ThirdPartyCategory::class,
|
||||
'choice_label' => function (ThirdPartyCategory $category): string {
|
||||
return $this->translatableStringHelper->localize($category->getName());
|
||||
},
|
||||
'query_builder' => function (EntityRepository $er): QueryBuilder {
|
||||
return $er->createQueryBuilder('c')
|
||||
->where('c.active = true');
|
||||
},
|
||||
'required' => true,
|
||||
'multiple' => true,
|
||||
'attr' => ['class' => 'select2']
|
||||
->add('typesAndCategories', PickThirdPartyTypeCategoryType::class, [
|
||||
'label' => 'thirdparty.Categories'
|
||||
])
|
||||
->add('active', ChoiceType::class, [
|
||||
'label' => 'thirdparty.Status',
|
||||
@ -196,42 +176,6 @@ class ThirdPartyType extends AbstractType
|
||||
'expanded' => true,
|
||||
'multiple' => false
|
||||
]);
|
||||
|
||||
// add the types
|
||||
$types = [];
|
||||
foreach ($this->typesManager->getProviders() as $key => $provider) {
|
||||
$types['chill_3party.key_label.'.$key] = $key;
|
||||
}
|
||||
if (count($types) === 1) {
|
||||
$builder
|
||||
->add('types', HiddenType::class, [
|
||||
'data' => array_values($types)
|
||||
])
|
||||
->get('types')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
function (?array $typeArray): ?string {
|
||||
if (null === $typeArray) {
|
||||
return null;
|
||||
}
|
||||
return implode(',', $typeArray);
|
||||
},
|
||||
function (?string $typeStr): ?array {
|
||||
if (null === $typeStr) {
|
||||
return null;
|
||||
}
|
||||
return explode(',', $typeStr);
|
||||
}
|
||||
))
|
||||
;
|
||||
} else {
|
||||
$builder
|
||||
->add('types', ChoiceType::class, [
|
||||
'choices' => $types,
|
||||
'expanded' => true,
|
||||
'multiple' => true,
|
||||
'label' => 'thirdparty.Type'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdPartyBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||
use Chill\ThirdPartyBundle\Repository\ThirdPartyCategoryRepository;
|
||||
use Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class PickThirdPartyTypeCategoryType extends \Symfony\Component\Form\AbstractType
|
||||
{
|
||||
private ThirdPartyCategoryRepository $thirdPartyCategoryRepository;
|
||||
private ThirdPartyTypeManager $thirdPartyTypeManager;
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private const PREFIX_TYPE = 'chill_3party.key_label.';
|
||||
|
||||
public function __construct(
|
||||
ThirdPartyCategoryRepository $thirdPartyCategoryRepository,
|
||||
ThirdPartyTypeManager $thirdPartyTypeManager,
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->thirdPartyCategoryRepository = $thirdPartyCategoryRepository;
|
||||
$this->thirdPartyTypeManager = $thirdPartyTypeManager;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$choices = \array_merge(
|
||||
$this->thirdPartyCategoryRepository->findBy(['active' => true]),
|
||||
$this->thirdPartyTypeManager->getTypes()
|
||||
);
|
||||
|
||||
\uasort($choices, function ($itemA, $itemB) {
|
||||
$strA = $itemA instanceof ThirdPartyCategory ? $this->translatableStringHelper
|
||||
->localize($itemA->getName()) : $this->translator->trans(self::PREFIX_TYPE.$itemA);
|
||||
$strB = $itemB instanceof ThirdPartyCategory ? $this->translatableStringHelper
|
||||
->localize($itemB->getName()) : $this->translator->trans(self::PREFIX_TYPE.$itemB);
|
||||
|
||||
return $strA <=> $strB;
|
||||
});
|
||||
|
||||
|
||||
$resolver->setDefaults([
|
||||
'choices' => $choices,
|
||||
'attr' => [ 'class' => 'select2' ],
|
||||
'multiple' => true,
|
||||
'choice_label' => function($item) {
|
||||
if ($item instanceof ThirdPartyCategory) {
|
||||
return $this->translatableStringHelper->localize($item->getName());
|
||||
}
|
||||
return self::PREFIX_TYPE.$item;
|
||||
},
|
||||
'choice_value' => function($item) {
|
||||
return $this->reverseTransform($item);
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
public function reverseTransform($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
if (is_array($value)){
|
||||
$r = [];
|
||||
|
||||
foreach ($value as $v) {
|
||||
$r[] = $this->transform($v);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
if ($value instanceof ThirdPartyCategory) {
|
||||
return 'category:'.$value->getId();
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return 'type:'.$value;
|
||||
}
|
||||
|
||||
throw new UnexpectedTypeException($value, \implode(' or ', ['array', 'string', ThirdPartyCategory::class]));
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
.badge {
|
||||
&.bg-thirdparty-company {
|
||||
//@extend .bg-info;
|
||||
background-color: $yellow;
|
||||
}
|
||||
&.bg-thirdparty-child {
|
||||
//@extend .bg-chill-blue;
|
||||
background-color: $chill-blue;
|
||||
}
|
||||
&.bg-thirdparty-contact {
|
||||
//@extedn .bg-secondary;
|
||||
background-color: $secondary;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
require('./chillthirdparty.scss');
|
@ -1,2 +1,2 @@
|
||||
require('./chillthirdparty.scss');
|
||||
require('./index_3party.scss');
|
||||
|
||||
|
@ -13,16 +13,33 @@
|
||||
</a>
|
||||
<span class="name">{{ thirdparty.text }}</span>
|
||||
|
||||
<span class="badge bg-thirdparty-child" v-if="thirdparty.kind == 'child'">
|
||||
{{ $t('thirdparty.child')}}
|
||||
</span>
|
||||
<span class="badge bg-thirdparty-company" v-else-if="thirdparty.kind == 'company'">
|
||||
{{ $t('thirdparty.company')}}
|
||||
</span>
|
||||
<span class="badge bg-thirdparty-contact" v-else="thirdparty.kind == 'contact'">
|
||||
{{ $t('thirdparty.contact')}}
|
||||
</span>
|
||||
|
||||
<span v-if="options.addId == true" class="id-number" :title="'n° ' + thirdparty.id">{{ thirdparty.id }}</span>
|
||||
<span v-if="options.addEntity == true && thirdparty.type === 'thirdparty'" class="badge rounded-pill bg-secondary">{{ $t('renderbox.type.thirdparty') }}</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="hasParent">
|
||||
<span class="name tparty-parent">
|
||||
> {{ thirdparty.parent.text }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="this.options.addInfo === true" class="moreinfo">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="item-col">
|
||||
<div class="float-button bottom">
|
||||
<div class="box">
|
||||
@ -57,19 +74,34 @@
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
import {dateToISO} from 'ChillMainAssets/chill/js/date.js';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
tparty: {
|
||||
contact: "Personne physique",
|
||||
company: "Personne morale"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "ThirdPartyRenderBox",
|
||||
components: {
|
||||
AddressRenderBox
|
||||
},
|
||||
i18n,
|
||||
props: ['thirdparty', 'options'],
|
||||
computed: {
|
||||
isMultiline: function() {
|
||||
if(this.options.isMultiline){
|
||||
if (this.options.isMultiline){
|
||||
return this.options.isMultiline
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
hasParent() {
|
||||
return !(this.$props.thirdparty.parent === null || this.$props.thirdparty.parent === undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,6 +112,10 @@ export default {
|
||||
&:before{
|
||||
content: " "
|
||||
}
|
||||
&.tparty-parent {
|
||||
font-weight: bold;
|
||||
font-variant: all-small-caps;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -20,32 +20,55 @@
|
||||
</div>
|
||||
<div v-else-if="action === 'edit' || action === 'create'">
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<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">
|
||||
{{ $t('tparty.company')}}
|
||||
<span class="badge bg-thirdparty-company" style="padding-top: 0;">
|
||||
{{ $t('tparty.company')}}
|
||||
</span>
|
||||
</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">
|
||||
{{ $t('tparty.contact')}}
|
||||
<span class="badge bg-thirdparty-contact" style="padding-top: 0;">
|
||||
{{ $t('tparty.contact')}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>Contact de :</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>
|
||||
|
||||
<add-address
|
||||
key="thirdparty"
|
||||
:context="context"
|
||||
:options="addAddress.options"
|
||||
:address-changed-callback="submitAddress"
|
||||
ref="addAddress">
|
||||
</add-address>
|
||||
<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>
|
||||
@ -77,8 +100,8 @@ const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
tparty: {
|
||||
contact: "Contact",
|
||||
company: "Institution"
|
||||
contact: "Personne physique",
|
||||
company: "Personne morale"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +159,7 @@ export default {
|
||||
edit: false,
|
||||
addressId: null
|
||||
};
|
||||
if ( typeof this.thirdparty.address !== 'undefined'
|
||||
if ( !(this.thirdparty.address === undefined || this.thirdparty.address === null)
|
||||
&& this.thirdparty.address.address_id !== null
|
||||
) { // to complete
|
||||
context.addressId = this.thirdparty.address.address_id;
|
||||
@ -151,10 +174,13 @@ export default {
|
||||
loadData(){
|
||||
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') {
|
||||
// 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();
|
||||
}));
|
||||
@ -170,6 +196,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('mounted', this.action);
|
||||
if (this.action !== 'create') {
|
||||
this.loadData();
|
||||
} else {
|
||||
|
@ -81,22 +81,18 @@
|
||||
<div class="item-row entity-bloc">
|
||||
<div class="item-col">
|
||||
{{ _self.label(thirdparty, options) }}
|
||||
{% if thirdparty.kind == 'company' %}
|
||||
<span class="badge bg-info">{{ 'thirdparty.company'|trans }}</span>
|
||||
{% elseif thirdparty.kind == 'child' %}
|
||||
<span class="badge bg-chill-red">{{ 'thirdparty.Child'|trans }}</span>
|
||||
{% elseif thirdparty.kind == 'contact' %}
|
||||
<span class="badge bg-secondary">{{ 'thirdparty.contact'|trans }}</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-thirdparty-{{ thirdparty.kind }}">{{ ('thirdparty.' ~ thirdparty.kind)|trans }}</span>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content fa-ul">
|
||||
{{ thirdparty.getAddress|chill_entity_render_box({
|
||||
'render': 'list',
|
||||
'with_picto': true,
|
||||
'multiline': false,
|
||||
'with_valid_from': false
|
||||
}) }}
|
||||
<li>
|
||||
{{ thirdparty.getAddress|chill_entity_render_box({
|
||||
'render': 'list',
|
||||
'with_picto': true,
|
||||
'multiline': false,
|
||||
'with_valid_from': false
|
||||
}) }}
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-phone"></i>
|
||||
{% if thirdparty.telephone %}
|
||||
<a href="{{ 'tel:' ~ thirdparty.telephone }}">{{ thirdparty.telephone|chill_format_phonenumber }}</a>
|
||||
@ -144,4 +140,16 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if options['showParent'] and thirdparty.isChild %}
|
||||
<div class="item-row">
|
||||
{{ 'thirdparty.Contact of'|trans }} :
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: thirdparty.parent.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: thirdparty.parent|chill_entity_render_string
|
||||
} %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- endif -%}
|
||||
|
@ -14,8 +14,7 @@
|
||||
{{ form_row(form.profession) }}
|
||||
{% endif %}
|
||||
|
||||
{{ form_row(form.types) }}
|
||||
{{ form_row(form.categories) }}
|
||||
{{ form_row(form.typesAndCategories) }}
|
||||
|
||||
{{ form_row(form.telephone) }}
|
||||
{{ form_row(form.email) }}
|
||||
@ -29,12 +28,16 @@
|
||||
{{ form_widget(form.activeChildren) }}
|
||||
{% endif %}
|
||||
|
||||
{{ form_row(form.address) }}
|
||||
|
||||
{#
|
||||
<div class="mb-3 row">
|
||||
{{ form_label(form.address) }}
|
||||
{{ form_widget(form.address) }}
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% if thirdParty.address %}
|
||||
{# include vue_address component #}
|
||||
{# include vue_address component #
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: thirdParty.id },
|
||||
mode: 'edit',
|
||||
@ -43,9 +46,9 @@
|
||||
} %}
|
||||
{#
|
||||
backUrl: path('chill_3party_3party_new'),
|
||||
#}
|
||||
#
|
||||
{% else %}
|
||||
{# include vue_address component #}
|
||||
{# include vue_address component #
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: thirdParty.id },
|
||||
mode: 'new',
|
||||
@ -56,6 +59,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
#}
|
||||
|
||||
{{ form_row(form.comment) }}
|
||||
{{ form_row(form.centers) }}
|
||||
|
@ -19,3 +19,11 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('mod_input_address') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_input_address') }}
|
||||
{% endblock %}
|
||||
|
@ -36,87 +36,10 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content_not %}
|
||||
<div class="thirdparty-edit my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10">
|
||||
|
||||
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
{% if form.civility is defined %}
|
||||
{{ form_row(form.civility) }}
|
||||
{% endif %}
|
||||
|
||||
{{ form_row(form.name) }}
|
||||
|
||||
{% if form.nameCompany is defined %}
|
||||
{{ form_row(form.nameCompany) }}
|
||||
{{ form_row(form.acronym) }}
|
||||
{% endif %}
|
||||
|
||||
{% if form.profession is defined %}
|
||||
{{ form_row(form.profession) }}
|
||||
{% endif %}
|
||||
|
||||
{{ form_row(form.types) }}
|
||||
{{ form_row(form.categories) }}
|
||||
|
||||
{{ form_row(form.telephone) }}
|
||||
{{ form_row(form.email) }}
|
||||
|
||||
<div class="mb-3 row">
|
||||
{{ form_label(form.address) }}
|
||||
{{ form_widget(form.address) }}
|
||||
<div class="col-sm-8">
|
||||
{% if thirdParty.address %}
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: thirdParty.id },
|
||||
mode: 'edit',
|
||||
addressId: thirdParty.address.id,
|
||||
buttonSize: 'btn-sm',
|
||||
} %}
|
||||
{#
|
||||
backUrl: path('chill_3party_3party_update', { thirdparty_id: thirdParty.id }),
|
||||
#}
|
||||
{% else %}
|
||||
{# include vue_address component #}
|
||||
{% include '@ChillMain/Address/_insert_vue_address.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: thirdParty.id },
|
||||
mode: 'new',
|
||||
buttonSize: 'btn-sm',
|
||||
buttonText: 'Create a new address',
|
||||
modalTitle: 'Create a new address',
|
||||
} %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(form.comment) }}
|
||||
{{ form_row(form.centers) }}
|
||||
|
||||
{{ form_row(form.active) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href="{{ chill_path_forward_return_path('chill_3party_3party_index') }}">
|
||||
{{ 'Back to the list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, {'label': 'Update', 'attr': {'class': 'btn btn-update' }}) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('mod_input_address') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_input_address') }}
|
||||
{% endblock %}
|
||||
|
@ -48,13 +48,20 @@
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{{ 'Type'|trans }}</dt>
|
||||
<dt>{{ 'thirdparty.Categories'|trans }}</dt>
|
||||
{% set types = [] %}
|
||||
{% for t in thirdParty.types %}
|
||||
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
|
||||
{% endfor %}
|
||||
{% for c in thirdParty.categories %}
|
||||
{% set types = types|merge([ c.name|localize_translatable_string ]) %}
|
||||
{% endfor %}
|
||||
<dd>
|
||||
{{ types|join(', ') }}
|
||||
{% if types|length > 0 %}
|
||||
{{ types|join(', ') }}
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'thirdParty.Any categories' }}</p>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{{ 'Phonenumber'|trans }}</dt>
|
||||
|
@ -5,7 +5,36 @@ namespace Chill\ThirdPartyBundle\Search;
|
||||
use Chill\MainBundle\Search\SearchApiInterface;
|
||||
use Chill\MainBundle\Search\SearchApiQuery;
|
||||
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||
use function explode;
|
||||
|
||||
|
||||
/*
|
||||
* Internal note: test query for parametrizing / testing:
|
||||
*
|
||||
WITH rows AS (
|
||||
SELECT 'aide a domicile en milieu rural admr' AS c, 'la roche sur yon' AS l
|
||||
UNION
|
||||
SELECT 'aide a domicile en milieu rural admr' AS c, 'fontenay-le-comte' AS l
|
||||
), searches AS (
|
||||
SELECT 'admr roche' AS s, 'admr' AS s1, 'roche' As s2
|
||||
UNION
|
||||
SELECT 'admr font' AS s, 'admr' AS s1, 'font' AS s2
|
||||
)
|
||||
SELECT
|
||||
c, l, s, s1, s2,
|
||||
strict_word_similarity(s, c)
|
||||
+ (c LIKE '%' || s1 || '%')::int
|
||||
+ (c LIKE '%' || s2 || '%')::int
|
||||
+ (l LIKE '%' || s1 || '%')::int
|
||||
+ (l LIKE '%' || s2 || '%')::int,
|
||||
l LIKE '%' || s1 || '%',
|
||||
l LIKE '%' || s2 || '%'
|
||||
FROM rows, searches
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate query for searching amongst third parties
|
||||
*/
|
||||
class ThirdPartyApiSearch implements SearchApiInterface
|
||||
{
|
||||
private ThirdPartyRepository $thirdPartyRepository;
|
||||
@ -17,18 +46,45 @@ class ThirdPartyApiSearch implements SearchApiInterface
|
||||
|
||||
public function provideQuery(string $pattern, array $parameters): SearchApiQuery
|
||||
{
|
||||
return (new SearchApiQuery)
|
||||
$query = (new SearchApiQuery)
|
||||
->setSelectKey('tparty')
|
||||
->setSelectJsonbMetadata("jsonb_build_object('id', tparty.id)")
|
||||
->setSelectPertinence("GREATEST(".
|
||||
"STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized),".
|
||||
"(tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')::int".
|
||||
")", [ $pattern, $pattern ])
|
||||
->setFromClause('chill_3party.third_party AS tparty')
|
||||
->setWhereClause("tparty.active IS TRUE ".
|
||||
"AND (LOWER(UNACCENT(?)) <<% tparty.canonicalized OR ".
|
||||
"tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')", [ $pattern, $pattern ])
|
||||
;
|
||||
->setFromClause('chill_3party.third_party AS tparty
|
||||
LEFT JOIN chill_main_address cma ON cma.id = tparty.address_id
|
||||
LEFT JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id
|
||||
LEFT JOIN chill_3party.third_party AS parent ON tparty.parent_id = parent.id
|
||||
LEFT JOIN chill_main_address cma_p ON parent.address_id = cma_p.id
|
||||
LEFT JOIN chill_main_postal_code cmpc_p ON cma_p.postcode_id = cmpc.id')
|
||||
->andWhereClause("tparty.active IS TRUE")
|
||||
;
|
||||
|
||||
$strs = explode(' ', $pattern);
|
||||
$wheres = [];
|
||||
$whereArgs = [];
|
||||
$pertinence = [];
|
||||
$pertinenceArgs = [];
|
||||
|
||||
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 + ".
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
$query
|
||||
->setSelectPertinence(\implode(' + ', $pertinence), \array_merge([],
|
||||
...$pertinenceArgs))
|
||||
->andWhereClause(\implode(' OR ', $wheres), \array_merge([],
|
||||
...$whereArgs));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function supportsTypes(string $pattern, array $types, array $parameters): bool
|
||||
|
@ -61,7 +61,8 @@ class ThirdPartyRender extends AbstractChillEntityRender
|
||||
'hLevel' => $options['hLevel'] ?? 3,
|
||||
'customButtons' => $options['customButtons'] ?? [],
|
||||
'customArea' => $options['customArea'] ?? [],
|
||||
'showContacts' => $options['showContacts'] ?? [],
|
||||
'showContacts' => $options['showContacts'] ?? false,
|
||||
'showParent' => $options['showParent'] ?? true,
|
||||
];
|
||||
|
||||
return
|
||||
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\ThirdParty\Tests\Entity;
|
||||
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ThirdPartyTest extends TestCase
|
||||
{
|
||||
public function testAddingRemovingActivityTypes()
|
||||
{
|
||||
$tp = new ThirdParty();
|
||||
$cat1 = new ThirdPartyCategory();
|
||||
$cat2 = new ThirdPartyCategory();
|
||||
|
||||
$tp->addTypesAndCategories('type');
|
||||
$tp->addTypesAndCategories($cat1);
|
||||
$tp->addTypesAndCategories($cat2);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1));
|
||||
$this->assertTrue($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(2, $tp->getCategories());
|
||||
|
||||
$this->assertCount(1, $tp->getTypes());
|
||||
$this->assertContains('type', $tp->getTypes());
|
||||
|
||||
$this->assertCount(3, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertContains('type', $tp->getTypesAndCategories());
|
||||
|
||||
// remove type
|
||||
$tp->removeTypesAndCategories('type');
|
||||
$tp->removeTypesAndCategories($cat2);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1),
|
||||
"test that cat1 is still present");
|
||||
$this->assertFalse($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(1, $tp->getCategories());
|
||||
|
||||
$this->assertCount(0, $tp->getTypes());
|
||||
$this->assertNotContains('type', $tp->getTypes());
|
||||
|
||||
$this->assertCount(1, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertNotContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertNotContains('type', $tp->getTypesAndCategories());
|
||||
}
|
||||
|
||||
public function testSyncingActivityTypes()
|
||||
{
|
||||
$tp = new ThirdParty();
|
||||
$tp->setTypesAndCategories([
|
||||
'type1',
|
||||
'type2',
|
||||
$cat1 = new ThirdPartyCategory(),
|
||||
$cat2 = new ThirdPartyCategory()
|
||||
]);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1));
|
||||
$this->assertTrue($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(2, $tp->getCategories());
|
||||
|
||||
$this->assertCount(2, $tp->getTypes());
|
||||
$this->assertContains('type1', $tp->getTypes());
|
||||
$this->assertContains('type2', $tp->getTypes());
|
||||
|
||||
$this->assertCount(4, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertContains('type1', $tp->getTypesAndCategories());
|
||||
$this->assertContains('type2', $tp->getTypesAndCategories());
|
||||
|
||||
$tp->setTypesAndCategories([$cat1, 'type1']);
|
||||
|
||||
$this->assertTrue($tp->getCategories()->contains($cat1));
|
||||
$this->assertFalse($tp->getCategories()->contains($cat2));
|
||||
$this->assertCount(1, $tp->getCategories());
|
||||
|
||||
$this->assertCount(1, $tp->getTypes());
|
||||
$this->assertContains('type1', $tp->getTypes());
|
||||
$this->assertNotContains('type2', $tp->getTypes());
|
||||
|
||||
$this->assertCount(2, $tp->getTypesAndCategories());
|
||||
$this->assertContains($cat1, $tp->getTypesAndCategories());
|
||||
$this->assertNotContains($cat2, $tp->getTypesAndCategories());
|
||||
$this->assertContains('type1', $tp->getTypesAndCategories());
|
||||
$this->assertNotContains('type2', $tp->getTypesAndCategories());
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,8 @@ module.exports = function(encore, entries)
|
||||
ChillThirdPartyAssets: __dirname + '/Resources/public'
|
||||
});
|
||||
|
||||
entries.push(__dirname + '/Resources/public/chill/index.js');
|
||||
|
||||
encore.addEntry(
|
||||
'page_3party_3party_index',
|
||||
__dirname + '/Resources/public/page/index/index.js'
|
||||
|
@ -1,19 +1,5 @@
|
||||
services:
|
||||
Chill\ThirdPartyBundle\Form\ThirdPartyType:
|
||||
arguments:
|
||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$typesManager: '@Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager'
|
||||
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
|
||||
$om: '@doctrine.orm.entity_manager'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
$urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
$typesManager: '@Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
Chill\ThirdPartyBundle\Form\:
|
||||
resource: '../../Form/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -16,7 +16,9 @@ thirdparty.NameCompany: Service/Département
|
||||
thirdparty.Acronym: Sigle
|
||||
thirdparty.Categories: Catégories
|
||||
thirdparty.Child: Personne de contact
|
||||
thirdparty.child: Personne de contact
|
||||
thirdparty.Children: Personnes de contact
|
||||
thirdparty.children: Personnes de contact
|
||||
thirdparty.Parent: Tiers institutionnel
|
||||
thirdparty.Parents: Tiers institutionnels
|
||||
thirdparty.Civility: Civilité
|
||||
@ -29,15 +31,17 @@ thirdparty.UpdateBy.short: ' par '
|
||||
thirdparty.CreatedAt.long: Date de création
|
||||
thirdparty.UpdatedAt.long: Date de la dernière modification
|
||||
thirdparty.UpdateBy.long: Utilisateur qui a effectué la dernière modification
|
||||
thirdparty.A company: Une institution
|
||||
thirdparty.company: Institution
|
||||
thirdparty.A company: Une personne morale
|
||||
thirdparty.company: Personne morale
|
||||
thirdparty.A contact: Une personne physique
|
||||
thirdparty.contact: Personne physique
|
||||
thirdparty.Contact of: Contact de
|
||||
thirdparty.a_company_explanation: >-
|
||||
Les institutions peuvent compter un ou plusieurs contacts, interne à l'instution. 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.
|
||||
Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. Il est possible de leur
|
||||
indiquer une civilité et un métier.
|
||||
thirdparty.Which kind of third party ?: Quel type de tiers souhaitez-vous créer ?
|
||||
thirdparty.Contact data are confidential: Données de contact confidentielles
|
||||
|
||||
@ -65,6 +69,7 @@ No nameCompany given: Aucune raison sociale renseignée
|
||||
No acronym given: Aucun sigle renseigné
|
||||
No phone given: Aucun téléphone renseigné
|
||||
No email given: Aucune adresse courriel renseignée
|
||||
thirdparty.Any categories: Aucune catégorie
|
||||
|
||||
The party is visible in those centers: Le tiers est visible dans ces centres
|
||||
The party is not visible in any center: Le tiers n'est associé à aucun centre
|
||||
|
Loading…
x
Reference in New Issue
Block a user