Feat/disable services option for groups (#711)
* add servicesDisabled field for groups and for global configuration * fixup! add servicesDisabled field for groups and for global configuration * comment libcurl upgrade to fix ci temporarly * upgrade caddy version * review * fix test * feat: add paying membership option (#714) * feat: add paying membership option * fix: ci * fix: phpstan + review * fix: eslint ci
This commit is contained in:
parent
a045a787a5
commit
b9a87a420b
52 changed files with 1023 additions and 221 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -51,6 +51,6 @@ jobs:
|
|||
- name: Twig Linter
|
||||
run: docker compose exec -T php ./vendor/bin/twigcs templates/ --exclude vendor
|
||||
- name: Install eslint
|
||||
run: docker run --rm -w "/usr/app" -v "${PWD}":/usr/app gmolaire/yarn yarn add eslint
|
||||
run: docker run --rm -w "/usr/app" -v "${PWD}":/usr/app gmolaire/yarn yarn add eslint@8.57.0
|
||||
- name: Run eslint on javascript files
|
||||
run: docker run --rm -w "/usr/app" -v "${PWD}":/usr/app gmolaire/yarn yarn lint
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
# https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
|
||||
ARG PHP_VERSION=8.1
|
||||
ARG CADDY_VERSION=2
|
||||
ARG CADDY_VERSION=2.8.4
|
||||
|
||||
# yarn build
|
||||
FROM gmolaire/yarn AS yarn_build
|
||||
|
|
@ -24,7 +24,7 @@ RUN yarn build
|
|||
FROM php:${PHP_VERSION}-fpm-alpine AS app_php
|
||||
|
||||
# needed for security update until base image is updated
|
||||
RUN apk upgrade libcurl curl openssl openssl-dev libressl libcrypto3 libssl3
|
||||
#RUN apk upgrade libcurl curl openssl openssl-dev libressl libcrypto3 libssl3
|
||||
|
||||
# Allow to use development versions of Symfony
|
||||
ARG STABILITY="stable"
|
||||
|
|
@ -188,7 +188,7 @@ RUN rm -f .env.local.php
|
|||
# Temporary fix for https://github.com/dunglas/mercure/issues/770
|
||||
# https://github.com/dunglas/symfony-docker/pull/407/files
|
||||
|
||||
FROM caddy:2.7-builder-alpine AS app_caddy_builder
|
||||
FROM caddy:2.8.4-builder-alpine AS app_caddy_builder
|
||||
|
||||
# RUN xcaddy build \
|
||||
# --with github.com/dunglas/mercure \
|
||||
|
|
@ -204,7 +204,7 @@ RUN xcaddy build \
|
|||
FROM caddy:${CADDY_VERSION} AS app_caddy
|
||||
|
||||
# needed for security update until base image is updated
|
||||
RUN apk upgrade libcurl curl openssl openssl-dev libressl libcrypto1.1 libssl1.1 libcrypto3 libssl3
|
||||
#RUN apk upgrade libcurl curl openssl openssl-dev libressl libcrypto1.1 libssl1.1 libcrypto3 libssl3
|
||||
|
||||
WORKDIR /srv/app
|
||||
|
||||
|
|
|
|||
8
assets/admin.js
Normal file
8
assets/admin.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { startStimulusApp } from '@symfony/stimulus-bridge'
|
||||
import AdminParentgroup from './controllers/admin_parentgroup_controller'
|
||||
|
||||
import './styles/admin.css'
|
||||
|
||||
const app = startStimulusApp()
|
||||
|
||||
app.register('admin-parentgroup', AdminParentgroup)
|
||||
77
assets/controllers/admin_parentgroup_controller.js
Normal file
77
assets/controllers/admin_parentgroup_controller.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Controller} from '@hotwired/stimulus'
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static targets = ['servicesEnabledField', 'parentField', 'idField']
|
||||
|
||||
parentFieldTargetConnected(element) {
|
||||
const observer = new MutationObserver( ( ) => {
|
||||
if (element.tomselect) {
|
||||
observer.disconnect()
|
||||
|
||||
const toggle = document.getElementById('Group_servicesEnabled')
|
||||
toggle.addEventListener('change', () => {
|
||||
this.updateParentOptions(toggle.checked, this.parentFieldTarget)
|
||||
})
|
||||
}
|
||||
})
|
||||
observer.observe(element, {attributes: true})
|
||||
}
|
||||
|
||||
async updateParentOptions(servicesEnabled, parentField) {
|
||||
const url = `/api/groups?services_enabled=${servicesEnabled}`
|
||||
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
return
|
||||
}
|
||||
const data = await response.json()
|
||||
const groups = data['hydra:member']
|
||||
|
||||
// Remove options
|
||||
parentField.tomselect.clearOptions()
|
||||
|
||||
// Populate with new options
|
||||
groups.map(group => {
|
||||
parentField.tomselect.addOption(new Option(group.name, group.id))
|
||||
})
|
||||
}
|
||||
|
||||
servicesEnabledFieldTargetConnected() {
|
||||
this.servicesEnabledFieldTarget.addEventListener('change', () => {
|
||||
if(!this.servicesEnabledFieldTarget.checked) {
|
||||
const params = new URLSearchParams(this.servicesEnabledFieldTarget.getAttribute('data-toggle-url'))
|
||||
this.disableServicesForChildGroups(params.get('entityId'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async disableServicesForChildGroups(groupId) {
|
||||
const url = `/api/groups/${groupId}/disable_child_services`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/merge-patch+json',
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
return
|
||||
}
|
||||
const data = await response.json()
|
||||
const groupChild = data.children
|
||||
|
||||
const groupChildId = groupChild.map(group => {
|
||||
return group.split('/')[3]
|
||||
})
|
||||
|
||||
const allToggles = document.querySelectorAll('[data-admin-parentgroup-target="servicesEnabledField"]')
|
||||
Array.from(allToggles).map(toggle => {
|
||||
const params = new URLSearchParams(toggle.getAttribute('data-toggle-url'))
|
||||
if(groupChildId.includes(params.get('entityId'))) {
|
||||
toggle.checked = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
36
assets/controllers/parentgroup_controller.js
Normal file
36
assets/controllers/parentgroup_controller.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Controller} from '@hotwired/stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['servicesEnabledField', 'parentField']
|
||||
|
||||
connect() {
|
||||
}
|
||||
|
||||
updateParentOptions() {
|
||||
const userId = this.servicesEnabledFieldTarget.getAttribute('data-user-id')
|
||||
const servicesEnabled = this.servicesEnabledFieldTarget.checked
|
||||
|
||||
const url = `/api/groups?user=${userId}&services_enabled=${servicesEnabled}`
|
||||
this.addGroupsWithEnabledServices(url)
|
||||
}
|
||||
|
||||
async addGroupsWithEnabledServices(url) {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
return
|
||||
}
|
||||
const data = await response.json()
|
||||
const groups = data['hydra:member']
|
||||
|
||||
// Remove options and set a default value
|
||||
Array.from(this.parentFieldTarget.options).map((group) => {
|
||||
this.parentFieldTarget.remove(group)
|
||||
})
|
||||
this.parentFieldTarget.add(new Option())
|
||||
|
||||
// Populate with new options
|
||||
groups.map(group => {
|
||||
this.parentFieldTarget.add(new Option(group.name, group.id))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { startStimulusApp } from '@symfony/stimulus-bridge'
|
||||
import { Application } from '@hotwired/stimulus'
|
||||
|
||||
import PasswordVisibility from 'stimulus-password-visibility'
|
||||
import Carousel from 'stimulus-carousel'
|
||||
|
|
@ -15,7 +14,5 @@ export const app = startStimulusApp(require.context(
|
|||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
|
||||
const application = Application.start()
|
||||
application.register('carousel', Carousel)
|
||||
application.register('password-visibility', PasswordVisibility)
|
||||
|
||||
app.register('carousel', Carousel)
|
||||
app.register('password-visibility', PasswordVisibility)
|
||||
|
|
|
|||
4
assets/styles/admin.css
Normal file
4
assets/styles/admin.css
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/* Remove the extra arrow added by Tom Select */
|
||||
.ts-wrapper.single .ts-control:after {
|
||||
display: none;
|
||||
}
|
||||
3
assets/styles/app.css
Normal file
3
assets/styles/app.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background-color: skyblue;
|
||||
}
|
||||
|
|
@ -78,6 +78,7 @@
|
|||
"symfony/runtime": "6.2.*",
|
||||
"symfony/security-bundle": "6.2.*",
|
||||
"symfony/serializer": "6.2.*",
|
||||
"symfony/stimulus-bundle": "^2.14",
|
||||
"symfony/translation-contracts": "^3.2",
|
||||
"symfony/twig-bridge": "6.2.*",
|
||||
"symfony/twig-bundle": "6.2.*",
|
||||
|
|
|
|||
73
composer.lock
generated
73
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3e1f8a3631e991b528d9f68758299e0e",
|
||||
"content-hash": "2b97a3771217b9b8a9f07e3f5ec9aec2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "alcohol/iso4217",
|
||||
|
|
@ -13247,6 +13247,75 @@
|
|||
],
|
||||
"time": "2023-03-01T10:32:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/stimulus-bundle",
|
||||
"version": "v2.14.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/stimulus-bundle.git",
|
||||
"reference": "f775f6e811215156bfe41e6be234272d0c27e02b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/f775f6e811215156bfe41e6be234272d0c27e02b",
|
||||
"reference": "f775f6e811215156bfe41e6be234272d0c27e02b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/config": "^5.4|^6.0|^7.0",
|
||||
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
|
||||
"symfony/deprecation-contracts": "^2.0|^3.0",
|
||||
"symfony/finder": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-kernel": "^5.4|^6.0|^7.0",
|
||||
"twig/twig": "^2.15.3|^3.4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/asset-mapper": "^6.3|^7.0",
|
||||
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
|
||||
"symfony/phpunit-bridge": "^5.4|^6.0|^7.0",
|
||||
"symfony/twig-bundle": "^5.4|^6.0|^7.0",
|
||||
"zenstruck/browser": "^1.4"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\UX\\StimulusBundle\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Integration with your Symfony app & Stimulus!",
|
||||
"keywords": [
|
||||
"symfony-ux"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/stimulus-bundle/tree/v2.14.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-07T20:26:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
"version": "v6.2.7",
|
||||
|
|
@ -18590,5 +18659,5 @@
|
|||
"ext-zip": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,5 @@ return [
|
|||
Payum\Bundle\PayumBundle\PayumBundle::class => ['all' => true],
|
||||
FOS\CKEditorBundle\FOSCKEditorBundle::class => ['all' => true],
|
||||
Misd\PhoneNumberBundle\MisdPhoneNumberBundle::class => ['all' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ App\Entity\Configuration:
|
|||
|
||||
features (extends configuration_template):
|
||||
configuration:
|
||||
services:
|
||||
servicesEnabled: true
|
||||
global:
|
||||
globalServicesEnabled: true
|
||||
globalPaidMembership: false
|
||||
notificationsSender:
|
||||
notificationsSenderEmail: info@example.com
|
||||
notificationsSenderName: Contact
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ App\Entity\Configuration:
|
|||
|
||||
features (extends configuration_template):
|
||||
configuration:
|
||||
services:
|
||||
servicesEnabled: true
|
||||
global:
|
||||
globalServicesEnabled: true
|
||||
globalPaidMembership: false
|
||||
notificationsSender:
|
||||
notificationsSenderEmail: info@example.com
|
||||
notificationsSenderName: Contact
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ App\Entity\GroupOffer:
|
|||
# Templates
|
||||
group_offer_group_1_template (template, extends group_offer_template):
|
||||
group: '@group_1'
|
||||
type: !php/enum App\Enum\Group\GroupOfferType::YEARLY
|
||||
type: !php/enum App\Enum\OfferType::YEARLY
|
||||
|
||||
group_offer_group_7_template (template, extends group_offer_template):
|
||||
group: '@group_7'
|
||||
type: !php/enum App\Enum\Group\GroupOfferType::YEARLY
|
||||
type: !php/enum App\Enum\OfferType::YEARLY
|
||||
|
||||
# Group 1
|
||||
group_offer_group_1_10 (extends group_offer_group_1_template):
|
||||
|
|
|
|||
11
fixtures/test/platform_offer.yaml
Normal file
11
fixtures/test/platform_offer.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
App\Entity\PlatformOffer:
|
||||
platform_offer_1:
|
||||
id: <uuid('9040b3fb-8a01-4bbf-a228-ca9f90db5034')>
|
||||
name: Lorem ipsum
|
||||
price: 2000
|
||||
type: !php/enum App\Enum\OfferType::YEARLY
|
||||
|
||||
platform_offer_2:
|
||||
name: Aliquet risus
|
||||
price: 200
|
||||
type: !php/enum App\Enum\OfferType::MONTHLY
|
||||
|
|
@ -19,6 +19,7 @@ use App\Form\Type\Security\GroupInvitationFormType;
|
|||
use App\Helper\CsvExporter;
|
||||
use App\Message\Command\Group\CreateGroupInvitationMessage;
|
||||
use App\MessageBus\CommandBus;
|
||||
use App\Repository\ConfigurationRepository;
|
||||
use App\Security\Checker\AuthorizationChecker;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
|
||||
|
|
@ -66,6 +67,7 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
private readonly TranslatorInterface $translator,
|
||||
private readonly FilterFactory $filterFactory,
|
||||
private readonly SluggerInterface $slugger,
|
||||
private readonly ConfigurationRepository $configurationRepository
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +77,7 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
->setEntityLabelInPlural('groups')
|
||||
->setSearchFields(['name', 'description'])
|
||||
->setDefaultSort(['id' => 'ASC'])
|
||||
->overrideTemplate('crud/field/boolean', 'admin/field/services_enabled.html.twig')
|
||||
;
|
||||
}
|
||||
|
||||
|
|
@ -230,8 +233,24 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
->setFormTypeOption('class', GroupMembership::class)
|
||||
->setChoices(GroupMembership::getAsArray());
|
||||
|
||||
if ($this->configurationRepository->getInstanceConfigurationOrCreate()->getServicesEnabled()) {
|
||||
$servicesEnabledField = BooleanField::new('servicesEnabled')
|
||||
->renderAsSwitch()
|
||||
->setFormTypeOption('attr', [
|
||||
'data-controller' => 'admin-parentgroup',
|
||||
'data-admin-parentgroup-target' => 'servicesEnabledField',
|
||||
])
|
||||
->addWebpackEncoreEntries('admin');
|
||||
}
|
||||
|
||||
$parentField = AssociationField::new('parent')
|
||||
->setRequired(false);
|
||||
->setRequired(false)
|
||||
->addWebpackEncoreEntries('admin')
|
||||
->setFormTypeOption('attr', [
|
||||
'data-controller' => 'admin-parentgroup',
|
||||
'data-admin-parentgroup-target' => 'parentField',
|
||||
])
|
||||
;
|
||||
$childrenField = AssociationField::new('children');
|
||||
$usersField = AssociationField::new('userGroups')
|
||||
->setTemplatePath('admin/group/user_groups_field.html.twig');
|
||||
|
|
@ -247,14 +266,20 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
$panels = $this->getPanels();
|
||||
|
||||
if ($pageName === Crud::PAGE_INDEX) {
|
||||
return [$nameField, $typeField, $parentField, $membershipField, $usersField, $createdAt, $updatedAt];
|
||||
$fields = [$nameField, $typeField, $parentField, $membershipField, $usersField, $createdAt, $updatedAt];
|
||||
|
||||
if ($this->configurationRepository->getInstanceConfigurationOrCreate()->getServicesEnabled()) {
|
||||
array_splice($fields, 3, 0, [$servicesEnabledField]);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
if ($pageName === Crud::PAGE_NEW || $pageName === Crud::PAGE_EDIT) {
|
||||
$typeField->setChoices(GroupType::cases());
|
||||
$membershipField->setChoices(GroupMembership::cases());
|
||||
|
||||
return [
|
||||
$fields = [
|
||||
$nameField,
|
||||
$typeField,
|
||||
$membershipField,
|
||||
|
|
@ -264,11 +289,17 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
$invitationByAdminField,
|
||||
$membershipField,
|
||||
];
|
||||
|
||||
if ($this->configurationRepository->getInstanceConfigurationOrCreate()->getServicesEnabled()) {
|
||||
array_splice($fields, 3, 0, [$servicesEnabledField]);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
// show
|
||||
|
||||
return [
|
||||
$fields = [
|
||||
$panels['information'],
|
||||
$nameField,
|
||||
$parentField,
|
||||
|
|
@ -283,6 +314,12 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
$updatedAt,
|
||||
$createdAt,
|
||||
];
|
||||
|
||||
if ($this->configurationRepository->getInstanceConfigurationOrCreate()->getServicesEnabled()) {
|
||||
array_splice($fields, 2, 0, [$servicesEnabledField]);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -309,7 +346,7 @@ final class GroupCrudController extends AbstractCrudController implements GroupA
|
|||
}
|
||||
|
||||
/**
|
||||
* For now we export exactly what we see in the list to avoid seurity problems.
|
||||
* For now we export exactly what we see in the list to avoid security problems.
|
||||
*/
|
||||
public function export(AdminContext $context): Response
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ use App\EasyAdmin\Field\FieldTrait;
|
|||
use App\EasyAdmin\Filter\EnumFilter;
|
||||
use App\EasyAdmin\Filter\UserGroup\MyGroupFilter;
|
||||
use App\EasyAdmin\Filter\UuidFilter;
|
||||
use App\EasyAdmin\Form\Type\GroupOfferTypeType;
|
||||
use App\EasyAdmin\Form\Type\OfferTypeType;
|
||||
use App\Entity\GroupOffer;
|
||||
use App\Entity\User;
|
||||
use App\Enum\Group\GroupMembership;
|
||||
use App\Enum\Group\GroupOfferType;
|
||||
use App\Enum\Group\UserMembership;
|
||||
use App\Enum\OfferType;
|
||||
use App\Security\Checker\AuthorizationChecker;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
|
||||
|
|
@ -65,7 +65,7 @@ final class GroupOfferCrudController extends AbstractCrudController implements G
|
|||
return $filters
|
||||
->add(UuidFilter::new('id'))
|
||||
->add(MyGroupFilter::new('group'))
|
||||
->add(EnumFilter::new('membership', GroupOfferTypeType::class))
|
||||
->add(EnumFilter::new('membership', OfferTypeType::class))
|
||||
->add('name')
|
||||
->add('active')
|
||||
;
|
||||
|
|
@ -129,8 +129,8 @@ final class GroupOfferCrudController extends AbstractCrudController implements G
|
|||
$nameField = TextField::new('name');
|
||||
$typeField = ChoiceField::new('type')
|
||||
->setFormType(EnumType::class)
|
||||
->setFormTypeOption('class', GroupOfferType::class)
|
||||
->setChoices(GroupOfferType::getAsArray());
|
||||
->setFormTypeOption('class', OfferType::class)
|
||||
->setChoices(OfferType::getAsArray());
|
||||
|
||||
$priceField = MoneyField::new('price')
|
||||
->setCurrencyPropertyPath('currency')
|
||||
|
|
@ -149,7 +149,7 @@ final class GroupOfferCrudController extends AbstractCrudController implements G
|
|||
}
|
||||
|
||||
if ($pageName === Crud::PAGE_NEW || $pageName === Crud::PAGE_EDIT) {
|
||||
$typeField->setChoices(GroupOfferType::cases());
|
||||
$typeField->setChoices(OfferType::cases());
|
||||
|
||||
return [
|
||||
$groupField,
|
||||
|
|
|
|||
121
src/Controller/Admin/PlatformOfferCrudController.php
Normal file
121
src/Controller/Admin/PlatformOfferCrudController.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Controller\FlashTrait;
|
||||
use App\Controller\i18nTrait;
|
||||
use App\EasyAdmin\Field\FieldTrait;
|
||||
use App\EasyAdmin\Filter\EnumFilter;
|
||||
use App\EasyAdmin\Filter\UuidFilter;
|
||||
use App\EasyAdmin\Form\Type\OfferTypeType;
|
||||
use App\Entity\PlatformOffer;
|
||||
use App\Enum\OfferType;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\CurrencyField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
|
||||
final class PlatformOfferCrudController extends AbstractCrudController implements AdminSecuredCrudControllerInterface
|
||||
{
|
||||
use FlashTrait;
|
||||
use FieldTrait;
|
||||
use i18nTrait;
|
||||
|
||||
public function configureCrud(Crud $crud): Crud
|
||||
{
|
||||
return $crud
|
||||
->setEntityLabelInPlural('platform_offers')
|
||||
->setSearchFields(['name'])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureFilters(Filters $filters): Filters
|
||||
{
|
||||
return $filters
|
||||
->add(UuidFilter::new('id'))
|
||||
->add(EnumFilter::new('type', OfferTypeType::class))
|
||||
->add('name')
|
||||
->add('active')
|
||||
;
|
||||
}
|
||||
|
||||
public function configureActions(Actions $actions): Actions
|
||||
{
|
||||
return $actions
|
||||
->add(Crud::PAGE_INDEX, Action::DETAIL)
|
||||
->add(Crud::PAGE_EDIT, Action::DETAIL)
|
||||
->add(Crud::PAGE_EDIT, Action::INDEX)
|
||||
;
|
||||
}
|
||||
|
||||
public static function getEntityFqcn(): string
|
||||
{
|
||||
return PlatformOffer::class;
|
||||
}
|
||||
|
||||
public function configureFields(string $pageName): iterable
|
||||
{
|
||||
$idFIeld = IdField::new('id')
|
||||
->setLabel('id')
|
||||
->hideOnForm();
|
||||
|
||||
$nameField = TextField::new('name');
|
||||
$typeField = ChoiceField::new('type')
|
||||
->setFormType(EnumType::class)
|
||||
->setFormTypeOption('class', OfferType::class)
|
||||
->setChoices(OfferType::getAsArray());
|
||||
|
||||
$priceField = MoneyField::new('price')
|
||||
->setCurrencyPropertyPath('currency')
|
||||
->setStoredAsCents();
|
||||
$currencyField = CurrencyField::new('currency');
|
||||
|
||||
$activeField = BooleanField::new('active')
|
||||
->setTemplatePath('easy_admin/field/boolean.html.twig')
|
||||
;
|
||||
$createdAtField = DateTimeField::new('createdAt');
|
||||
$updatedAtField = DateTimeField::new('updatedAt');
|
||||
|
||||
$panels = $this->getPanels();
|
||||
if ($pageName === Crud::PAGE_INDEX) {
|
||||
return [$nameField, $typeField, $priceField, $activeField, $createdAtField, $updatedAtField];
|
||||
}
|
||||
|
||||
if ($pageName === Crud::PAGE_NEW || $pageName === Crud::PAGE_EDIT) {
|
||||
$typeField->setChoices(OfferType::cases());
|
||||
|
||||
return [
|
||||
$nameField,
|
||||
$typeField,
|
||||
$priceField,
|
||||
$currencyField,
|
||||
$activeField,
|
||||
];
|
||||
}
|
||||
|
||||
// show
|
||||
return [
|
||||
$panels['information'],
|
||||
$nameField,
|
||||
$typeField,
|
||||
$priceField,
|
||||
$currencyField,
|
||||
|
||||
$panels['tech_information'],
|
||||
$idFIeld,
|
||||
$updatedAtField,
|
||||
$createdAtField,
|
||||
];
|
||||
}
|
||||
}
|
||||
148
src/Doctrine/Behavior/AbstractOfferEntity.php
Normal file
148
src/Doctrine/Behavior/AbstractOfferEntity.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Doctrine\Behavior;
|
||||
|
||||
use App\Enum\OfferType;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\MappedSuperclass]
|
||||
abstract class AbstractOfferEntity implements \Stringable
|
||||
{
|
||||
use TimestampableEntity;
|
||||
|
||||
final public const DEFAULT_CURRENCY = 'EUR';
|
||||
|
||||
/**
|
||||
* Generates a V6 uuid.
|
||||
*/
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'uuid', unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
|
||||
protected Uuid $id;
|
||||
|
||||
/**
|
||||
* Short name of the offer.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: false)]
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(max: 255)]
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* Type of offer.
|
||||
*/
|
||||
#[ORM\Column(name: 'type', type: 'string', nullable: false, enumType: OfferType::class)]
|
||||
#[Assert\NotBlank]
|
||||
protected OfferType $type;
|
||||
|
||||
/**
|
||||
* Price, we stored the amount multiplied by 100 so we can use an integer for
|
||||
* this property.
|
||||
*/
|
||||
#[ORM\Column(type: Types::INTEGER, nullable: false)]
|
||||
protected int $price;
|
||||
|
||||
/**
|
||||
* Associated currency for the price property.
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/ISO_4217
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, nullable: false)]
|
||||
protected string $currency = self::DEFAULT_CURRENCY;
|
||||
|
||||
/**
|
||||
* If the offer is visible on the front site. Can be used to deactivate offers
|
||||
* for some time.
|
||||
*/
|
||||
#[ORM\Column(type: 'boolean', nullable: false)]
|
||||
protected bool $active = true;
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->name.' ('.$this->type->value.')';
|
||||
}
|
||||
|
||||
public function getId(): Uuid
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(Uuid $uuid): self
|
||||
{
|
||||
$this->id = $uuid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): OfferType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(OfferType $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPrice(int $price): self
|
||||
{
|
||||
$this->price = $price;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPrice(): int
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
public function getActualPrice(): int
|
||||
{
|
||||
return $this->price / 100;
|
||||
}
|
||||
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
public function setCurrency(string $currency): self
|
||||
{
|
||||
$this->currency = $currency;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): self
|
||||
{
|
||||
$this->active = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,20 +5,20 @@ declare(strict_types=1);
|
|||
namespace App\EasyAdmin\Form\Type;
|
||||
|
||||
use App\Controller\Admin\DashboardController;
|
||||
use App\Enum\Group\GroupOfferType;
|
||||
use App\Enum\OfferType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Form type for the GroupOfferType enumeration.
|
||||
* Form type for the OfferType enumeration.
|
||||
*/
|
||||
class GroupOfferTypeType extends AbstractType
|
||||
class OfferTypeType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'choices' => GroupOfferType::getAsArray(),
|
||||
'choices' => OfferType::getAsArray(),
|
||||
'translation_domain' => DashboardController::DOMAIN,
|
||||
]);
|
||||
}
|
||||
|
|
@ -78,19 +78,26 @@ class Configuration
|
|||
/**
|
||||
* @return bool[]
|
||||
*/
|
||||
public function getServices(): array
|
||||
public function getGlobals(): array
|
||||
{
|
||||
/** @var array<string, bool> $services */
|
||||
$services = $this->configuration['services'] ?? [];
|
||||
/** @var array<string, bool> $globals */
|
||||
$globals = $this->configuration['global'] ?? [];
|
||||
|
||||
return $services;
|
||||
return $globals;
|
||||
}
|
||||
|
||||
public function getServicesEnabled(): bool
|
||||
{
|
||||
$services = $this->getServices();
|
||||
$globals = $this->getGlobals();
|
||||
|
||||
return $services['servicesEnabled'];
|
||||
return $globals['globalServicesEnabled'];
|
||||
}
|
||||
|
||||
public function getPaidMembership(): bool
|
||||
{
|
||||
$globals = $this->getGlobals();
|
||||
|
||||
return $globals['globalPaidMembership'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,10 +7,15 @@ namespace App\Entity;
|
|||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use App\Doctrine\Behavior\TimestampableEntity;
|
||||
use App\Enum\Group\GroupMembership;
|
||||
use App\Enum\Group\GroupType;
|
||||
use App\Repository\GroupRepository;
|
||||
use App\State\GroupsProvider;
|
||||
use App\State\Processor\GroupChildServicesEnabledProcessor;
|
||||
use App\Validator as AppAssert;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
|
@ -26,6 +31,16 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
#[ORM\Index(columns: ['type'])]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name'])]
|
||||
#[AppAssert\Constraints\Group\GroupParentNotSelf]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new GetCollection(provider: GroupsProvider::class),
|
||||
new Patch(
|
||||
uriTemplate: '/groups/{id}/disable_child_services',
|
||||
input: false,
|
||||
processor: GroupChildServicesEnabledProcessor::class
|
||||
),
|
||||
]
|
||||
)]
|
||||
class Group implements \Stringable
|
||||
{
|
||||
use TimestampableEntity;
|
||||
|
|
@ -123,6 +138,9 @@ class Group implements \Stringable
|
|||
#[ORM\ManyToMany(targetEntity: Product::class, mappedBy: 'groups')]
|
||||
private Collection $products;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $servicesEnabled = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
|
|
@ -340,6 +358,16 @@ class Group implements \Stringable
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getServicesEnabled(): bool
|
||||
{
|
||||
return $this->servicesEnabled;
|
||||
}
|
||||
|
||||
public function setServicesEnabled(bool $servicesEnabled): void
|
||||
{
|
||||
$this->servicesEnabled = $servicesEnabled;
|
||||
}
|
||||
|
||||
// End of basic 'etters ----------------------------------------------------
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,33 +4,16 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Doctrine\Behavior\TimestampableEntity;
|
||||
use App\Enum\Group\GroupOfferType;
|
||||
use App\Doctrine\Behavior\AbstractOfferEntity;
|
||||
use App\Repository\GroupOfferRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: GroupOfferRepository::class)]
|
||||
#[ORM\Table(name: 'group_offer')]
|
||||
#[ORM\Index(columns: ['type'])]
|
||||
class GroupOffer implements \Stringable
|
||||
class GroupOffer extends AbstractOfferEntity
|
||||
{
|
||||
use TimestampableEntity;
|
||||
|
||||
final public const DEFAULT_CURRENCY = 'EUR';
|
||||
|
||||
/**
|
||||
* Generates a V6 uuid.
|
||||
*/
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'uuid', unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
|
||||
private Uuid $id;
|
||||
|
||||
/**
|
||||
* Related group.
|
||||
*/
|
||||
|
|
@ -39,134 +22,15 @@ class GroupOffer implements \Stringable
|
|||
#[ORM\OrderBy(['createdAt' => 'ASC'])]
|
||||
private Group $group;
|
||||
|
||||
/**
|
||||
* Short name of the offer.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: false)]
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(max: 255)]
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* Type of offer.
|
||||
*/
|
||||
#[ORM\Column(name: 'type', type: 'string', nullable: false, enumType: GroupOfferType::class)]
|
||||
#[Assert\NotBlank]
|
||||
protected GroupOfferType $type;
|
||||
|
||||
/**
|
||||
* Price, we stored the amount multiplied by 100 so we can use an integer for
|
||||
* this property.
|
||||
*/
|
||||
#[ORM\Column(type: Types::INTEGER, nullable: false)]
|
||||
protected int $price;
|
||||
|
||||
/**
|
||||
* Associated currency for the price property.
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/ISO_4217
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, nullable: false)]
|
||||
protected string $currency = self::DEFAULT_CURRENCY;
|
||||
|
||||
/**
|
||||
* If the offer is visible on the front site. Can be use to deactivate offers
|
||||
* for some time.
|
||||
*/
|
||||
#[ORM\Column(type: 'boolean', nullable: false)]
|
||||
protected bool $active = true;
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->name.' ('.$this->type->value.')';
|
||||
}
|
||||
|
||||
public function getId(): Uuid
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(Uuid $uuid): self
|
||||
{
|
||||
$this->id = $uuid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGroup(): Group
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
|
||||
public function setGroup(Group $group): GroupOffer
|
||||
public function setGroup(Group $group): self
|
||||
{
|
||||
$this->group = $group;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): GroupOffer
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): GroupOfferType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(GroupOfferType $type): GroupOffer
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPrice(int $price): self
|
||||
{
|
||||
$this->price = $price;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPrice(): int
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
public function getActualPrice(): int
|
||||
{
|
||||
return $this->price / 100;
|
||||
}
|
||||
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
public function setCurrency(string $currency): self
|
||||
{
|
||||
$this->currency = $currency;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): GroupOffer
|
||||
{
|
||||
$this->active = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
src/Entity/PlatformOffer.php
Normal file
16
src/Entity/PlatformOffer.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Doctrine\Behavior\AbstractOfferEntity;
|
||||
use App\Repository\PlatformOfferRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PlatformOfferRepository::class)]
|
||||
#[ORM\Table(name: 'platform_offer')]
|
||||
#[ORM\Index(columns: ['type'])]
|
||||
class PlatformOffer extends AbstractOfferEntity
|
||||
{
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
operations: [
|
||||
new Patch(
|
||||
uriTemplate: '/product/{id}/switchStatus',
|
||||
openapiContext: ['summary' => 'Swicth the status of the product'],
|
||||
openapiContext: ['summary' => 'Switch the status of the product'],
|
||||
normalizationContext: ['groups' => [ProductSwitchProcessor::class]],
|
||||
security: "is_granted('".ProductVoter::EDIT."', object)",
|
||||
input: false,
|
||||
|
|
|
|||
|
|
@ -770,15 +770,23 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, ImageIn
|
|||
*
|
||||
* @return Collection<int,Group>
|
||||
*/
|
||||
public function getMyGroupsAsAdmin(): Collection
|
||||
public function getMyGroupsAsAdmin(bool $enabledServices = false): Collection
|
||||
{
|
||||
$adminUserGroups = $this->userGroups->filter(
|
||||
static fn (UserGroup $userGroup) => $userGroup->getMembership()->isAdmin() || $userGroup->isMainAdminAccount()
|
||||
);
|
||||
|
||||
return new ArrayCollection(
|
||||
$groups = new ArrayCollection(
|
||||
array_map(static fn (UserGroup $userGroup) => $userGroup->getGroup(), $adminUserGroups->toArray())
|
||||
);
|
||||
|
||||
if ($enabledServices) {
|
||||
return $groups->filter(
|
||||
static fn (Group $group) => $group->getServicesEnabled()
|
||||
);
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use App\Doctrine\Behavior\TimestampableEntity;
|
||||
use App\Enum\Group\UserMembership;
|
||||
use App\Repository\UserGroupRepository;
|
||||
|
|
@ -16,6 +17,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
|
||||
#[ORM\Entity(repositoryClass: UserGroupRepository::class)]
|
||||
#[ORM\UniqueConstraint(columns: ['user', 'group'])]
|
||||
#[ApiResource]
|
||||
class UserGroup
|
||||
{
|
||||
use TimestampableEntity;
|
||||
|
|
|
|||
|
|
@ -2,22 +2,20 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enum\Group;
|
||||
namespace App\Enum;
|
||||
|
||||
use App\Enum\AsArrayTrait;
|
||||
|
||||
enum GroupOfferType: string
|
||||
enum OfferType: string
|
||||
{
|
||||
use AsArrayTrait;
|
||||
|
||||
// The user only to pay once to access the group. In his case the end date is
|
||||
// The user only to pay once to access the group/platform. In his case the end date is
|
||||
// not set and the membership is valid until it is deleted or a end date is
|
||||
// set. The end date can always be set manually in case of a problem.
|
||||
case ONESHOT = 'oneshot';
|
||||
|
||||
// Monthly subscription. The membership is valid 1 month and the user has to
|
||||
// renew it once the end date is over. This can be useful when a user when to
|
||||
// try a group on the short period before taking a longer subscription.
|
||||
// try a group/platform on the short period before taking a longer subscription.
|
||||
case MONTHLY = 'monthly';
|
||||
|
||||
// Subscription valid for one year. An email will be send a few days before
|
||||
|
|
@ -25,13 +25,20 @@ final class ParametersFormType extends AbstractType
|
|||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('servicesEnabled', CheckboxType::class, [
|
||||
->add('globalServicesEnabled', CheckboxType::class, [
|
||||
'label' => 'parameter.services',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline checkbox-switch',
|
||||
],
|
||||
])
|
||||
|
||||
->add('globalPaidMembership', CheckboxType::class, [
|
||||
'label' => 'parameter.paid_membership',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline checkbox-switch',
|
||||
],
|
||||
])
|
||||
|
||||
->add('notificationsSenderEmail', EmailType::class, [
|
||||
'label' => 'parameter.mail',
|
||||
'label_attr' => ['class' => 'col-sm-2 col-form-label'],
|
||||
|
|
|
|||
|
|
@ -4,23 +4,30 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\Type\Group;
|
||||
|
||||
use App\Entity\Configuration;
|
||||
use App\Entity\Group;
|
||||
use App\Entity\User;
|
||||
use App\Enum\Group\GroupMembership;
|
||||
use App\Enum\Group\GroupType;
|
||||
use App\Repository\ConfigurationRepository;
|
||||
use App\Repository\GroupRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class CreateGroupFormType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Security $security,
|
||||
private readonly ConfigurationRepository $configurationRepository,
|
||||
private readonly GroupRepository $groupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -28,6 +35,8 @@ class CreateGroupFormType extends AbstractType
|
|||
{
|
||||
/** @var User $user */
|
||||
$user = $this->security->getUser();
|
||||
$configuration = $this->configurationRepository->getInstanceConfiguration();
|
||||
$myGroupsWithDisabledServices = $this->groupRepository->getGroupsByEnabledServices(false, $user);
|
||||
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
|
|
@ -43,7 +52,22 @@ class CreateGroupFormType extends AbstractType
|
|||
'label_attr' => ['class' => 'fs-6 text-black'],
|
||||
'expanded' => true,
|
||||
'choice_label' => 'transKey',
|
||||
])
|
||||
]);
|
||||
Assert::isInstanceOf($configuration, Configuration::class);
|
||||
if ($configuration->getServicesEnabled()) {
|
||||
$builder
|
||||
->add('servicesEnabled', CheckboxType::class, [
|
||||
'label' => 'templates.pages.group.create.form.servicesEnabled',
|
||||
'label_attr' => ['class' => 'fs-6 text-black mb-3 switch-custom'],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'data-action' => 'click->parentgroup#updateParentOptions',
|
||||
'data-parentgroup-target' => 'servicesEnabledField',
|
||||
'data-user-id' => $user->getId(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
$builder
|
||||
->add('membership', EnumType::class, [
|
||||
'class' => GroupMembership::class,
|
||||
'label' => 'templates.pages.group.create.form.membership',
|
||||
|
|
@ -55,15 +79,17 @@ class CreateGroupFormType extends AbstractType
|
|||
'attr' => ['class' => 'btn btn-primary btn-sm d-grid col-12 my-5'],
|
||||
]);
|
||||
|
||||
$myGroups = $user->getMyGroupsAsAdmin();
|
||||
if (!$myGroups->isEmpty()) {
|
||||
if ([] === $myGroupsWithDisabledServices) {
|
||||
$builder
|
||||
->add('parent', EntityType::class, [
|
||||
'class' => Group::class,
|
||||
'choices' => $myGroups,
|
||||
'choices' => $myGroupsWithDisabledServices,
|
||||
'label' => 'templates.pages.group.create.form.subgroup',
|
||||
'label_attr' => ['class' => 'fs-6 text-black'],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'data-parentgroup-target' => 'parentField',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@ final class ParametersFormCommand extends AbstractFormCommand
|
|||
final public const ONLY_ADMIN = 'only_admin';
|
||||
final public const ALL = 'all';
|
||||
|
||||
// services section —————————————————————————————————————————————
|
||||
// global section —————————————————————————————————————————————
|
||||
#[Assert\Type('bool')]
|
||||
public bool $servicesEnabled = true;
|
||||
public bool $globalServicesEnabled = true;
|
||||
|
||||
#[Assert\Type('bool')]
|
||||
public bool $globalPaidMembership = false;
|
||||
|
||||
// notificationsSender section —————————————————————————————————————————————
|
||||
#[Assert\Email()]
|
||||
|
|
@ -59,7 +62,7 @@ final class ParametersFormCommand extends AbstractFormCommand
|
|||
protected function getSections(): array
|
||||
{
|
||||
return [
|
||||
'services',
|
||||
'global',
|
||||
'notificationsSender',
|
||||
'contact',
|
||||
'groups',
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@ namespace App\MessageHandler\Command\Admin;
|
|||
|
||||
use App\Message\Command\Admin\ParametersFormCommand;
|
||||
use App\Repository\ConfigurationRepository;
|
||||
use App\Repository\GroupRepository;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler]
|
||||
final class ParametersFormCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConfigurationRepository $configurationRepository
|
||||
private readonly ConfigurationRepository $configurationRepository,
|
||||
private readonly GroupRepository $groupRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -24,5 +26,10 @@ final class ParametersFormCommandHandler
|
|||
$configuration = $this->configurationRepository->getInstanceConfigurationOrCreate();
|
||||
$configuration->setConfiguration($message->toJsonArray());
|
||||
$this->configurationRepository->save($configuration, true);
|
||||
|
||||
if (!$configuration->getServicesEnabled()) {
|
||||
$groups = $this->groupRepository->findAll();
|
||||
$this->groupRepository->disableServicesForAllGroups($groups);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,13 +61,13 @@ final class ConfigurationRepository extends ServiceEntityRepository
|
|||
|
||||
public function getServicesParameter(): bool
|
||||
{
|
||||
/** @var array{configuration: array{ services: array{ servicesEnabled: bool }}} $config */
|
||||
/** @var array{configuration: array{ global: array{ globalServicesEnabled: bool }}} $config */
|
||||
$config = $this
|
||||
->createQueryBuilder('c')
|
||||
->select('c.configuration')
|
||||
->setMaxResults(1)
|
||||
->getQuery()->getOneOrNullResult();
|
||||
|
||||
return $config['configuration']['services']['servicesEnabled'];
|
||||
return $config['configuration']['global']['globalServicesEnabled'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use App\Entity\Group;
|
|||
use App\Entity\User;
|
||||
use App\Entity\UserGroup;
|
||||
use App\Enum\Group\GroupType;
|
||||
use App\Enum\Group\UserMembership;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
|
@ -87,4 +88,51 @@ final class GroupRepository extends ServiceEntityRepository
|
|||
->andWhere('ug.user = :user')
|
||||
->setParameter('user', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Group[]
|
||||
*/
|
||||
public function getGroupsByEnabledServices(bool $servicesEnabled, ?User $user = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('g')
|
||||
->andWhere('g.servicesEnabled = :servicesEnabled')
|
||||
->setParameter('servicesEnabled', $servicesEnabled);
|
||||
|
||||
if ($user instanceof User) {
|
||||
$qb
|
||||
->leftJoin('g.userGroups', 'gu')
|
||||
->andWhere('gu.user = :user')
|
||||
->andWhere('gu.mainAdminAccount = :mainAdminAccount OR gu.membership = :membership')
|
||||
->setParameter('user', $user)
|
||||
->setParameter('mainAdminAccount', true)
|
||||
->setParameter('membership', UserMembership::ADMIN);
|
||||
}
|
||||
|
||||
/** @var Group[] */
|
||||
return $qb
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Group[] $groups
|
||||
*/
|
||||
public function disableServicesForAllGroups(array $groups): void
|
||||
{
|
||||
foreach ($groups as $group) {
|
||||
$group->setServicesEnabled(false);
|
||||
$this->getEntityManager()->persist($group);
|
||||
}
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
|
||||
public function disableServicesForChildGroup(Group $group): void
|
||||
{
|
||||
/** @var Group $child */
|
||||
foreach ($group->getChildren() as $child) {
|
||||
$child->setServicesEnabled(false);
|
||||
$this->getEntityManager()->persist($child);
|
||||
}
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
src/Repository/PlatformOfferRepository.php
Normal file
35
src/Repository/PlatformOfferRepository.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\PlatformOffer;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<PlatformOffer>
|
||||
*
|
||||
* @method PlatformOffer|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method PlatformOffer|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method PlatformOffer[] findAll()
|
||||
* @method PlatformOffer[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class PlatformOfferRepository extends ServiceEntityRepository
|
||||
{
|
||||
private const ENTITY_CLASS = PlatformOffer::class;
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, self::ENTITY_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object or throws an exception if not found.
|
||||
*/
|
||||
public function get(mixed $id, int|null $lockMode = null, int|null $lockVersion = null): PlatformOffer
|
||||
{
|
||||
return $this->find($id, $lockMode, $lockVersion) ?? throw new \LogicException('Platform offer not found.');
|
||||
}
|
||||
}
|
||||
34
src/State/GroupsProvider.php
Normal file
34
src/State/GroupsProvider.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Entity\Group;
|
||||
use App\Repository\GroupRepository;
|
||||
use App\Repository\UserRepository;
|
||||
|
||||
/**
|
||||
* @implements ProviderInterface<Group>
|
||||
*/
|
||||
class GroupsProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
readonly private GroupRepository $groupRepository,
|
||||
readonly private UserRepository $userRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null // @phpstan-ignore-line
|
||||
{
|
||||
if (isset($context['filters']['user'])) {
|
||||
$user = $this->userRepository->find($context['filters']['user']);
|
||||
|
||||
return $this->groupRepository->getGroupsByEnabledServices($context['filters']['services_enabled'] === 'true', $user);
|
||||
}
|
||||
|
||||
return $this->groupRepository->getGroupsByEnabledServices($context['filters']['services_enabled'] === 'true');
|
||||
}
|
||||
}
|
||||
31
src/State/Processor/GroupChildServicesEnabledProcessor.php
Normal file
31
src/State/Processor/GroupChildServicesEnabledProcessor.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Group;
|
||||
use App\Repository\GroupRepository;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class GroupChildServicesEnabledProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GroupRepository $groupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $uriVariables
|
||||
* @param array<mixed> $context
|
||||
*/
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Group
|
||||
{
|
||||
Assert::isInstanceOf($data, Group::class);
|
||||
$this->groupRepository->disableServicesForChildGroup($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
18
symfony.lock
18
symfony.lock
|
|
@ -185,6 +185,15 @@
|
|||
"config/packages/http_discovery.yaml"
|
||||
]
|
||||
},
|
||||
"phpstan/phpstan": {
|
||||
"version": "1.11",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||
}
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"version": "9.5",
|
||||
"recipe": {
|
||||
|
|
@ -444,6 +453,15 @@
|
|||
"config/packages/security.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/stimulus-bundle": {
|
||||
"version": "2.18",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.8",
|
||||
"ref": "9e33a8a3794b603fb4be6c04ee5ecab901ce549e"
|
||||
}
|
||||
},
|
||||
"symfony/translation": {
|
||||
"version": "6.2",
|
||||
"recipe": {
|
||||
|
|
|
|||
17
templates/admin/field/services_enabled.html.twig
Normal file
17
templates/admin/field/services_enabled.html.twig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
|
||||
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
|
||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
|
||||
{% trans_default_domain 'EasyAdminBundle' %}
|
||||
|
||||
{% if ea.crud.currentAction == 'detail' or not field.customOptions.get('renderAsSwitch') %}
|
||||
<span class="badge {{ field.value == true ? 'badge-boolean-true' : 'badge-boolean-false' }}">
|
||||
{{ (field.value == true ? 'label.true' : 'label.false')|trans }}
|
||||
</span>
|
||||
{% else %}
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="{{ field.uniqueId }}" {{ field.value == true ? 'checked' }}
|
||||
data-toggle-url="{{ field.customOptions.get('toggleUrl') }}"
|
||||
{{ field.formTypeOption('disabled') == true ? 'disabled' }} autocomplete="off" data-controller="admin-parentgroup" data-admin-parentgroup-target="servicesEnabledField">
|
||||
<label class="form-check-label" for="{{ field.uniqueId }}"></label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -14,12 +14,28 @@
|
|||
{% block main %}
|
||||
{{ form_start(form) }}
|
||||
<div class="row mb-lg-5">
|
||||
<h2 class="h3 fw-bold mt-3">{{ 'parameters.services.h2'|trans }}</h2>
|
||||
<h2 class="h3 fw-bold mt-3">{{ 'parameters.global.h2'|trans }}</h2>
|
||||
<hr/>
|
||||
|
||||
{{ form_widget(form.servicesEnabled) }}
|
||||
<h2 class="h5 fw-bold my-3">{{ 'parameters.services.h3'|trans }}</h2>
|
||||
|
||||
<h2 class="h3 fw-bold mt-3">{{ 'parameters.senders.h2'|trans }}</h2>
|
||||
{{ form_widget(form.globalServicesEnabled) }}
|
||||
|
||||
<h2 class="h5 fw-bold my-3">{{ 'parameters.paid_membership.h3'|trans }}</h2>
|
||||
|
||||
{{ form_widget(form.globalPaidMembership) }}
|
||||
|
||||
<div class="col-2 my-3 mx-4">
|
||||
<a
|
||||
type="button"
|
||||
class="btn btn-primary "
|
||||
href="{{ ea_url().setController('App\\Controller\\Admin\\PlatformOfferCrudController').setAction('index') }}"
|
||||
>
|
||||
{{ 'parameter.set_price'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 class="h3 fw-bold mt-3 mb-1">{{ 'parameters.senders.h2'|trans }}</h2>
|
||||
<hr/>
|
||||
|
||||
{{ form_row(form.notificationsSenderEmail) }}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,20 @@
|
|||
{{ form_row(form.name) }}
|
||||
{{ form_row(form.type) }}
|
||||
{{ form_row(form.membership) }}
|
||||
|
||||
<div {{ stimulus_controller('parentgroup') }}>
|
||||
{% if form.servicesEnabled is defined %}
|
||||
<div
|
||||
{{ stimulus_target('parentgroup', 'servicesField') }}
|
||||
data-product-route-value="{{ path('app_group_list') }}"
|
||||
>
|
||||
{{ form_widget(form.servicesEnabled) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.parent is defined and form.parent is not null %}
|
||||
{{ form_row(form.parent) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form_widget(form.submit) }}
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Functional\Controller\Admin;
|
||||
|
||||
use App\Controller\Admin\PlatformOfferCrudController;
|
||||
use App\Enum\OfferType;
|
||||
use App\Test\KernelTrait;
|
||||
use App\Tests\TestReference;
|
||||
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
final class PlatformOfferCrudControllerTest extends WebTestCase
|
||||
{
|
||||
use KernelTrait;
|
||||
use RefreshDatabaseTrait;
|
||||
|
||||
/**
|
||||
* @see PlatformOfferCrudController
|
||||
*/
|
||||
public function testController(): void
|
||||
{
|
||||
$client = self::createClient();
|
||||
$this->loginAsAdmin($client);
|
||||
|
||||
// list+custom filters
|
||||
$filters = '&filters[type]='.(OfferType::MONTHLY->isMonthly() ? '1' : '0');
|
||||
|
||||
$client->request('GET', sprintf(TestReference::ADMIN_URL, 'index', PlatformOfferCrudController::class.'&'.$filters));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
// edit
|
||||
$client->request('GET', sprintf(TestReference::ADMIN_URL.'&entityId=%s', 'edit', PlatformOfferCrudController::class, TestReference::PLATFORM_OFFER_1));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
// detail
|
||||
$client->request('GET', sprintf(TestReference::ADMIN_URL.'&entityId=%s', 'detail', PlatformOfferCrudController::class, TestReference::PLATFORM_OFFER_1));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
// new
|
||||
$crawler = $client->request('GET', sprintf(TestReference::ADMIN_URL, 'new', PlatformOfferCrudController::class));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$form = $crawler->selectButton(TestReference::ACTION_SAVE_AND_RETURN)->form();
|
||||
$client->submit($form, [
|
||||
$form->getName().'[name]' => 'New special offer',
|
||||
$form->getName().'[type]' => 'yearly',
|
||||
$form->getName().'[price]' => 490,
|
||||
$form->getName().'[currency]' => 'EUR',
|
||||
$form->getName().'[active]' => false,
|
||||
]);
|
||||
self::assertResponseRedirects();
|
||||
$client->followRedirect();
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ final class CreateGroupActionTest extends WebTestCase
|
|||
$client->submit($form, [
|
||||
$form->getName().'[name]' => 'Groupe 1',
|
||||
$form->getName().'[type]' => 'public',
|
||||
$form->getName().'[parent]' => TestReference::GROUP_1,
|
||||
$form->getName().'[servicesEnabled]' => false,
|
||||
]);
|
||||
self::assertResponseRedirects();
|
||||
self::assertTrue(u($client->getResponse()->headers->get('Location'))->startsWith('http://localhost/admin'));
|
||||
|
|
|
|||
|
|
@ -127,4 +127,7 @@ final class TestReference
|
|||
|
||||
// payments
|
||||
final public const PAYMENT_USER_16_1 = '1edcefc9-45b3-6a3e-b4a6-db137f56da56';
|
||||
|
||||
// platform offer
|
||||
final public const PLATFORM_OFFER_1 = '9040b3fb-8a01-4bbf-a228-ca9f90db5034';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ final class DoneActionTest extends TestCase
|
|||
->setId($this->getUuid())
|
||||
->setSlug('group');
|
||||
|
||||
return (new GroupOffer())
|
||||
return (new GroupOffer()) // @phpstan-ignore-line
|
||||
->setId($this->getUuid())
|
||||
->setGroup($group);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Tests\Unit\Enum\Group;
|
||||
|
||||
use App\Enum\Group\GroupOfferType;
|
||||
use App\Enum\OfferType;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class GroupOfferTypeTest extends TestCase
|
||||
{
|
||||
public function testGroupOfferType(): void
|
||||
{
|
||||
self::assertTrue(GroupOfferType::MONTHLY->isMonthly());
|
||||
self::assertTrue(GroupOfferType::YEARLY->isYearly());
|
||||
self::assertTrue(OfferType::MONTHLY->isMonthly());
|
||||
self::assertTrue(OfferType::YEARLY->isYearly());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -260,11 +260,21 @@
|
|||
<target>Paramètres de l'instance</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="e1qNBMp" resname="parameters.services.h2">
|
||||
<source>parameters.services.h2</source>
|
||||
<trans-unit id="e1qNBMp" resname="parameters.global.h2">
|
||||
<source>parameters.global.h2</source>
|
||||
<target>Paramètres généraux de la plateforme</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="z9SxLWb" resname="parameters.services.h3">
|
||||
<source>parameters.services.h3</source>
|
||||
<target>Services</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="z2cGYub" resname="parameters.paid_membership.h3">
|
||||
<source>parameters.paid_membership.h3</source>
|
||||
<target>Adhésion payante</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="eNqNAUs" resname="parameters.senders.h2">
|
||||
<source>parameters.senders.h2</source>
|
||||
<target>Expéditeur·rice des notifications</target>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file source-language="en" target-language="fr" datatype="plaintext" original="file.ext">
|
||||
<header>
|
||||
<tool tool-id="symfony" tool-name="Symfony" />
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="M1x1Kq4" resname="platform_offers">
|
||||
<source>platform_offers</source>
|
||||
<target>Tarifs d'adhésion pour la plateforme</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="W2z6Ls8" resname="PlatformOffer">
|
||||
<source>PlatformOffer</source>
|
||||
<target>Tarifs d'adhésion</target>
|
||||
</trans-unit>
|
||||
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
@ -25,6 +25,11 @@
|
|||
<target>Envoi d'invitations par les admins possible</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="KBpmXZn" resname="Services Enabled">
|
||||
<source>Services Enabled</source>
|
||||
<target>Activer la disponibilité des services pour le groupe</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="MQA7gVL" resname="Invitation By Moderator">
|
||||
<source>Invitation By Moderator</source>
|
||||
<target>Envoi d'invitations par les modérateur·rice·s possible</target>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,22 @@
|
|||
</header>
|
||||
<body>
|
||||
|
||||
<!-- notification sender section -->
|
||||
<!-- global section -->
|
||||
<trans-unit id="qlPbwB3" resname="parameter.services">
|
||||
<source>parameter.services</source>
|
||||
<target>Services activés</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="swXqmJ4" resname="parameter.paid_membership">
|
||||
<source>parameter.paid_membership</source>
|
||||
<target>Adhésion à la plateforme payante</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="qsLAkx6" resname="parameter.set_price">
|
||||
<source>parameter.set_price</source>
|
||||
<target>Configurer le tarif</target>
|
||||
</trans-unit>
|
||||
|
||||
<!-- notification sender section -->
|
||||
<trans-unit id="qqTbwBV" resname="parameter.mail">
|
||||
<source>parameter.mail</source>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@
|
|||
<target>Type</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="BOAMpGn" resname="templates.pages.group.create.form.servicesEnabled">
|
||||
<source>templates.pages.group.create.form.servicesEnabled</source>
|
||||
<target>Activer la disponibilité des services pour le groupe</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="qBmcBDh" resname="templates.pages.group.create.form.membership">
|
||||
<source>templates.pages.group.create.form.membership</source>
|
||||
<target>Tarif</target>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ Encore
|
|||
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
|
||||
*/
|
||||
.addEntry('app', './assets/app.js')
|
||||
.addEntry('admin', './assets/admin.js')
|
||||
|
||||
// enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)
|
||||
.enableStimulusBridge('./assets/controllers.json')
|
||||
|
|
|
|||
Loading…
Reference in a new issue