Commit d63df04c by Damien Moulard

fix merge conflict

parents 080eef95 fec7bc5e
......@@ -53,6 +53,15 @@ legend.required:after {
display: inline-block !important;
}
.header-placeholder {
height: 0.5rem;
}
.payment-page-header {
display: flex;
justify-content: space-between;
}
#code-digits-container {
display: flex;
flex-wrap: wrap;
......@@ -87,11 +96,11 @@ legend.required:after {
}
#code-visual {
height: 60px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
font-size: 3rem;
}
#payment-validation-subheader {
......@@ -101,3 +110,187 @@ legend.required:after {
margin-top: -10px;
margin-bottom: 20px;
}
.formEncaisserCotisationAdherent-no-profile {
font-style: italic;
color: #ff4136;
}
form[name="formEncaissement"] label {
font-size: 1.3rem;
}
.payment-done-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.payment-done-title {
color: #4CAF50;
font-weight: bold;
}
/**
* Success check animation.
* Extracted from: SweetAlert
* Modified by: Istiak Tridip
*/
.success-checkmark {
width: 80px;
height: 115px;
margin: 0 auto;
}
.success-checkmark .check-icon {
width: 80px;
height: 80px;
position: relative;
border-radius: 50%;
box-sizing: content-box;
border: 4px solid #4CAF50;
}
.success-checkmark .check-icon::before {
top: 3px;
left: -2px;
width: 30px;
transform-origin: 100% 50%;
border-radius: 100px 0 0 100px;
}
.success-checkmark .check-icon::after {
top: 0;
left: 30px;
width: 60px;
transform-origin: 0 50%;
border-radius: 0 100px 100px 0;
animation: rotate-circle 4.25s ease-in;
}
.success-checkmark .check-icon::before, .success-checkmark .check-icon::after {
content: '';
height: 100px;
position: absolute;
background: #FFFFFF;
transform: rotate(-45deg);
}
.success-checkmark .icon-line {
height: 5px;
background-color: #4CAF50;
display: block;
border-radius: 2px;
position: absolute;
z-index: 10;
}
.success-checkmark .icon-line.line-tip {
top: 46px;
left: 14px;
width: 25px;
transform: rotate(45deg);
animation: icon-line-tip 0.75s;
}
.success-checkmark .icon-line.line-long {
top: 38px;
right: 8px;
width: 47px;
transform: rotate(-45deg);
animation: icon-line-long 0.75s;
}
.success-checkmark .icon-circle {
top: -4px;
left: -4px;
z-index: 10;
width: 80px;
height: 80px;
border-radius: 50%;
position: absolute;
box-sizing: content-box;
border: 4px solid rgba(76, 175, 80, .5);
}
.success-checkmark .icon-fix {
top: 8px;
width: 5px;
left: 26px;
z-index: 1;
height: 85px;
position: absolute;
transform: rotate(-45deg);
background-color: #FFFFFF;
}
@keyframes rotate-circle {
0% {
transform: rotate(-45deg);
}
5% {
transform: rotate(-45deg);
}
12% {
transform: rotate(-405deg);
}
100% {
transform: rotate(-405deg);
}
}
@keyframes icon-line-tip {
0% {
width: 0;
left: 1px;
top: 19px;
}
54% {
width: 0;
left: 1px;
top: 19px;
}
70% {
width: 50px;
left: -8px;
top: 37px;
}
84% {
width: 17px;
left: 21px;
top: 48px;
}
100% {
width: 25px;
left: 14px;
top: 45px;
}
}
@keyframes icon-line-long {
0% {
width: 0;
right: 46px;
top: 54px;
}
65% {
width: 0;
right: 46px;
top: 54px;
}
84% {
width: 55px;
right: 0px;
top: 35px;
}
100% {
width: 47px;
right: 8px;
top: 38px;
}
}
/**
* END Success check animation.
*/
\ No newline at end of file
......@@ -69,4 +69,27 @@ $(document).ready(function() {
}
});
});
// Hide or display Cotisation or Profils de Cotisation submenu depending on TAV env
const tav_env = document.getElementsByName('is-tav-env')[0].getAttribute('content');
let linksToCotisationAdherent = $('a[href="/admin/cotisation_adherent/list"]');
for(let i = 0 ; i < linksToCotisationAdherent.length ; i++) {
if(linksToCotisationAdherent[i].innerText === "Cotisation") {
linksToCotisationAdherent[i].parentNode.style.display = (tav_env === "1") ? "none" : "";
}
}
let linksToCotisationPresta = $('a[href="/admin/cotisation_prestataire/list"]');
for(let i = 0 ; i < linksToCotisationPresta.length ; i++) {
if(linksToCotisationPresta[i].innerText === "Cotisation") {
linksToCotisationPresta[i].parentNode.style.display = (tav_env === "1") ? "none" : "";
}
}
let linksToProfilDeCotisation = $('a[href="/admin/profilcotisation/list"]');
for(let i = 0 ; i < linksToProfilDeCotisation.length ; i++) {
if(linksToProfilDeCotisation[i].innerText === "Profils De Cotisation") {
linksToProfilDeCotisation[i].parentNode.style.display = (tav_env === "1") ? "" : "none";
}
}
});
......@@ -492,13 +492,13 @@ $(function() {
$("#formEncaissement_payment_code").val('');
// Set payment summary on validation page
let paymentValidationSubheaderText = `${$("#formEncaissement_adherent option:selected").text()} - Montant : ${$("#formEncaissement_montant").val()}`;
$("#payment-validation-subheader").text(paymentValidationSubheaderText);
$(".payment-recap").text(`${$("#formEncaissement_adherent option:selected").text()}, montant : ${$("#formEncaissement_montant").val()}`);
}
/**
* On cashing payment, validation code screen, add digit to input when corresponding button clicked.
*/
$(".payment-code-button").off("click");
$(".payment-code-button").on("click", function() {
// Get existing input
let code = $("#formEncaissement_payment_code").val();
......@@ -511,6 +511,7 @@ $(function() {
$("#code-visual").text("*".repeat(code.length));
});
$(".payment-code-correction-button").off("click");
$(".payment-code-correction-button").on("click", function() {
// Get existing input
let code = $("#formEncaissement_payment_code").val();
......@@ -521,6 +522,32 @@ $(function() {
$("#code-visual").text("*".repeat(code.length));
});
$("#formEncaisserCotisationAdherent_destinataire").on("change", function() {
const cotisationMontants = JSON.parse($("#formEncaisserCotisationAdherent_cotisationMontants").val());
let cotisationmontant = cotisationMontants[this.value];
console.log(cotisationmontant);
if (cotisationmontant === undefined) {
$("#formEncaisserCotisationAdherent-montant-container").hide();
$("#formEncaisserCotisationAdherent-no-profile").hide();
return;
}
if (cotisationmontant !== null) {
$("#formEncaisserCotisationAdherent-montant-display").text(`${cotisationmontant} €`);
$("#formEncaisserCotisationAdherent-montant-container").show();
$("#formEncaisserCotisationAdherent-no-profile").hide();
$("#formEncaisserCotisationAdherent_save").prop("disabled",false);
} else {
// no cotisation profile
$("#formEncaisserCotisationAdherent-montant-container").hide();
$("#formEncaisserCotisationAdherent-no-profile").show();
$("#formEncaisserCotisationAdherent_save").prop("disabled",true);
}
});
// $('.js-datepicker').datepicker({
// closeText: 'Fermer',
// prevText: '&#x3c;Préc',
......
......@@ -111,17 +111,23 @@ services:
class: App\Utils\CotisationUtils
autowire: false
public: true
arguments: ['@app.utils.custom_entity_manager', '@session', '@security.helper', '@event_dispatcher']
arguments: ['@app.utils.custom_entity_manager', '@session', '@security.helper', '@event_dispatcher', '@service_container']
app.utils.account:
class: App\Utils\AccountUtils
autowire: false
arguments: ['@doctrine.orm.entity_manager']
app.utils.tav_cotisations:
class: App\Utils\TAVCotisationUtils
autowire: false
public: true
arguments: ['@app.utils.custom_entity_manager', '@security.helper', '@app.utils.operations']
app.twig.main.extension:
class: App\Twig\AppExtension
autowire: false
arguments: ["@service_container", "@security.helper", "@doctrine.orm.entity_manager", "@knp_paginator", "@session", "@app.utils.operations", "@app.utils.cotisations"]
arguments: ["@service_container", "@security.helper", "@doctrine.orm.entity_manager", "@knp_paginator", "@session", "@app.utils.operations", "@app.utils.cotisations", "@app.utils.tav_cotisations"]
app.twig.logentry.extension:
class: App\Twig\LogEntryExtension
......
......@@ -131,7 +131,8 @@ App\Entity\Usergroup:
'ROLE_ADMIN_PRESTATAIRE_GERER_EDIT',
'ROLE_ADMIN_PRESTATAIRE_COTISATIONS_EDIT',
'ROLE_ADMIN_PRESTATAIRE_COTISATIONS_LIST',
'ROLE_ADMIN_PRESTATAIRE_COTISATIONS_CREATE']]
'ROLE_ADMIN_PRESTATAIRE_COTISATIONS_CREATE',
'ROLE_CHANGE_ADHERENT_PERSONAL_DATA']]
usergroup_contact:
__construct: ['Contact', [
'ROLE_CONTACT',
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,16 +3,16 @@
"app": {
"js": [
"/build/runtime.6ad5c9da.js",
"/build/app.d475cc01.js"
"/build/app.fe8f482e.js"
],
"css": [
"/build/app.0815270c.css"
"/build/app.8f9aaf53.css"
]
},
"admin": {
"js": [
"/build/runtime.6ad5c9da.js",
"/build/admin.8a6adf4b.js"
"/build/admin.b35f5723.js"
],
"css": [
"/build/admin.5dc0eea7.css"
......
{
"build/app.css": "/build/app.0815270c.css",
"build/app.js": "/build/app.d475cc01.js",
"build/app.css": "/build/app.8f9aaf53.css",
"build/app.js": "/build/app.fe8f482e.js",
"build/admin.css": "/build/admin.5dc0eea7.css",
"build/admin.js": "/build/admin.8a6adf4b.js",
"build/admin.js": "/build/admin.b35f5723.js",
"build/runtime.js": "/build/runtime.6ad5c9da.js",
"build/images/fa-solid-900.svg": "/build/images/fa-solid-900.a838c42a.svg",
"build/images/fa-brands-400.svg": "/build/images/fa-brands-400.05d20183.svg",
......
......@@ -6,6 +6,7 @@ use App\Entity\AccountAdherent;
use App\Entity\Adherent;
use App\Entity\CotisationAdherent;
use App\Entity\Geoloc;
use App\Entity\GlobalParameter;
use App\Entity\Groupe;
use App\Entity\User;
use App\Entity\Usergroup;
......@@ -26,6 +27,7 @@ use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Route\RouteCollection;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\DoctrineORMAdminBundle\Datagrid\OrderByToSelectWalker;
use Sonata\DoctrineORMAdminBundle\Filter\CallbackFilter;
use Sonata\Form\Type\DateTimeRangePickerType;
......@@ -110,6 +112,7 @@ class AdherentAdmin extends AbstractAdmin
'uri' => $this->getConfigurationPool()->getContainer()->get('router')->generate('admin_app_user_edit', ['id' => $user->getId()], UrlGeneratorInterface::ABSOLUTE_URL),
]);
}
if (!$this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$menu->addChild('Ajouter une cotisation', [
'uri' => $this->getConfigurationPool()->getContainer()->get('router')->generate('cotisation_adherent_create', ['expediteur' => $id], UrlGeneratorInterface::ABSOLUTE_URL),
]);
......@@ -117,6 +120,7 @@ class AdherentAdmin extends AbstractAdmin
'uri' => $this->getConfigurationPool()->getContainer()->get('router')->generate('cotisation_adherent_list', ['filter' => ['expediteur' => ['value' => $id]]], UrlGeneratorInterface::ABSOLUTE_URL),
]);
}
}
/**
* {@inheritdoc}
......@@ -169,12 +173,13 @@ class AdherentAdmin extends AbstractAdmin
if ($this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$formMapper
->tab('General')
->with('Profil de cotisation', ['class' => 'col-md-5'])
->with('Informations de cotisation', ['class' => 'col-md-5'])
->add('profilDeCotisation', ChoiceType::class, [
'required' => false,
'label' => 'Choix du profil : ',
'required' => true,
'label' => 'Choix du profil de cotisation :',
'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(ProfilDeCotisation::class)->findAll(),
'empty_data' => null,
'placeholder' => 'Choisir un profil',
'choice_label' => function ($choice, $key, $value) {
if (null === $choice) {
return '';
......@@ -185,6 +190,23 @@ class AdherentAdmin extends AbstractAdmin
])
->end()
->end();
if (!empty($adherent) && !empty($adherent->getEmlcAccount()) ) {
$em = $this->getConfigurationPool()->getContainer()->get('doctrine')->getManager();
$balance = $adherent->getEmlcAccount()->getBalance();
$mlc = $em->getRepository(GlobalParameter::class)->val(GlobalParameter::MLC_SYMBOL);
$formMapper
->tab('General')
->with('Informations de cotisation')
->add('idmlc', TextType::class, [
'disabled' => true,
'required' => false,
'label' => 'Solde e-' . $mlc . ' :',
'data' => $balance . ' ' . $mlc
])
->end()
->end();
}
}
$em = $this->getConfigurationPool()->getContainer()->get('doctrine')->getManager();
......@@ -287,6 +309,10 @@ class AdherentAdmin extends AbstractAdmin
'label' => 'Date de mise à jour',
])
;
if ($this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$datagridMapper->remove('cotisationajour');
}
}
public function getCotisationFilter($queryBuilder, $alias, $field, $value)
......@@ -384,6 +410,10 @@ class AdherentAdmin extends AbstractAdmin
->addIdentifier('user.lastname', null, ['label' => 'Nom'])
->addIdentifier('user.firstname', null, ['label' => 'Prénom'])
->addIdentifier('user.email', null, ['label' => 'Email'])
;
if (!$this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$listMapper
->add(
'cotisation',
null,
......@@ -391,7 +421,20 @@ class AdherentAdmin extends AbstractAdmin
'label' => 'Cotisation à jour',
'template' => '@kohinos/bundles/SonataAdminBundle/CRUD/list_user_cotisation.html.twig',
]
)
);
} else {
$listMapper
->add(
'cotisation',
null,
[
'label' => 'Dernière cotisation',
'template' => '@kohinos/tav/list_user_tav_cotisation.html.twig',
]
);
}
$listMapper
->addIdentifier('groupe', null, [
'label' => 'Groupe',
'sortable' => true,
......@@ -500,3 +543,4 @@ class AdherentAdmin extends AbstractAdmin
return $iterator;
}
}
......@@ -21,6 +21,7 @@ use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Administration des cotisations.
......@@ -189,6 +190,14 @@ class CotisationAdmin extends AbstractAdmin
*/
protected function configureListFields(ListMapper $listMapper)
{
// In TAV env, block access to standard cotisations admin.
// TODO: find a better way (eg. block access with App\EventListener\MenuBuilderListener?)
if ($this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$url = $this->getConfigurationPool()->getContainer()->get( 'router' )->generate( 'sonata_admin_dashboard' );
$redirection = new RedirectResponse( $url );
$redirection->send();
}
unset($this->listModes['mosaic']);
$listMapper
->add('id', 'text', [
......
......@@ -144,7 +144,7 @@ class PrestataireAdmin extends AbstractAdmin
// ]);
}
// Le prestataire "Monnaie Locale" représentant l'asso recevant les cotisations n'a pas de cotisations lui même !
if (!$presta->isMlc()) {
if (!$presta->isMlc() && !$this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$menu->addChild('Ajouter une cotisation', [
'uri' => $this->getConfigurationPool()->getContainer()->get('router')->generate('cotisation_prestataire_create', ['expediteur' => $id], UrlGeneratorInterface::ABSOLUTE_URL),
]);
......@@ -519,6 +519,11 @@ class PrestataireAdmin extends AbstractAdmin
'advanced_filter' => false,
])
;
if ($this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$datagridMapper->remove('cotisationajour');
$datagridMapper->remove('gestionnaireajour');
}
}
public function getGestionnaireFilter($queryBuilder, $alias, $field, $value)
......@@ -691,6 +696,7 @@ class PrestataireAdmin extends AbstractAdmin
];
}
}
// listmappers for each environment (pro & tav_env)
$listMapper
->addIdentifier('raisonAndIdmlc', 'html', [
'label' => 'Raison',
......@@ -719,6 +725,7 @@ class PrestataireAdmin extends AbstractAdmin
'label' => 'Cotisation à jour',
'template' => '@kohinos/bundles/SonataAdminBundle/CRUD/list_presta_cotisation.html.twig',
]
)
->add('users', null, [
'label' => 'Gestionnaires [Cotisation à jour]',
......@@ -745,6 +752,11 @@ class PrestataireAdmin extends AbstractAdmin
'actions' => $actions,
])
;
if ($this->getConfigurationPool()->getContainer()->getParameter('tav_env')) {
$listMapper->remove('cotisation');
$listMapper->remove('users');
}
}
protected function configureRoutes(RouteCollection $collection)
......
......@@ -194,7 +194,10 @@ class UserAdmin extends BaseUserAdmin
->add('username')
->add('email')
;
if ('dev' == $_ENV['APP_ENV'] && $this->isGranted('ROLE_SUPER_ADMIN')) {
if (
'dev' == $_ENV['APP_ENV'] && ($this->isGranted('ROLE_SUPER_ADMIN')
|| $this->getSecurityHandler()->grantChangeAdherentPasswordToNonSuperAdmin('ROLE_CHANGE_ADHERENT_PERSONAL_DATA'))
) {
$formMapper
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
......
......@@ -12,6 +12,8 @@ use App\Enum\CurrencyEnum;
use App\Flux\FluxInterface;
use App\Utils\CustomEntityManager;
use App\Utils\OperationUtils;
use App\Utils\TAVCotisationUtils;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Util\TokenGeneratorInterface;
use Gamez\Symfony\Component\Serializer\Normalizer\UuidNormalizer;
use Sonata\Exporter\Handler;
......@@ -81,6 +83,7 @@ class FluxController extends AbstractController
SessionInterface $session,
Environment $templating,
OperationUtils $operationUtils,
TAVCotisationUtils $tavCotisationsUtils,
TokenGeneratorInterface $tokenGenerator,
ValidatorInterface $validator,
CsrfTokenManagerInterface $tokenManager
......@@ -95,6 +98,7 @@ class FluxController extends AbstractController
$this->tokenGenerator = $tokenGenerator;
$this->validator = $validator;
$this->tokenManager = $tokenManager;
$this->tavCotisationsUtils = $tavCotisationsUtils;
}
protected function manageFluxForm(Request $request, Form $form, $template = '@kohinos/flux/transaction.html.twig', $params = [])
......
......@@ -136,19 +136,46 @@ class UserAdherentController extends FluxController
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// TODO: set CB payment when functionality validated
// TODO: set CB payment when the functionality is validated
$flux = $form->getData();
// Look for existing cotisation
if ($this->tavCotisationsUtils->checkExistingCotisation($flux)) {
$this->addFlash(
'error',
$this->translator->trans('Cotisation déjà payée ce mois-ci.')
);
return $this->redirectToRoute('index');
}
$destinataire = $flux->getDestinataire();
$profile = $destinataire->getProfilDeCotisation();
if (is_null($profile)) {
$this->addFlash(
'error',
$this->translator->trans('Opération impossible : vous n\'avez pas de profil de cotisation associé. Veuillez contacter un administrateur.')
);
return $this->redirectToRoute('index');
}
if (null == $flux->getDon() || 0 == $flux->getDon()->getMontant()) {
$flux->setDon(null);
}
$this->em->persist($flux);
$this->operationUtils->executeOperations($flux);
// Apply cotisation rate, create new flux
$this->tavCotisationsUtils->applyTauxCotisation($flux);
$this->em->flush();
$this->addFlash(
'success',
$this->translator->trans('Cotisation payée ! [Payzen désactivé en attent du compte ; bonification et mensualité pas encore implémentés]')
$this->translator->trans('Cotisation payée ! [Paiement via Payzen temporairement désactivé]')
);
return $this->redirectToRoute('index');
......@@ -211,7 +238,7 @@ class UserAdherentController extends FluxController
$data = $form->getData();
$plainCode = $data->getPaymentCode();
if (is_numeric($plainCode) && strlen($plainCode) >= 4 && strlen($plainCode) <= 8) {
if (ctype_digit($plainCode) && strlen($plainCode) >= 4 && strlen($plainCode) <= 8) {
$encoded = crypt($plainCode, $this->getUser()->getSalt());
$adherent->setPaymentCode($encoded);
......
......@@ -21,6 +21,7 @@ use App\Form\Type\VenteComptoirAdherentFormType;
use App\Form\Type\VenteComptoirPrestataireFormType;
use App\Form\Type\VenteEmlcAdherentFormType;
use App\Form\Type\VenteEmlcPrestataireFormType;
use App\Form\Type\EncaisserCotisationAdherentFormType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
......@@ -186,4 +187,60 @@ class UserComptoirController extends FluxController
$form
);
}
/**
* @Route("/user/comptoir/encaisser/cotisation/adherent/", name="comptoirEncaisserCotisation")
* @IsGranted("ROLE_COMPTOIR")
*/
public function comptoirEncaisserCotisationAction(Request $request)
{
$entity = new VenteEmlcComptoirAdherent();
$entity->setOperateur($this->getUser());
$form = $this->createForm(EncaisserCotisationAdherentFormType::class, $entity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$flux = $form->getData();
// Look for existing cotisation
if ($this->tavCotisationsUtils->checkExistingCotisation($flux)) {
$this->addFlash(
'error',
$this->translator->trans('L\'adhérent•e a déjà payé sa cotisation ce mois-ci.')
);
return $this->redirectToRoute('index');
}
$destinataire = $flux->getDestinataire();
$profile = $destinataire->getProfilDeCotisation();
if (is_null($profile)) {
$this->addFlash(
'error',
$this->translator->trans('Opération impossible : l\'habitant.e n\'a pas de profil de cotisation associé.')
);
return $this->redirectToRoute('index');
}
$cotisationAmount = $profile->getMontant(); // Amount in € paid by the user
$flux->setMontant($cotisationAmount);
$this->em->persist($flux);
$this->operationUtils->executeOperations($flux);
// Apply cotisation rate, create new flux
$this->tavCotisationsUtils->applyTauxCotisation($flux);
$this->em->flush();
$this->addFlash(
'success',
$this->translator->trans('Opération réussie !')
);
}
return $this->redirectToRoute('index');
}
}
......@@ -256,14 +256,6 @@ class UserController extends AbstractController
$data = $form->getData();
if ($form->isValid()) {
$input_code = $data["payment_code"];
$validation = true; // As soon as the form is submitted & valid, we enter the validation process
if (empty($input_code)) {
// First step validated (set user & amount) -> go to validation
goto end;
}
$adherent = $data["adherent"];
$adherent_code = $adherent->getPaymentCode();
......@@ -287,8 +279,16 @@ class UserController extends AbstractController
goto end;
}
$input_code = $data["payment_code"];
$validation = true; // When the form is submitted & valid, and the user account checks passed, we enter the validation process
if (empty($input_code)) {
// First step validated (set user & amount) -> go to validation
goto end;
}
// Check validation code
// NOTE as we use password salt, must change payment code if password changes
// NOTE as we use password salt, the user must change his payment code if his password changes
$encoded_input = crypt($input_code, $adherent->getUser()->getSalt());
if (!hash_equals($adherent_code, $encoded_input)) {
$this->addFlash(
......@@ -346,12 +346,7 @@ class UserController extends AbstractController
$this->operationUtils->executeOperations($flux);
$this->em->flush();
$this->addFlash(
'success',
$this->translator->trans('Transaction réussie !')
);
return $this->redirectToRoute('index');
return $this->redirectToRoute('encaissementSuccess');
} else {
$this->addFlash(
'error',
......@@ -366,4 +361,13 @@ class UserController extends AbstractController
'validation' => $validation
]);
}
/**
* @Route("/encaissement/succes", name="encaissementSuccess")
* @IsGranted({"ROLE_CAISSIER", "ROLE_PRESTATAIRE"})
*/
public function encaissementSuccessAction(Request $request)
{
return $this->render('@kohinos/tav/payment_done_page.html.twig', []);
}
}
......@@ -54,6 +54,9 @@ use Symfony\Component\Validator\Constraints as Assert;
* "ticket_fix" = "TicketFix",
* "ticket_fix_print" = "TicketFixPrint",
* "ticket_fix_destroy" = "TicketFixDestroy",
* "application_taux_cotisation" = "TauxCotisationApplication",
* "reversement_cotisation_adherent" = "TauxCotisationReversement",
* "prelevement_cotisation_adherent" = "TauxCotisationPrelevement",
* })
*/
abstract class Flux implements FluxInterface
......@@ -71,6 +74,7 @@ abstract class Flux implements FluxInterface
const TYPE_VENTE = 'vente';
const TYPE_VENTE_EMLC = 'vente_emlc';
const TYPE_TICKET_FIX = 'ticket_fix';
const TYPE_APPLICATION_TAUX_COTISATION = 'application_taux_cotisation';
/**
* @var \Ramsey\Uuid\UuidInterface
......@@ -116,7 +120,7 @@ abstract class Flux implements FluxInterface
*
* @var string
*
* @ORM\Column(name="parenttype", type="string", length=20)
* @ORM\Column(name="parenttype", type="string", length=30)
* @Assert\NotBlank
* @Groups({"read", "write"})
*/
......
<?php
namespace App\Entity;
use App\Enum\CurrencyEnum;
use App\Utils\OperationFactory;
use Doctrine\ORM\Mapping as ORM;
/**
* Application du taux de cotisation lors du paiement d'une cotisation (au sens TAV).
*
* Au paiement d'une cotisation:
* - Un premier Flux est enregistré, correspondant à l'achat/vente d'emlc.
* - Puis on applique le taux défini dans le ProfilDeCotisation de l'adhérent,
* on crée un nouveau flux pour compléter la cositsation.
*
* @ORM\Entity
*/
class TauxCotisationApplication extends Flux
{
const TYPE_REVERSEMENT_COTISATION_ADHERENT = 'reversement_cotisation_adherent';
const TYPE_PRELEVEMENT_COTISATION_ADHERENT = 'prelevement_cotisation_adherent';
/**
* @return string
*/
public function getParenttype(): string
{
return parent::TYPE_APPLICATION_TAUX_COTISATION;
}
public function getAllOperations($em)
{
return [];
}
public function operate($em)
{
return [];
}
/**
* @return string
*/
public function getType(): string
{
return '';
}
public function getUsersToNotify()
{
return [];
}
}
<?php
namespace App\Entity;
use App\Enum\CurrencyEnum;
use App\Utils\OperationFactory;
use Doctrine\ORM\Mapping as ORM;
/**
* En cas de taux < 1, l'adhérent•e reçoit moins d'emlc que ce qu'elle•il paye en € :
* un second flux est créé pour prélever le complément de la cotisation.
*
* @ORM\Entity
*/
class TauxCotisationPrelevement extends TauxCotisationApplication
{
/**
* @ORM\OneToOne(targetEntity="Adherent")
* @ORM\JoinColumn(name="adherent_id", referencedColumnName="id")
*/
protected $expediteur;
/**
* @ORM\OneToOne(targetEntity="Siege")
* @ORM\JoinColumn(name="siege_id", referencedColumnName="id")
*/
protected $destinataire;
public function getAllOperations($em)
{
return [
OperationFactory::getOperation($this, $this->getExpediteur(), CurrencyEnum::CURRENCY_EMLC, -$this->getMontant()),
OperationFactory::getOperation($this, $this->getDestinataire(), CurrencyEnum::CURRENCY_EMLC, -$this->getMontant()), // Decrease siege ecompte too
];
}
public function operate($em)
{
$this->getDestinataire()->removeEcompteNantie($this->getMontant());
$this->getExpediteur()->removeEcompte($this->getMontant());
return [$this->getExpediteur(), $this->getDestinataire()];
}
/**
* @return string
*/
public function getType(): string
{
return parent::TYPE_PRELEVEMENT_COTISATION_ADHERENT;
}
public function getUsersToNotify()
{
return [];
}
}
<?php
namespace App\Entity;
use App\Enum\CurrencyEnum;
use App\Utils\OperationFactory;
use Doctrine\ORM\Mapping as ORM;
/**
* En cas de taux > 1, l'adhérent•e reçoit plus d'emlc que ce qu'elle•il paye en € :
* un second flux est créé pour reverser le complément de la cotisation.
*
* @ORM\Entity
*/
class TauxCotisationReversement extends TauxCotisationApplication
{
/**
* @ORM\OneToOne(targetEntity="Siege")
* @ORM\JoinColumn(name="siege_id", referencedColumnName="id")
*/
protected $expediteur;
/**
* @ORM\OneToOne(targetEntity="Adherent")
* @ORM\JoinColumn(name="adherent_id", referencedColumnName="id")
*/
protected $destinataire;
public function getAllOperations($em)
{
return [
OperationFactory::getOperation($this, $this->getExpediteur(), CurrencyEnum::CURRENCY_EMLC, $this->getMontant()), // Increase Siege ecompte
OperationFactory::getOperation($this, $this->getDestinataire(), CurrencyEnum::CURRENCY_EMLC, $this->getMontant()),
];
}
public function operate($em)
{
$this->getExpediteur()->addEcompteNantie($this->getMontant());
$this->getDestinataire()->addEcompte($this->getMontant());
return [$this->getExpediteur(), $this->getDestinataire()];
}
/**
* @return string
*/
public function getType(): string
{
return parent::TYPE_REVERSEMENT_COTISATION_ADHERENT;
}
public function getUsersToNotify()
{
return [];
}
}
......@@ -51,6 +51,7 @@ use App\Form\Type\UserInfosFormType;
use App\Form\Type\VenteEmlcAdherentFormType;
use App\Form\Type\VenteEmlcPrestataireFormType;
use App\Form\Type\SetPaymentCodeFormType;
use App\Form\Type\EncaisserCotisationAdherentFormType;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Form\Type\ChangePasswordFormType;
use Symfony\Component\Form\FormFactoryInterface as FormF;
......@@ -445,6 +446,19 @@ class FormFactory
return $form->createView();
}
public function getComptoirEncaisserCotisationForm(User $user)
{
if (empty($user) || empty($this->session->get('_comptoirgere'))) {
throw new \Exception('[FORM 26] Opération impossible !');
}
$entity = new AchatMonnaieAdherent();
$entity->setOperateur($user);
$form = $this->ff->create(EncaisserCotisationAdherentFormType::class, $entity, ['action' => $this->router->generate('comptoirEncaisserCotisation')]);
return $form->createView();
}
public function getPayerCotisationTAVForm(User $user)
{
if (empty($user) || !$user->isGranted('ROLE_ADHERENT')) {
......@@ -457,5 +471,4 @@ class FormFactory
return $form->createView();
}
}
......@@ -40,7 +40,7 @@ class AchatMonnaieFormType extends FluxFormType
if (null != $profilDeCotisation) {
$montant = $profilDeCotisation->getMontant();
} else {
$montant = intval($this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::COTISATION_ADHERENT_DEFAULT));
$montant = false;
}
$builder
......
......@@ -36,7 +36,7 @@ class EncaissementFormType extends AbstractType
],
'placeholder' => 'Choisissez un.e adhérent.e',
'required' => true,
'label' => 'Adherent :',
'label' => 'Habitant•e : ',
'choice_label' => 'name'
])
->add('montant', NumberType::class, [
......
<?php
namespace App\Form\Type;
use App\Entity\Comptoir;
use App\Entity\Adherent;
use App\Entity\VenteEmlcComptoirAdherent;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EncaisserCotisationAdherentFormType extends VenteEmlcAdherentFormType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (empty($this->security) || empty($this->security->getUser())) {
throw new \Exception('[FORM VENTE EMLC] Opération impossible !');
}
// Get the cotisation amount for each adherent. Used for display purposes on the front end.
$adherents = $this->em->getRepository(Adherent::class)->findOrderByName();
$adherentsProfiles = [];
foreach ($adherents as $adh) {
$montant = null;
if (!is_null($adh->getProfilDeCotisation()) ) {
$montant = $adh->getProfilDeCotisation()->getMontant();
}
$adherentsProfiles[strval($adh->getId())] = $montant;
}
$builder
->add('reference', HiddenType::class, [
'data' => 'Encaissement cotisation adhérent'
])
->add('montant', HiddenType::class, [
'data' => 0
])
->add('cotisationMontants', HiddenType::class, [
'data' => json_encode($adherentsProfiles),
'mapped' => false
])
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => VenteEmlcComptoirAdherent::class,
]);
}
public function getParent()
{
return VenteEmlcAdherentFormType::class;
}
public function getBlockPrefix()
{
return 'formEncaisserCotisationAdherent';
}
}
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20221211160940 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
//$this->addSql('UPDATE usergroup SET roles = \'a:13:{i:0;s:13:\"ROLE_COMPTOIR\";i:1;s:30:\"ROLE_ADMIN_ADHERENT_GERER_EDIT\";i:2;s:30:\"ROLE_ADMIN_ADHERENT_GERER_LIST\";i:3;s:32:\"ROLE_ADMIN_ADHERENT_GERER_CREATE\";i:4;s:30:\"ROLE_ADMIN_ADHERENT_GERER_VIEW\";i:5;s:35:\"ROLE_ADMIN_ADHERENT_COTISATIONS_ALL\";i:6;s:30:\"ROLE_ADMIN_COMPTOIR_GERER_EDIT\";i:7;s:30:\"ROLE_ADMIN_COMPTOIR_GERER_VIEW\";i:8;s:31:\"ROLE_ADMIN_TRANSFERT_GERER_LIST\";i:9;s:33:\"ROLE_ADMIN_TRANSFERT_GERER_CREATE\";i:10;s:31:\"ROLE_ADMIN_TRANSFERT_GERER_VIEW\";i:11;s:43:\"ROLE_ADMIN_OPERATION_PRESTATAIRE_GERER_LIST\";i:12;s:40:\"ROLE_ADMIN_OPERATION_ADHERENT_GERER_LIST\";}\' WHERE name = "Comptoir"');
$this->addSql('UPDATE usergroup SET roles = \'a:19:{i:0;s:13:\"ROLE_COMPTOIR\";i:1;s:30:\"ROLE_ADMIN_ADHERENT_GERER_EDIT\";i:2;s:30:\"ROLE_ADMIN_ADHERENT_GERER_LIST\";i:3;s:32:\"ROLE_ADMIN_ADHERENT_GERER_CREATE\";i:4;s:30:\"ROLE_ADMIN_ADHERENT_GERER_VIEW\";i:5;s:35:\"ROLE_ADMIN_ADHERENT_COTISATIONS_ALL\";i:6;s:30:\"ROLE_ADMIN_COMPTOIR_GERER_EDIT\";i:7;s:30:\"ROLE_ADMIN_COMPTOIR_GERER_VIEW\";i:8;s:31:\"ROLE_ADMIN_TRANSFERT_GERER_LIST\";i:9;s:33:\"ROLE_ADMIN_TRANSFERT_GERER_CREATE\";i:10;s:31:\"ROLE_ADMIN_TRANSFERT_GERER_VIEW\";i:11;s:33:\"ROLE_ADMIN_PRESTATAIRE_GERER_VIEW\";i:12;s:39:\"ROLE_ADMIN_PRESTATAIRE_COTISATIONS_VIEW\";i:13;s:33:\"ROLE_ADMIN_PRESTATAIRE_GERER_LIST\";i:14;s:33:\"ROLE_ADMIN_PRESTATAIRE_GERER_EDIT\";i:15;s:39:\"ROLE_ADMIN_PRESTATAIRE_COTISATIONS_EDIT\";i:16;s:39:\"ROLE_ADMIN_PRESTATAIRE_COTISATIONS_LIST\";i:17;s:41:\"ROLE_ADMIN_PRESTATAIRE_COTISATIONS_CREATE\";i:18;s:34:\"ROLE_CHANGE_ADHERENT_PERSONAL_DATA\";}\' WHERE name = "Comptoir"');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
}
}
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20221212100241 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE flux CHANGE parenttype parenttype VARCHAR(30) NOT NULL');
$this->addSql('ALTER TABLE prestataire CHANGE iban iban LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:personal_data)\'');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE flux CHANGE parenttype parenttype VARCHAR(20) CHARACTER SET utf8mb3 NOT NULL COLLATE `utf8mb3_general_ci`');
$this->addSql('ALTER TABLE prestataire CHANGE iban iban LONGTEXT CHARACTER SET utf8mb3 DEFAULT NULL COLLATE `utf8mb3_general_ci` COMMENT \'(DC2Type:personal_data)\'');
}
}
......@@ -239,4 +239,50 @@ class FluxRepository extends ServiceEntityRepository
->getQuery()
->getSingleScalarResult();
}
/**
* @param Adherent $adherent the user to look the cotisations for
* @param String $from, $to dates (ISO format) between wich to look for
*
* @return Array list of cotisations between the dates
*/
public function getTavCotisationsBetweenDates($adherent, $from, $to)
{
$sqlQuery = "SELECT f.id
FROM {$this->tableName} f
WHERE f.type IN ('achat_monnaie_adherent', 'vente_emlc_adherent')
AND f.created_at >= :f
AND f.created_at <= :t
AND (f.adherent_id = :adh_id OR f.adherent_dest_id = :adh_id)";
$statement = $this->connection->prepare($sqlQuery);
$statement->bindValue(':f', $from);
$statement->bindValue(':t', $to);
$statement->bindValue(':adh_id', $adherent->getId());
$statement->execute();
$results = $statement->fetchAll();
return $results;
}
/**
* @param Adherent $adherent the user to look the cotisation for
*
* @return
*/
public function getLastTavCotisation($adherent)
{
$sqlQuery = "SELECT f.created_at
FROM {$this->tableName} f
WHERE f.type IN ('achat_monnaie_adherent', 'vente_emlc_adherent')
AND (f.adherent_id = :adh_id OR f.adherent_dest_id = :adh_id)
ORDER BY created_at DESC";
$statement = $this->connection->prepare($sqlQuery);
$statement->bindValue(':adh_id', $adherent->getId());
$statement->execute();
$results = $statement->fetchAll();
return $results;
}
}
......@@ -3,6 +3,7 @@
namespace App\Security\Handler;
use App\Entity\GlobalParameter;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Security\Handler\RoleSecurityHandler;
......@@ -43,7 +44,8 @@ class VoterSecurityHandler extends RoleSecurityHandler
try {
return $this->isAnyGranted($this->superAdminRoles)
|| $this->isAnyGranted($attributes, $object)
|| $this->isAnyGranted([$allRole], $object);
|| $this->isAnyGranted([$allRole], $object)
|| $this->allowToModifyAdherentPersonalData($object);
} catch (AuthenticationCredentialsNotFoundException $e) {
return false;
}
......@@ -64,4 +66,16 @@ class VoterSecurityHandler extends RoleSecurityHandler
return false;
}
private function allowToModifyAdherentPersonalData($object): bool
{
return $_ENV['TAV_ENV']
&& $object instanceof User && $object->getAdherent()
&& $this->authorizationChecker->isGranted('ROLE_CHANGE_ADHERENT_PERSONAL_DATA');
}
public function grantChangeAdherentPasswordToNonSuperAdmin($str): bool
{
return $_ENV['TAV_ENV'] && $this->authorizationChecker->isGranted($str);
}
}
......@@ -27,6 +27,7 @@ use App\Enum\CurrencyEnum;
use App\Flux\AccountableInterface;
use App\Utils\CotisationUtils;
use App\Utils\OperationUtils;
use App\Utils\TAVCotisationUtils;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Knp\Component\Pager\PaginatorInterface;
......@@ -46,9 +47,18 @@ class AppExtension extends AbstractExtension
public $session;
public $operationUtils;
public $cotisationUtils;
public function __construct(ContainerInterface $container, Security $security, EntityManagerInterface $em, PaginatorInterface $paginator, SessionInterface $session, OperationUtils $operationUtils, CotisationUtils $cotisationUtils)
{
public $tavCotisationUtils;
public function __construct(
ContainerInterface $container,
Security $security,
EntityManagerInterface $em,
PaginatorInterface $paginator,
SessionInterface $session,
OperationUtils $operationUtils,
CotisationUtils $cotisationUtils,
TAVCotisationUtils $tavCotisationUtils
) {
$this->em = $em;
$this->security = $security;
$this->container = $container;
......@@ -56,6 +66,7 @@ class AppExtension extends AbstractExtension
$this->session = $session;
$this->operationUtils = $operationUtils;
$this->cotisationUtils = $cotisationUtils;
$this->tavCotisationUtils = $tavCotisationUtils;
}
public function getFunctions()
......@@ -92,6 +103,7 @@ class AppExtension extends AbstractExtension
new \Twig_SimpleFunction('getCurrencyName', [$this, 'getCurrencyName']),
new \Twig_SimpleFunction('getPaymentReceiptUrlFromFlux', [$this, 'getPaymentReceiptUrlFromFlux']),
new \Twig_SimpleFunction('getDonType', [$this, 'getDonType']),
new \Twig_SimpleFunction('getLastTavCotisationForAdherent', [$this, 'getLastTavCotisationForAdherent']),
new \Twig_SimpleFunction('parameter', function ($name) {
return $this->container->getParameter($name);
}),
......@@ -287,6 +299,18 @@ class AppExtension extends AbstractExtension
return $this->cotisationUtils->isCotisationValidForAdherent($adherent);
}
/**
* [getLastTavCotisationForAdherent.
*
* @param Adherent|null $adherent
*
* @return bool|date
*/
public function getLastTavCotisationForAdherent(?Adherent $adherent = null)
{
return $this->tavCotisationUtils->getLastTavCotisationForAdherent($adherent);
}
public function isDevFixture(?string $username = null)
{
if (null == $username) {
......
......@@ -53,7 +53,8 @@ class FormExtension extends AbstractExtension
new \Twig_SimpleFunction('getTicketFixPrintForm', [$this, 'getTicketFixPrintForm']),
new \Twig_SimpleFunction('getTicketFixDestroyForm', [$this, 'getTicketFixDestroyForm']),
new \Twig_SimpleFunction('getSetPaymentCodeForm', [$this, 'getSetPaymentCodeForm']),
new \Twig_SimpleFunction('getPayerCotisationTAVForm', [$this, 'getPayerCotisationTAVForm']),
new \Twig_SimpleFunction('getComptoirEncaisserCotisationForm', [$this, 'getComptoirEncaisserCotisationForm']),
new \Twig_SimpleFunction('getPayerCotisationTAVForm', [$this, 'getPayerCotisationTAVForm'])
];
}
......@@ -207,6 +208,11 @@ class FormExtension extends AbstractExtension
return $this->container->get('app.formfactory')->getSetPaymentCodeForm($user);
}
public function getComptoirEncaisserCotisationForm(User $user)
{
return $this->container->get('app.formfactory')->getComptoirEncaisserCotisationForm($user);
}
public function getPayerCotisationTAVForm(User $user)
{
return $this->container->get('app.formfactory')->getPayerCotisationTAVForm($user);
......
......@@ -9,6 +9,7 @@ use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\DependencyInjection\ContainerInterface;
class CotisationUtils
{
......@@ -16,13 +17,20 @@ class CotisationUtils
private $session;
private $security;
private $eventDispatcher;
public function __construct(CustomEntityManager $em, SessionInterface $session, Security $security, EventDispatcherInterface $eventDispatcher)
{
private $container;
public function __construct(
CustomEntityManager $em,
SessionInterface $session,
Security $security,
EventDispatcherInterface $eventDispatcher,
ContainerInterface $container
) {
$this->em = $em;
$this->session = $session;
$this->security = $security;
$this->eventDispatcher = $eventDispatcher;
$this->container = $container;
}
public function isUserCotisationExpired(UserInterface $user, int $nbDay = 30): bool
......@@ -97,6 +105,11 @@ class CotisationUtils
*/
public function isCotisationValidForPresta(?Prestataire $presta)
{
// In TAV mode, no standard cotisation required. Return next year date to make all accounts valid.
if ($this->container->getParameter('tav_env')) {
return date('Y-m-d', strtotime('+1 year'));
}
$query = null;
if (null !== $presta) {
$query = $this->em->getRepository(Flux::class)->getQueryByPrestataire($presta, 'cotisation');
......@@ -147,6 +160,11 @@ class CotisationUtils
*/
public function isCotisationValidForAdherent(?Adherent $adherent)
{
// In TAV mode, no standard cotisation required. Return next year date to make all accounts valid.
if ($this->container->getParameter('tav_env')) {
return date('Y-m-d', strtotime('+1 year'));
}
$query = null;
if (null !== $adherent) {
$query = $this->em->getRepository(Flux::class)->getQueryByAdherent($adherent, 'cotisation');
......@@ -172,6 +190,11 @@ class CotisationUtils
*/
public function isCotisationValid(?UserInterface $user)
{
// In TAV mode, no standard cotisation required. Return next year date to make all accounts valid.
if ($this->container->getParameter('tav_env')) {
return date('Y-m-d', strtotime('+1 year'));
}
$query = null;
if (null === $user && null != $this->security->getUser()) {
if ($this->security->isGranted('ROLE_ADHERENT') && null != $this->security->getUser()->getAdherent()) {
......
<?php
namespace App\Utils;
use App\Entity\Adherent;
use App\Entity\Siege;
use App\Entity\Flux;
use App\Entity\TauxCotisationReversement;
use App\Entity\TauxCotisationPrelevement;
use App\Enum\MoyenEnum;
use App\Utils\CustomEntityManager;
use Symfony\Component\Security\Core\Security;
class TAVCotisationUtils
{
private $em;
private $security;
private $operationUtils;
public function __construct (
CustomEntityManager $em,
Security $security,
OperationUtils $operationUtils
) {
$this->em = $em;
$this->security = $security;
$this->operationUtils = $operationUtils;
}
/**
* Check if cotisation already exist this month the the recipient of the given Flux.
*/
public function checkExistingCotisation(Flux $flux)
{
$first_day_this_month = date('Y-m-01');
$last_day_this_month = date('Y-m-t');
$existing = $this->em->getRepository(Flux::class)->getTavCotisationsBetweenDates(
$flux->getDestinataire(),
$first_day_this_month,
$last_day_this_month
);
return count($existing) > 0;
}
/**
* Apply the cotisation profile rate to the amount paid
* and register the complement as a new flux
*
* Warning: EntityManager not flushed here.
*/
public function applyTauxCotisation(Flux $flux)
{
// get the mlc amount the user will receive
$profile = $flux->getDestinataire()->getProfilDeCotisation();
$cotisationAmount = $profile->getMontant();
$cotisationTaux = $profile->getTauxCotisation();
$mlcAmount = round($cotisationAmount * $cotisationTaux);
// get the difference between what the user paid and what he•she's supposed to receive
$amountDiff = $mlcAmount - $cotisationAmount;
if ($flux->getExpediteur() instanceof Siege) {
$siege = $flux->getExpediteur();
} else {
$siege = $flux->getExpediteur()->getGroupe()->getSiege();
}
if ($amountDiff > 0) {
// User should receive more than he•she paid: send a new flux to the user to complete its cotisation
$fluxCotis = new TauxCotisationReversement();
$fluxCotis->setExpediteur($siege);
$fluxCotis->setDestinataire($flux->getDestinataire());
$fluxCotis->setMontant($amountDiff);
$fluxCotis->setReference("Reversement cotisation après paiement de " . $cotisationAmount . "€ et application du taux " . $cotisationTaux);
} else {
// User should receive less than he•she paid: fetch the difference from his account
$fluxCotis = new TauxCotisationPrelevement();
$fluxCotis->setExpediteur($flux->getDestinataire());
$fluxCotis->setDestinataire($siege);
$fluxCotis->setMontant(-$amountDiff);
$fluxCotis->setReference("Prélèvement cotisation après paiement de " . $cotisationAmount . "€ et application du taux " . $cotisationTaux);
}
$fluxCotis->setOperateur($this->security->getUser());
$fluxCotis->setRole($this->security->getUser()->getGroups()[0]->__toString());
$fluxCotis->setMoyen(MoyenEnum::MOYEN_EMLC);
$this->em->persist($fluxCotis);
$this->operationUtils->executeOperations($fluxCotis);
}
/**
* Get the last cotisation of an adhérent
*
* @param Adherent $adherent
*
* @return bool|date
*/
public function getLastTavCotisationForAdherent(?Adherent $adherent)
{
$cotisations = [];
if (null !== $adherent) {
$cotisations = $this->em->getRepository(Flux::class)->getLastTavCotisation($adherent);
}
if (count($cotisations) > 0) {
return $cotisations[0]["created_at"];
}
return false;
}
}
......@@ -12,9 +12,9 @@
{% include '@kohinos/comptoir/block/retrait_adherent.html.twig' %}
{% include '@kohinos/comptoir/block/reconversion.html.twig' %}
{% include '@kohinos/comptoir/block/reconversion_adherent.html.twig' %}
{% endif %}
{% include '@kohinos/comptoir/block/vente_emlc_adherent.html.twig' %}
{% if not tav_env %}
{% include '@kohinos/comptoir/block/vente_emlc_prestataire.html.twig' %}
{% else %}
{% include '@kohinos/tav/block/comptoir_encaisser_cotisation.html.twig' %}
{% endif %}
{% endif %}
\ No newline at end of file
......@@ -26,6 +26,7 @@ Modified for MLC from Sonata package.
<html {% block html_attributes %}class="no-js"{% endblock %}>
<head>
<title>{{title|default('Kohinos') }}</title>
<meta name="is-tav-env" content="{{ tav_env }}" />
<meta name="description" content="{{ description|default('Kohinos : Outil de gestion de monnaie locale complémentaire') }}" />
<meta name="keywords" content="{{ keywords|default('Kohinos, mlc, mlcc, monnaie locale, monnaies locales') }}" />
......
......@@ -6,10 +6,13 @@
{% block blockcontent %}
{% set form = getPayerCotisationTAVForm(app.user) %}
{% if form.montant.vars.value == false %}
<p>{{ 'Vous n\'avez pas de profil de cotisation associé, vous ne pouvez donc pas payer de cotisation.'|trans }}</p>
<p>{{ 'Veuillez contacter un administrateur.'|trans }}</p>
{% else %}
<p>
{{ 'Montant de la cotisation à payer'|trans }} : <span class="paiement_cotisation_montant">{{ form.montant.vars.value }}</span>
</p>
{{form_start(form)}}
{% if form.don is defined %}
{{ form_row(form.don, {row_attr:{style: 'max-width: 200px;margin: 0 auto;'}}) }}
......@@ -23,5 +26,7 @@
{{ form_widget(form.saveHelloAsso) }}
{% endif %}
{{form_end(form)}}
{% endif %}
{% endblock blockcontent %}
\ No newline at end of file
{% extends '@kohinos/block/onetransaction.html.twig' %}
{% block blocktitle %}
<i class="fa fa-exchange fa-exchange-alt mr-4"></i> {{ 'Encaisser la cotisation d\'un habitant'|trans }}
{% endblock blocktitle %}
{% block blockcontent %}
{% if app.session.has('_comptoirgere') %}
<div id="formEncaisserCotisationAdherent-montant-container" style="display:none">
<h5> <b>Montant de la cotisation : <span id="formEncaisserCotisationAdherent-montant-display"></span></b></h5>
<br/>
</div>
<div id="formEncaisserCotisationAdherent-no-profile" style="display:none">
<p class="formEncaisserCotisationAdherent-no-profile">
L'habitant•e n'a pas de profil de cotisation affecté, impossible de l'encaisser.
</p>
</div>
{% set form = getComptoirEncaisserCotisationForm(app.user) %}
{{ parent() }}
{% else %}
<span><em> {{ 'Aucun Comptoir assigné ! Veuillez contacter un administrateur !'|trans }} </em></span>
{% endif %}
{% endblock blockcontent %}
\ No newline at end of file
......@@ -5,6 +5,6 @@
{% endblock blocktitle %}
{% block blockcontent %}
<a class='btn btn-xs btn-primary mt-2' href='{{ path('encaissement') }}'>
{{ 'Encaisser'|trans }}
{{ 'Terminal de paiement'|trans }}
</a>
{% endblock blockcontent %}
{% extends '@kohinos/common/layout.html.twig' %}
{% block header %}
<div class="header-placeholder"></div>
{% endblock header %}
{% block content %}
<div class='container' style='max-width: 800px;'>
{% include '@kohinos/block/breadcrumb.html.twig' with {'label': 'Encaissement'} %}
{% if validation != true %}
<div class="mt-3 payment-page-header">
<a href='{{ path('index') }}'>
<i class="fas fa-chevron-circle-left"></i> {{ 'Retour'|trans }}
</a>
<b>{{ app.session.get('_prestagere') }}</b>
</div>
{% if validation == true %}
<h2 class='text-center w-100 mt-4 mb-4'>{{ "Code de validation de l'habitant·e"|trans }}</h2>
<div id="payment-validation-subheader"></div>
<h2 class='text-center w-100 mt-4 mb-4'>{{ "Paiement en Monnaie Solidaire"|trans }}</h2>
{% else %}
<h2 class='text-center w-100 mt-4 mb-4'>{{ "Encaissement"|trans }}</h2>
<div class="mt-3 payment-page-header">
<a href='{{ path('encaissement') }}'>
<i class="fas fa-chevron-circle-left"></i> {{ 'Retour'|trans }}
</a>
<b>{{ app.session.get('_prestagere') }}</b>
</div>
<h2 class='text-center w-100 mt-4 mb-4 payment-recap'></h2>
<div id="payment-validation-subheader">{{ "Veuillez entrer votre code de validation"|trans }}</div>
{% endif %}
<br/>
<div class='text-center mb-5'>
{{ form_start(form, {'attr': {'autocomplete': 'off'}}) }}
......@@ -39,8 +56,13 @@
{% else %}
{{ form_row(form.adherent) }}
{{ form_row(form.montant) }}
<br/>
{% endif %}
<br/>
{{ form_end(form) }}
</div>
</div>
{% endblock %}
{% block footer %}{% endblock footer %}
\ No newline at end of file
{% extends admin.getTemplate('base_list_field') %}
{% block field %}
{%- spaceless %}
{% set cotisEnd = getLastTavCotisationForAdherent(object.user.adherent) %}
{% if false != cotisEnd %}
{% set text = cotisEnd|date('d/m/Y') %}
{% set class = 'label-success' %}
{% else %}
{% set text = 'aucune'|trans %}
{% set class = 'label-danger' %}
{% endif %}
<span class="label {{ class }}">{{ text }}</span>
{% endspaceless -%}
{% endblock %}
{% extends '@kohinos/common/layout.html.twig' %}
{% block header %}{% endblock header %}
{% block content %}
<div class='container' style='max-width: 800px;'>
<div class="payment-done-container">
<h1 class="text-center w-100 mt-4 payment-done-title">Paiement réussi !</h1>
<br/>
<div class="success-checkmark">
<div class="check-icon">
<span class="icon-line line-tip"></span>
<span class="icon-line line-long"></span>
<div class="icon-circle"></div>
<div class="icon-fix"></div>
</div>
</div>
<br/>
<a class='btn btn-xs btn-primary mt-4' href='{{ path('encaissement') }}'>
{{ 'Nouvel encaissement'|trans }}
</a>
</div>
{% endblock %}
{% block footer %}{% endblock footer %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment