mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-10 16:55:00 +00:00
Compare commits
82 Commits
user_edit_
...
362-bug-ma
Author | SHA1 | Date | |
---|---|---|---|
499aa3adea | |||
b597ff89e0 | |||
3c801a6d0e | |||
4093949c2f | |||
7ed10efcd1 | |||
350661a4fa | |||
08207b656a | |||
8de63de6d6 | |||
51804b10c0 | |||
02f555efae | |||
d2fcb6945b | |||
c89e3785ef | |||
9f17ec4841 | |||
b277a7749a | |||
c8b6b6e33a | |||
10eaebf610 | |||
0a34f9086f
|
|||
739e0b1692 | |||
8db8f5fdf5
|
|||
fc32f9eca9 | |||
ab35e8c034
|
|||
2aded2974f | |||
f84c1632b2
|
|||
|
03717a1a87 | ||
|
02c524dd79 | ||
99e4824137 | |||
dacaaea235 | |||
096466e79e
|
|||
7285e5c2b0 | |||
37227a3aeb | |||
7569667189
|
|||
b0993f4062 | |||
7c79b65f48 | |||
b8f25bcd45 | |||
f4efb0e975 | |||
c641baec78 | |||
cc150e32f0 | |||
|
bc7f0907ab | ||
26cf6459b4 | |||
d0fa6dd512 | |||
|
fbdc0d32f0 | ||
|
5f31473c90 | ||
|
98cf167040 | ||
|
6c37d798bf | ||
03748a7e84 | |||
9e3431f397 | |||
912861dbff | |||
35f25daf7c | |||
21274155b5 | |||
3f7c136d6b
|
|||
5d9c573853 | |||
9a5fd67842 | |||
2755bc12c4 | |||
9e191f1b5b | |||
ab684a20ad
|
|||
bc92b52498 | |||
be5655e537
|
|||
ceb0bd982e
|
|||
47c0af3623
|
|||
f6f2efee2c
|
|||
59fd9fc63f | |||
ec2c08681e | |||
fedcbb9a70
|
|||
3f1a4fe353 | |||
fc27c73dab | |||
20bfd5b717
|
|||
5e3a1eb2ab
|
|||
b02820407c
|
|||
594ed4a5b4
|
|||
88fbf7bc1c
|
|||
aa26e67f6f
|
|||
21ac3eaab4 | |||
2ff500b00e | |||
0cdd9184a3 | |||
2a61197999 | |||
0a53a9a9d1 | |||
eea1e40663 | |||
1b0771eb07 | |||
3a74c48104 | |||
b37d7fb907 | |||
57b8dacba0 | |||
60386ae9ac |
6
.changes/unreleased/DX-20250131-131801.yaml
Normal file
6
.changes/unreleased/DX-20250131-131801.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: DX
|
||||||
|
body: Create an unique source of trust for translations
|
||||||
|
time: 2025-01-31T13:18:01.239211506+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "333"
|
||||||
|
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250130-120207.yaml
Normal file
6
.changes/unreleased/Feature-20250130-120207.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Suggest all referrers within actions of the accompanying period when creating an activity
|
||||||
|
time: 2025-01-30T12:02:07.053587034+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "349"
|
||||||
|
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250130-120459.yaml
Normal file
6
.changes/unreleased/Feature-20250130-120459.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Add possibility to export a csv with all social issues and social actions
|
||||||
|
time: 2025-01-30T12:04:59.754998541+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "343"
|
||||||
|
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250214-150328.yaml
Normal file
6
.changes/unreleased/Feature-20250214-150328.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Restore document to previous kept version when a workflow is canceled
|
||||||
|
time: 2025-02-14T15:03:28.707250207+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "360"
|
||||||
|
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250219-120928.yaml
Normal file
6
.changes/unreleased/Feature-20250219-120928.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Add a list of third parties from within the admin (csv download)
|
||||||
|
time: 2025-02-19T12:09:28.487991703+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "341"
|
||||||
|
SchemaChange: No schema change
|
6
.changes/unreleased/Fixed-20250214-121010.yaml
Normal file
6
.changes/unreleased/Fixed-20250214-121010.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Fixed
|
||||||
|
body: fix generation of document with accompanying period context, and list of activities and works
|
||||||
|
time: 2025-02-14T12:10:10.920355454+01:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
||||||
|
SchemaChange: No schema change
|
62
.changes/v3.7.0.md
Normal file
62
.changes/v3.7.0.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
## v3.7.0 - 2025-01-21
|
||||||
|
### Feature
|
||||||
|
* Use the Notifier component from Symfony to sens short messages (SMS). This allow to use more provider.
|
||||||
|
### Fixed
|
||||||
|
* ([#348](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/348)) [export] Fix aggregation of referrer's scope and job: fix the date range comparison
|
||||||
|
|
||||||
|
### Warning on configuration of Notifier component
|
||||||
|
|
||||||
|
If installed in an symfony app where the recipes are activated, this configuration should be added automatically:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||||
|
channel_policy:
|
||||||
|
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||||
|
urgent: ['email']
|
||||||
|
high: ['email']
|
||||||
|
medium: ['email']
|
||||||
|
low: ['email']
|
||||||
|
admin_recipients:
|
||||||
|
- { email: admin@example.com }
|
||||||
|
```
|
||||||
|
|
||||||
|
Actually, you should either:
|
||||||
|
|
||||||
|
- remove the configuration of ovhcloud added by the recipe
|
||||||
|
- or remove the previous configuration of chill, to avoid keeping legacy configuration
|
||||||
|
|
||||||
|
#### Remove the added configuration and keep the legacy configuration
|
||||||
|
|
||||||
|
To remove the configuration:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
- ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||||
|
```
|
||||||
|
|
||||||
|
In that case, the previous configuration, which was stored under the `chill_main.short_messages.dsn` will be reconfigured into the Notifier component's configuration.
|
||||||
|
|
||||||
|
#### Properly configure SMS
|
||||||
|
|
||||||
|
You can also properly configure it, as [described in the OVH cloud provider repository](https://github.com/symfony/ovh-cloud-notifier/tree/5.4?tab=readme-ov-file#dsn-example) (where the scheme is `ovhcloud`):
|
||||||
|
|
||||||
|
**NOTE**: You have access to all notifier available with the [Notifier component](https://symfony.com/doc/current/notifier.html#notifier-sms-channel). You are not restricted to use OVH as a provider.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
+ ovhcloud: '%env(OVHCLOUD_DSN)%' # this value should be located in a variable, and have `ovhcloud://` as a scheme
|
||||||
|
|
||||||
|
chill_main:
|
||||||
|
- short_messages:
|
||||||
|
- dsn: '%env(string:SHORT_MESSAGE_DSN)%'
|
||||||
|
```
|
3
.changes/v3.7.1.md
Normal file
3
.changes/v3.7.1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## v3.7.1 - 2025-01-21
|
||||||
|
### Fixed
|
||||||
|
* Fix legacy configuration processor for notifier component
|
11
.changes/v3.8.0.md
Normal file
11
.changes/v3.8.0.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
## v3.8.0 - 2025-02-03
|
||||||
|
### Feature
|
||||||
|
* Improve the UX of the news item admin form to prevent wrong usage
|
||||||
|
* ([#319](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/319)) Notification list: display the concerned person's badges in the list
|
||||||
|
* ([#320](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/320)) Show the first 3 persons directly in the accompanying period's banner
|
||||||
|
* ([#334](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/334)) Suggest current user when creating an activity
|
||||||
|
* ([#331](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/331)) Add attachments to workflows
|
||||||
|
### Fixed
|
||||||
|
* ([#350](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/350)) Add validation error to manual selection of person in PersonDuplicateController
|
||||||
|
* ([#354](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/354)) Fix document category creation
|
||||||
|
* ([#351](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/351)) Add definitive whitespace between span elements in vue PersonText component
|
3
.changes/v3.8.1.md
Normal file
3
.changes/v3.8.1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## v3.8.1 - 2025-02-05
|
||||||
|
### Fixed
|
||||||
|
* Fix household link in the parcours banner
|
3
.changes/v3.8.2.md
Normal file
3
.changes/v3.8.2.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## v3.8.2 - 2025-02-10
|
||||||
|
### Fixed
|
||||||
|
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal
|
@@ -7,15 +7,29 @@ versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
|
|||||||
kindFormat: '### {{.Kind}}'
|
kindFormat: '### {{.Kind}}'
|
||||||
# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
|
# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
|
||||||
changeFormat: >-
|
changeFormat: >-
|
||||||
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if and (.Custom.Long) (not (eq .Custom.Long "")) }}
|
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{ .Body }} {{ if and .Custom.SchemaChange (ne .Custom.SchemaChange "No schema change") }}
|
||||||
|
|
||||||
|
**Schema Change**: {{ .Custom.SchemaChange }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{ if and (.Custom.Long) (not (eq .Custom.Long "")) }}{{ .Custom.Long }}{{ end }}
|
||||||
|
|
||||||
{{ .Custom.Long }}{{ end }}
|
|
||||||
custom:
|
custom:
|
||||||
|
- key: SchemaChange
|
||||||
|
label: Is a schema change required?
|
||||||
|
optional: false
|
||||||
|
type: enum
|
||||||
|
enumOptions:
|
||||||
|
- "No schema change"
|
||||||
|
- "Add columns or tables"
|
||||||
|
- "Drop or rename table or columns, or enforce new constraint that must be manually fixed"
|
||||||
|
|
||||||
- key: Issue
|
- key: Issue
|
||||||
label: Issue number (on chill-bundles repository) (optional)
|
label: Issue number (on chill-bundles repository) (optional)
|
||||||
optional: true
|
optional: true
|
||||||
type: int
|
type: int
|
||||||
minInt: 1
|
minInt: 1
|
||||||
|
|
||||||
body:
|
body:
|
||||||
# allow multiline messages
|
# allow multiline messages
|
||||||
block: true
|
block: true
|
||||||
|
4
.env
4
.env
@@ -88,3 +88,7 @@ REDIS_HOST=redis
|
|||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
||||||
###< chill-project/chill-bundles ###
|
###< chill-project/chill-bundles ###
|
||||||
|
|
||||||
|
###> symfony/ovh-cloud-notifier ###
|
||||||
|
# OVHCLOUD_DSN=ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME
|
||||||
|
###< symfony/ovh-cloud-notifier ###
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,12 +5,14 @@ composer.lock
|
|||||||
docs/build/
|
docs/build/
|
||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
.cache/*
|
.cache/*
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
docker/db/data
|
docker/db/data
|
||||||
docker/rabbitmq/data
|
docker/rabbitmq/data
|
||||||
|
|
||||||
# in this development bundle, we want to ignore directories related to a real app
|
# in this development bundle, we want to ignore directories related to a real app
|
||||||
assets/*
|
assets/*
|
||||||
|
!assets/translator.ts
|
||||||
migrations/*
|
migrations/*
|
||||||
templates/*
|
templates/*
|
||||||
translations/*
|
translations/*
|
||||||
|
@@ -113,7 +113,7 @@ lint:
|
|||||||
- export PATH="./node_modules/.bin:$PATH"
|
- export PATH="./node_modules/.bin:$PATH"
|
||||||
script:
|
script:
|
||||||
- yarn install --ignore-optional
|
- yarn install --ignore-optional
|
||||||
- npx eslint-baseline "**/*.{js,vue}"
|
- npx eslint-baseline "src/**/*.{js,ts,vue}"
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- node_modules/
|
- node_modules/
|
||||||
|
@@ -25,7 +25,7 @@ $config = new PhpCsFixer\Config();
|
|||||||
$config
|
$config
|
||||||
->setFinder($finder)
|
->setFinder($finder)
|
||||||
->setRiskyAllowed(true)
|
->setRiskyAllowed(true)
|
||||||
->setCacheFile('.cache/php-cs-fixer.cache')
|
->setCacheFile('var/php-cs-fixer.cache')
|
||||||
->setUsingCache(true)
|
->setUsingCache(true)
|
||||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||||
;
|
;
|
||||||
|
87
CHANGELOG.md
87
CHANGELOG.md
@@ -6,6 +6,93 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
|||||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||||
|
|
||||||
|
|
||||||
|
## v3.8.2 - 2025-02-10
|
||||||
|
### Fixed
|
||||||
|
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal
|
||||||
|
|
||||||
|
## v3.8.1 - 2025-02-05
|
||||||
|
### Fixed
|
||||||
|
* Fix household link in the parcours banner
|
||||||
|
|
||||||
|
## v3.8.0 - 2025-02-03
|
||||||
|
### Feature
|
||||||
|
* Improve the UX of the news item admin form to prevent wrong usage
|
||||||
|
* ([#319](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/319)) Notification list: display the concerned person's badges in the list
|
||||||
|
* ([#320](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/320)) Show the first 3 persons directly in the accompanying period's banner
|
||||||
|
* ([#334](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/334)) Suggest current user when creating an activity
|
||||||
|
* ([#331](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/331)) Add attachments to workflows
|
||||||
|
### Fixed
|
||||||
|
* ([#350](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/350)) Add validation error to manual selection of person in PersonDuplicateController
|
||||||
|
* ([#354](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/354)) Fix document category creation
|
||||||
|
* ([#351](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/351)) Add definitive whitespace between span elements in vue PersonText component
|
||||||
|
|
||||||
|
## v3.7.1 - 2025-01-21
|
||||||
|
### Fixed
|
||||||
|
* Fix legacy configuration processor for notifier component
|
||||||
|
|
||||||
|
## v3.7.0 - 2025-01-21
|
||||||
|
### Feature
|
||||||
|
* Use the Notifier component from Symfony to sens short messages (SMS). This allow to use more provider.
|
||||||
|
### Fixed
|
||||||
|
* ([#348](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/348)) [export] Fix aggregation of referrer's scope and job: fix the date range comparison
|
||||||
|
|
||||||
|
### Warning on configuration of Notifier component
|
||||||
|
|
||||||
|
If installed in an symfony app where the recipes are activated, this configuration should be added automatically:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||||
|
channel_policy:
|
||||||
|
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||||
|
urgent: ['email']
|
||||||
|
high: ['email']
|
||||||
|
medium: ['email']
|
||||||
|
low: ['email']
|
||||||
|
admin_recipients:
|
||||||
|
- { email: admin@example.com }
|
||||||
|
```
|
||||||
|
|
||||||
|
Actually, you should either:
|
||||||
|
|
||||||
|
- remove the configuration of ovhcloud added by the recipe
|
||||||
|
- or remove the previous configuration of chill, to avoid keeping legacy configuration
|
||||||
|
|
||||||
|
#### Remove the added configuration and keep the legacy configuration
|
||||||
|
|
||||||
|
To remove the configuration:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
- ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||||
|
```
|
||||||
|
|
||||||
|
In that case, the previous configuration, which was stored under the `chill_main.short_messages.dsn` will be reconfigured into the Notifier component's configuration.
|
||||||
|
|
||||||
|
#### Properly configure SMS
|
||||||
|
|
||||||
|
You can also properly configure it, as [described in the OVH cloud provider repository](https://github.com/symfony/ovh-cloud-notifier/tree/5.4?tab=readme-ov-file#dsn-example) (where the scheme is `ovhcloud`):
|
||||||
|
|
||||||
|
**NOTE**: You have access to all notifier available with the [Notifier component](https://symfony.com/doc/current/notifier.html#notifier-sms-channel). You are not restricted to use OVH as a provider.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
chatter_transports:
|
||||||
|
texter_transports:
|
||||||
|
+ ovhcloud: '%env(OVHCLOUD_DSN)%' # this value should be located in a variable, and have `ovhcloud://` as a scheme
|
||||||
|
|
||||||
|
chill_main:
|
||||||
|
- short_messages:
|
||||||
|
- dsn: '%env(string:SHORT_MESSAGE_DSN)%'
|
||||||
|
```
|
||||||
|
|
||||||
## v3.6.0 - 2025-01-16
|
## v3.6.0 - 2025-01-16
|
||||||
### Feature
|
### Feature
|
||||||
* Importer for addresses does not fails when the postal code is not found with some addresses, and compute a recap list of all addresses that could not be imported. This recap list can be send by email.
|
* Importer for addresses does not fails when the postal code is not found with some addresses, and compute a recap list of all addresses that could not be imported. This recap list can be send by email.
|
||||||
|
9
assets/translator.ts
Normal file
9
assets/translator.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// @ts-ignore Cannot find module (when used within an app)
|
||||||
|
import { trans, getLocale, setLocale, setLocaleFallbacks } from "@symfony/ux-translator";
|
||||||
|
|
||||||
|
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
|
||||||
|
setLocale('fr');
|
||||||
|
|
||||||
|
export { trans };
|
||||||
|
// @ts-ignore Cannot find module (when used within an app)
|
||||||
|
export * from '../var/translations';
|
@@ -16,8 +16,8 @@
|
|||||||
"ext-zlib": "*",
|
"ext-zlib": "*",
|
||||||
"champs-libres/wopi-bundle": "dev-master@dev",
|
"champs-libres/wopi-bundle": "dev-master@dev",
|
||||||
"champs-libres/wopi-lib": "dev-master@dev",
|
"champs-libres/wopi-lib": "dev-master@dev",
|
||||||
"doctrine/doctrine-bundle": "^2.1",
|
|
||||||
"doctrine/data-fixtures": "^1.8",
|
"doctrine/data-fixtures": "^1.8",
|
||||||
|
"doctrine/doctrine-bundle": "^2.1",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||||
"doctrine/orm": "^2.13.0",
|
"doctrine/orm": "^2.13.0",
|
||||||
"erusev/parsedown": "^1.7",
|
"erusev/parsedown": "^1.7",
|
||||||
@@ -58,7 +58,9 @@
|
|||||||
"symfony/messenger": "^5.4",
|
"symfony/messenger": "^5.4",
|
||||||
"symfony/mime": "^5.4",
|
"symfony/mime": "^5.4",
|
||||||
"symfony/monolog-bundle": "^3.5",
|
"symfony/monolog-bundle": "^3.5",
|
||||||
|
"symfony/notifier": "^5.4",
|
||||||
"symfony/options-resolver": "^5.4",
|
"symfony/options-resolver": "^5.4",
|
||||||
|
"symfony/ovh-cloud-notifier": "^5.4",
|
||||||
"symfony/process": "^5.4",
|
"symfony/process": "^5.4",
|
||||||
"symfony/property-access": "^5.4",
|
"symfony/property-access": "^5.4",
|
||||||
"symfony/property-info": "^5.4",
|
"symfony/property-info": "^5.4",
|
||||||
@@ -73,6 +75,7 @@
|
|||||||
"symfony/templating": "^5.4",
|
"symfony/templating": "^5.4",
|
||||||
"symfony/translation": "^5.4",
|
"symfony/translation": "^5.4",
|
||||||
"symfony/twig-bundle": "^5.4",
|
"symfony/twig-bundle": "^5.4",
|
||||||
|
"symfony/ux-translator": "^2.22",
|
||||||
"symfony/validator": "^5.4",
|
"symfony/validator": "^5.4",
|
||||||
"symfony/webpack-encore-bundle": "^1.11",
|
"symfony/webpack-encore-bundle": "^1.11",
|
||||||
"symfony/workflow": "^5.4",
|
"symfony/workflow": "^5.4",
|
||||||
@@ -160,7 +163,9 @@
|
|||||||
"cache:clear": "symfony-cmd",
|
"cache:clear": "symfony-cmd",
|
||||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
},
|
},
|
||||||
"php-cs-fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php --show-progress=none"
|
"php-cs-fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php --show-progress=none",
|
||||||
|
"phpstan": "phpstan --no-progress",
|
||||||
|
"rector": "rector --no-progress-bar"
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
|
@@ -36,4 +36,5 @@ return [
|
|||||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
13
config/packages/notifier.yaml
Normal file
13
config/packages/notifier.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
texter_transports:
|
||||||
|
#ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||||
|
#ovhcloud: '%env(SHORT_MESSAGE_DSN)%'
|
||||||
|
channel_policy:
|
||||||
|
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||||
|
urgent: ['email']
|
||||||
|
high: ['email']
|
||||||
|
medium: ['email']
|
||||||
|
low: ['email']
|
||||||
|
admin_recipients:
|
||||||
|
- { email: admin@example.com }
|
3
config/packages/ux_translator.yaml
Normal file
3
config/packages/ux_translator.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ux_translator:
|
||||||
|
# The directory where the JavaScript translations are dumped
|
||||||
|
dump_directory: '%kernel.project_dir%/var/translations'
|
19
config/routes/chill_assets_dev.yaml
Normal file
19
config/routes/chill_assets_dev.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
when@dev:
|
||||||
|
sass_assets:
|
||||||
|
path: /_dev/assets
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/dev.assets.html.twig'
|
||||||
|
|
||||||
|
sass_assets_test1:
|
||||||
|
path: /_dev/assets_test1
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/dev.assets.test1.html.twig'
|
||||||
|
|
||||||
|
sass_assets_test2:
|
||||||
|
path: /_dev/assets_test2
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/dev.assets.test2.html.twig'
|
||||||
|
|
12
config/routes/chill_swagger.yaml
Normal file
12
config/routes/chill_swagger.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
when@dev:
|
||||||
|
swagger_ui:
|
||||||
|
path: /_dev/swagger
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/swagger-ui/index.html.twig'
|
||||||
|
|
||||||
|
swagger_specs:
|
||||||
|
path: /_dev/specs.yaml
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: api/specs.yaml
|
@@ -12,6 +12,8 @@ This runs eslint **not** taking the baseline into account, thus showing all exis
|
|||||||
A script was also added to package.json allowing you to execute ``yarn run eslint``.
|
A script was also added to package.json allowing you to execute ``yarn run eslint``.
|
||||||
This will run eslint, but **taking the baseline into account**, thus only alerting to newly created errors.
|
This will run eslint, but **taking the baseline into account**, thus only alerting to newly created errors.
|
||||||
|
|
||||||
|
The eslint command is configured to also run ``prettier`` which will simply format the code to look more uniform (takes care indentation for example).
|
||||||
|
|
||||||
Interesting options that can be used in combination with eslint are:
|
Interesting options that can be used in combination with eslint are:
|
||||||
|
|
||||||
- ``--quiet`` to only get errors and silence the warnings
|
- ``--quiet`` to only get errors and silence the warnings
|
||||||
|
@@ -16,7 +16,7 @@ Welcome to Chill documentation!
|
|||||||
|
|
||||||
Chill is a free software for social workers.
|
Chill is a free software for social workers.
|
||||||
|
|
||||||
Chill rely on the php framework `Symfony <http://symfony.com>`_.
|
Chill rely on the php framework `Symfony <http://symfony.com>`_.
|
||||||
|
|
||||||
Contents of this documentation:
|
Contents of this documentation:
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ Contribute
|
|||||||
User manual
|
User manual
|
||||||
===========
|
===========
|
||||||
|
|
||||||
An user manual exists in French and currently focuses on describing the main concept of the software.
|
An user manual exists in French and currently focuses on describing the main concept of the software.
|
||||||
|
|
||||||
`Read (and contribute) to the manual <https://fr.wikibooks.org/wiki/Chill>`_
|
`Read (and contribute) to the manual <https://fr.wikibooks.org/wiki/Chill>`_
|
||||||
|
|
||||||
@@ -55,12 +55,11 @@ Available bundles
|
|||||||
* Chill Person, to deal with persons,
|
* Chill Person, to deal with persons,
|
||||||
* chill custom fields, to add custom fields to some entities,
|
* chill custom fields, to add custom fields to some entities,
|
||||||
* chill activity: to add activities to people,
|
* chill activity: to add activities to people,
|
||||||
* chill report: to add report to people,
|
* chill report: to add report to people,
|
||||||
* chill event: to gather people into events,
|
* chill event: to gather people into events,
|
||||||
* chill docs store: to store documents to people, but also entities,
|
* chill docs store: to store documents to people, but also entities,
|
||||||
* chill task: to register task with people,
|
* chill task: to register task with people,
|
||||||
* chill third party: to register third parties,
|
* chill third party: to register third parties,
|
||||||
* chill family members: to register family members
|
|
||||||
|
|
||||||
You will also found the following projects :
|
You will also found the following projects :
|
||||||
|
|
||||||
|
@@ -29,8 +29,7 @@ We strongly encourage you to initialize a git repository at this step, to track
|
|||||||
# add the flex endpoints required for custom recipes
|
# add the flex endpoints required for custom recipes
|
||||||
cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json
|
cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json
|
||||||
# install chill and some dependencies
|
# install chill and some dependencies
|
||||||
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released
|
symfony composer require chill-project/chill-bundles ^3.7.1 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev symfony/amqp-messenger
|
||||||
symfony composer require chill-project/chill-bundles v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
|
|
||||||
|
|
||||||
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
|
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
|
||||||
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
|
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
|
||||||
@@ -48,7 +47,7 @@ You must also accept to configure recipes from the contrib repository, unless yo
|
|||||||
|
|
||||||
If you encounter this error during assets compilation (:code:`yarn run encore production`) (repeated multiple times):
|
If you encounter this error during assets compilation (:code:`yarn run encore production`) (repeated multiple times):
|
||||||
|
|
||||||
.. code-block:: txt
|
.. code-block::
|
||||||
|
|
||||||
[tsl] ERROR in /tmp/chill/v1/public/bundles/chillcalendar/types.ts(2,65)
|
[tsl] ERROR in /tmp/chill/v1/public/bundles/chillcalendar/types.ts(2,65)
|
||||||
TS2307: Cannot find module '../../../ChillMainBundle/Resources/public/types' or its corresponding type declarations.
|
TS2307: Cannot find module '../../../ChillMainBundle/Resources/public/types' or its corresponding type declarations.
|
||||||
@@ -74,14 +73,22 @@ or in the :code:`.env.local` file, which should not be committed to the git repo
|
|||||||
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
|
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
|
||||||
by the symfony server, from the docker compose services.
|
by the symfony server, from the docker compose services.
|
||||||
|
|
||||||
The only required variable is the :code:`ADMIN_PASSWORD`. You can generate a hashed and salted admin password using the command
|
The required variables are:
|
||||||
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`. Then,
|
|
||||||
|
- the :code:`ADMIN_PASSWORD`;
|
||||||
|
- the :code:`OVHCLOUD_DSN` variable;
|
||||||
|
|
||||||
|
:code:`ADMIN_PASSWORD`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
You can generate a hashed and salted admin password using the command
|
||||||
|
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`.Then,
|
||||||
you can either:
|
you can either:
|
||||||
|
|
||||||
- add this password to the :code:`.env.local` file, you must escape the character :code:`$`: if the generated password
|
- add this password to the :code:`.env.local` file, you must escape the character :code:`$`: if the generated password
|
||||||
is :code:`$2y$13$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm`, your :code:`.env.local` file will be:
|
is :code:`$2y$13$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm`, your :code:`.env.local` file will be:
|
||||||
|
|
||||||
.. code-block:: env
|
.. code-block:: bash
|
||||||
|
|
||||||
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
|
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
|
||||||
# note: if you copy-paste the line above, the password will be "admin".
|
# note: if you copy-paste the line above, the password will be "admin".
|
||||||
@@ -89,12 +96,24 @@ you can either:
|
|||||||
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
|
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
|
||||||
not the password in clear text).
|
not the password in clear text).
|
||||||
|
|
||||||
- set up the jwt authentication bundle
|
:code:`OVHCLOUD_DSN` and sending SMS messages
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This is a temporary dependency, for ensuring compatibility for previous behaviour.
|
||||||
|
|
||||||
|
You can set it to :code:`null://null` if you do not plan to use sending SMS.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
OVHCLOUD_DSN=null://null
|
||||||
|
|
||||||
|
If you plan to do it, you can configure the notifier component `as described in the symfony documentation <https://symfony.com/doc/current/notifier.html#notifier-sms-channel>`_.
|
||||||
|
|
||||||
|
|
||||||
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
|
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
|
||||||
|
|
||||||
Prepare migrations and other tools
|
Prepare database, messenger queue, and other configuration
|
||||||
----------------------------------
|
----------------------------------------------------------
|
||||||
|
|
||||||
To continue the installation process, you will have to run migrations:
|
To continue the installation process, you will have to run migrations:
|
||||||
|
|
||||||
@@ -109,17 +128,22 @@ To continue the installation process, you will have to run migrations:
|
|||||||
symfony console messenger:setup-transports
|
symfony console messenger:setup-transports
|
||||||
# prepare some views
|
# prepare some views
|
||||||
symfony console chill:db:sync-views
|
symfony console chill:db:sync-views
|
||||||
|
# load languages data
|
||||||
|
symfony console chill:main:languages:populate
|
||||||
# generate jwt token, required for some api features (webdav access, ...)
|
# generate jwt token, required for some api features (webdav access, ...)
|
||||||
symfony console lexik:jwt:generate-keypair
|
symfony console lexik:jwt:generate-keypair
|
||||||
|
|
||||||
.. warning::
|
.. note::
|
||||||
|
|
||||||
If you encounter an error while running :code:`symfony console messenger:setup-transports`, you can set up the messenger
|
If you encounter this error:
|
||||||
transport to redis, by adding this in the :code:`.env.local` or :code:`.env` file:
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
No transport supports the given Messenger DSN.
|
||||||
|
|
||||||
|
Please check that you installed the package `symfony/amqp-messenger`.
|
||||||
|
|
||||||
.. code-block:: env
|
|
||||||
|
|
||||||
MESSENGER_TRANSPORT_DSN=redis://${REDIS_HOST}:${REDIS_PORT}/messages
|
|
||||||
|
|
||||||
Start your web server locally
|
Start your web server locally
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
@@ -41,16 +41,18 @@ Postal code are loaded from this database. There is no need to load postal codes
|
|||||||
The data are prepared for Chill (`See this repository <https://gitea.champs-libres.be/Chill-project/belgian-bestaddresses-transform/releases>`_).
|
The data are prepared for Chill (`See this repository <https://gitea.champs-libres.be/Chill-project/belgian-bestaddresses-transform/releases>`_).
|
||||||
One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose.
|
One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose.
|
||||||
|
|
||||||
|
The command expects a language code as first argument.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
# load postal code from 1000 to 3999:
|
# load postal code from 1000 to 3999:
|
||||||
bin/console chill:main:address-ref-from-best-addresse 1xxx 2xxx 3xxx
|
bin/console chill:main:address-ref-from-best-addresse fr 1xxx 2xxx 3xxx
|
||||||
|
|
||||||
# load only an extract (for dev purposes)
|
# load only an extract (for dev purposes)
|
||||||
bin/console chill:main:address-ref-from-best-addresse extract
|
bin/console chill:main:address-ref-from-best-addresse fr extract
|
||||||
|
|
||||||
# load full addresses (discouraged)
|
# load full addresses (discouraged)
|
||||||
bin/console chill:main:address-ref-from-best-addresse full
|
bin/console chill:main:address-ref-from-best-addresse fr full
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
26
package.json
26
package.json
@@ -6,31 +6,29 @@
|
|||||||
"@apidevtools/swagger-cli": "^4.0.4",
|
"@apidevtools/swagger-cli": "^4.0.4",
|
||||||
"@babel/core": "^7.20.5",
|
"@babel/core": "^7.20.5",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
|
"@ckeditor/ckeditor5-vue": "^7.3.0",
|
||||||
"@ckeditor/ckeditor5-dev-translations": "^40.2.0",
|
|
||||||
"@ckeditor/ckeditor5-dev-utils": "^40.2.0",
|
|
||||||
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
|
|
||||||
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
|
||||||
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
|
||||||
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
|
"@hotwired/stimulus": "^3.0.0",
|
||||||
"@luminateone/eslint-baseline": "^1.0.9",
|
"@luminateone/eslint-baseline": "^1.0.9",
|
||||||
|
"@symfony/stimulus-bridge": "^3.2.0",
|
||||||
|
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||||
"@symfony/webpack-encore": "^4.1.0",
|
"@symfony/webpack-encore": "^4.1.0",
|
||||||
"@tsconfig/node14": "^1.0.1",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/eslint__js": "^8.42.3",
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@typescript-eslint/parser": "^8.12.2",
|
"@typescript-eslint/parser": "^8.12.2",
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"bootstrap": "5.2.3",
|
"bootstrap": "5.2.3",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
|
"ckeditor5": "^44.1.0",
|
||||||
"dompurify": "^3.1.0",
|
"dompurify": "^3.1.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-vue": "^9.30.0",
|
"eslint-plugin-vue": "^9.30.0",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
|
"intl-messageformat": "^10.5.11",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"marked": "^12.0.1",
|
|
||||||
"node-sass": "^8.0.0",
|
"node-sass": "^8.0.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^7.0.2",
|
||||||
@@ -55,6 +53,7 @@
|
|||||||
"@fullcalendar/timegrid": "^6.1.4",
|
"@fullcalendar/timegrid": "^6.1.4",
|
||||||
"@fullcalendar/vue3": "^6.1.4",
|
"@fullcalendar/vue3": "^6.1.4",
|
||||||
"@popperjs/core": "^2.9.2",
|
"@popperjs/core": "^2.9.2",
|
||||||
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/leaflet": "^1.9.3",
|
"@types/leaflet": "^1.9.3",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
@@ -73,14 +72,19 @@
|
|||||||
"vuex": "^4.0.0"
|
"vuex": "^4.0.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"Firefox ESR"
|
"defaults and fully supports es6-module and not dead"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev-server": "encore dev-server",
|
"dev-server": "encore dev-server",
|
||||||
"dev": "encore dev",
|
"dev": "encore dev",
|
||||||
"watch": "encore dev --watch",
|
"watch": "encore dev --watch",
|
||||||
"build": "encore production --progress",
|
"build": "encore production --progress",
|
||||||
"eslint": "npx eslint-baseline \"**/*.{js,ts,vue}\""
|
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||||
|
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
||||||
|
"specs-create-dir": "mkdir -p templates/api",
|
||||||
|
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||||
|
"version": "node --version",
|
||||||
|
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,10 @@ return static function (RectorConfig $rectorConfig): void {
|
|||||||
__DIR__ . '/src',
|
__DIR__ . '/src',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$rectorConfig->skip([
|
||||||
|
\Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::class => __DIR__ . 'src/Bundle/ChillMainBundle/Service/Notifier/LegacyOvhCloudFactory.php'
|
||||||
|
]);
|
||||||
|
|
||||||
$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml ');
|
$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml ');
|
||||||
$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php');
|
$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php');
|
||||||
|
|
||||||
|
@@ -15,10 +15,13 @@ use Chill\ActivityBundle\Entity\Activity;
|
|||||||
use Chill\ActivityBundle\Repository\ActivityRepository;
|
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||||
use Chill\MainBundle\Entity\Notification;
|
use Chill\MainBundle\Entity\Notification;
|
||||||
use Chill\MainBundle\Notification\NotificationHandlerInterface;
|
use Chill\MainBundle\Notification\NotificationHandlerInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Symfony\Component\Translation\TranslatableMessage;
|
||||||
|
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||||
|
|
||||||
final readonly class ActivityNotificationHandler implements NotificationHandlerInterface
|
final readonly class ActivityNotificationHandler implements NotificationHandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(private ActivityRepository $activityRepository) {}
|
public function __construct(private ActivityRepository $activityRepository, private TranslatableStringHelperInterface $translatableStringHelper) {}
|
||||||
|
|
||||||
public function getTemplate(Notification $notification, array $options = []): string
|
public function getTemplate(Notification $notification, array $options = []): string
|
||||||
{
|
{
|
||||||
@@ -37,4 +40,30 @@ final readonly class ActivityNotificationHandler implements NotificationHandlerI
|
|||||||
{
|
{
|
||||||
return Activity::class === $notification->getRelatedEntityClass();
|
return Activity::class === $notification->getRelatedEntityClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTitle(Notification $notification, array $options = []): TranslatableInterface
|
||||||
|
{
|
||||||
|
if (null === $activity = $this->getRelatedEntity($notification)) {
|
||||||
|
return new TranslatableMessage('activity.deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TranslatableMessage('activity.title', [
|
||||||
|
'date' => $activity->getDate(),
|
||||||
|
'type' => $this->translatableStringHelper->localize($activity->getActivityType()->getName()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAssociatedPersons(Notification $notification, array $options = []): array
|
||||||
|
{
|
||||||
|
if (null === $activity = $this->getRelatedEntity($notification)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity->getPersonsAssociated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntity(Notification $notification): ?Activity
|
||||||
|
{
|
||||||
|
return $this->activityRepository->find($notification->getRelatedEntityId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,13 +30,14 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li class="add-persons">
|
<li class="add-persons">
|
||||||
<add-persons
|
<add-persons
|
||||||
button-title="activity.add_persons"
|
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
modal-title="activity.add_persons"
|
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
:key="addPersons.key"
|
v-bind:key="addPersons.key"
|
||||||
:options="addPersonsOptions"
|
v-bind:options="addPersonsOptions"
|
||||||
@add-new-persons="addNewPersons"
|
@addNewPersons="addNewPersons"
|
||||||
ref="addPersons"
|
ref="addPersons"
|
||||||
/>
|
>
|
||||||
|
</add-persons>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</teleport>
|
</teleport>
|
||||||
@@ -47,6 +48,14 @@ import { mapState, mapGetters } from "vuex";
|
|||||||
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
||||||
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
||||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
|
import {
|
||||||
|
ACTIVITY_BLOC_PERSONS,
|
||||||
|
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||||
|
ACTIVITY_BLOC_THIRDPARTY,
|
||||||
|
ACTIVITY_BLOC_USERS,
|
||||||
|
ACTIVITY_ADD_PERSONS,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ConcernedGroups",
|
name: "ConcernedGroups",
|
||||||
@@ -55,18 +64,24 @@ export default {
|
|||||||
PersonsBloc,
|
PersonsBloc,
|
||||||
PersonText,
|
PersonText,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_ADD_PERSONS,
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
personsBlocs: [
|
personsBlocs: [
|
||||||
{
|
{
|
||||||
key: "persons",
|
key: "persons",
|
||||||
title: "activity.bloc_persons",
|
title: trans(ACTIVITY_BLOC_PERSONS),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: false,
|
included: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "personsAssociated",
|
key: "personsAssociated",
|
||||||
title: "activity.bloc_persons_associated",
|
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity
|
included: window.activity
|
||||||
? window.activity.activityType.personsVisible !== 0
|
? window.activity.activityType.personsVisible !== 0
|
||||||
@@ -82,7 +97,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "thirdparty",
|
key: "thirdparty",
|
||||||
title: "activity.bloc_thirdparty",
|
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity
|
included: window.activity
|
||||||
? window.activity.activityType.thirdPartiesVisible !== 0
|
? window.activity.activityType.thirdPartiesVisible !== 0
|
||||||
@@ -90,7 +105,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "users",
|
key: "users",
|
||||||
title: "activity.bloc_users",
|
title: trans(ACTIVITY_BLOC_USERS),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity
|
included: window.activity
|
||||||
? window.activity.activityType.usersVisible !== 0
|
? window.activity.activityType.usersVisible !== 0
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<teleport to="#location">
|
<teleport to="#location">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label :class="locationClassList">
|
<label :class="locationClassList">
|
||||||
{{ $t("activity.location") }}
|
{{ trans(ACTIVITY_LOCATION) }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
@@ -13,17 +13,17 @@
|
|||||||
open-direction="top"
|
open-direction="top"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:placeholder="$t('activity.choose_location')"
|
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||||
:custom-label="customLabel"
|
:custom-label="customLabel"
|
||||||
:select-label="$t('multiselect.select_label')"
|
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||||
:deselect-label="$t('multiselect.deselect_label')"
|
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||||
:selected-label="$t('multiselect.selected_label')"
|
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||||
:options="availableLocations"
|
:options="availableLocations"
|
||||||
group-values="locations"
|
group-values="locations"
|
||||||
group-label="locationGroup"
|
group-label="locationGroup"
|
||||||
v-model="location"
|
v-model="location"
|
||||||
/>
|
/>
|
||||||
<new-location :available-locations="availableLocations" />
|
<new-location v-bind:available-locations="availableLocations" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
@@ -33,6 +33,14 @@
|
|||||||
import { mapState, mapGetters } from "vuex";
|
import { mapState, mapGetters } from "vuex";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
import NewLocation from "./Location/NewLocation.vue";
|
import NewLocation from "./Location/NewLocation.vue";
|
||||||
|
import {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Location",
|
name: "Location",
|
||||||
@@ -40,6 +48,16 @@ export default {
|
|||||||
NewLocation,
|
NewLocation,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL,
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-sm btn-create" @click="openModal">
|
<a class="btn btn-sm btn-create" @click="openModal">
|
||||||
{{ $t("activity.create_new_location") }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -11,12 +11,12 @@
|
|||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<modal
|
<modal
|
||||||
v-if="modal.showModal"
|
v-if="modal.showModal"
|
||||||
:modal-dialog-class="modal.modalDialogClass"
|
:modalDialogClass="modal.modalDialogClass"
|
||||||
@close="modal.showModal = false"
|
@close="modal.showModal = false"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<h3 class="modal-title">
|
<h3 class="modal-title">
|
||||||
{{ $t("activity.create_new_location") }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
v-model="selectType"
|
v-model="selectType"
|
||||||
>
|
>
|
||||||
<option selected disabled value="">
|
<option selected disabled value="">
|
||||||
{{ $t("activity.choose_location_type") }}
|
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="t in locationTypes"
|
v-for="t in locationTypes"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label>{{
|
<label>{{
|
||||||
$t("activity.location_fields.type")
|
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,14 +60,14 @@
|
|||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="name">{{
|
<label for="name">{{
|
||||||
$t("activity.location_fields.name")
|
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<add-address
|
<add-address
|
||||||
:context="addAddress.context"
|
:context="addAddress.context"
|
||||||
:options="addAddress.options"
|
:options="addAddress.options"
|
||||||
:address-changed-callback="submitNewAddress"
|
:addressChangedCallback="submitNewAddress"
|
||||||
v-if="showAddAddress"
|
v-if="showAddAddress"
|
||||||
ref="addAddress"
|
ref="addAddress"
|
||||||
/>
|
/>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="phonenumber1">{{
|
<label for="phonenumber1">{{
|
||||||
$t("activity.location_fields.phonenumber1")
|
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="phonenumber2">{{
|
<label for="phonenumber2">{{
|
||||||
$t("activity.location_fields.phonenumber2")
|
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="email">{{
|
<label for="email">{{
|
||||||
$t("activity.location_fields.email")
|
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
class="btn btn-save"
|
class="btn btn-save"
|
||||||
@click.prevent="saveNewLocation"
|
@click.prevent="saveNewLocation"
|
||||||
>
|
>
|
||||||
{{ $t("action.save") }}
|
{{ trans(SAVE) }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</modal>
|
</modal>
|
||||||
@@ -126,6 +126,17 @@ import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"
|
|||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { getLocationTypes } from "../../api";
|
import { getLocationTypes } from "../../api";
|
||||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
import {
|
||||||
|
SAVE,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NewLocation",
|
name: "NewLocation",
|
||||||
@@ -133,6 +144,19 @@ export default {
|
|||||||
Modal,
|
Modal,
|
||||||
AddAddress,
|
AddAddress,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
SAVE,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
|
};
|
||||||
|
},
|
||||||
props: ["availableLocations"],
|
props: ["availableLocations"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialIssuesClassList">{{
|
<label :class="socialIssuesClassList">{{
|
||||||
$t("activity.social_issues")
|
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
@@ -12,8 +12,9 @@
|
|||||||
:key="issue.id"
|
:key="issue.id"
|
||||||
:issue="issue"
|
:issue="issue"
|
||||||
:selection="socialIssuesSelected"
|
:selection="socialIssuesSelected"
|
||||||
@update-selected="updateIssuesSelected"
|
@updateSelected="updateIssuesSelected"
|
||||||
/>
|
>
|
||||||
|
</check-social-issue>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
@@ -31,10 +32,11 @@
|
|||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
:loading="issueIsLoading"
|
:loading="issueIsLoading"
|
||||||
:placeholder="$t('activity.choose_other_social_issue')"
|
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||||
:options="socialIssuesOther"
|
:options="socialIssuesOther"
|
||||||
@select="addIssueInList"
|
@select="addIssueInList"
|
||||||
/>
|
>
|
||||||
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,36 +44,38 @@
|
|||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialActionsClassList">{{
|
<label :class="socialActionsClassList">{{
|
||||||
$t("activity.social_actions")
|
trans(ACTIVITY_SOCIAL_ACTIONS)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<div v-if="actionIsLoading === true">
|
<div v-if="actionIsLoading === true">
|
||||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg" />
|
<i
|
||||||
|
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
|
||||||
|
></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-else-if="socialIssuesSelected.length === 0"
|
v-else-if="socialIssuesSelected.length === 0"
|
||||||
class="inline-choice chill-no-data-statement mt-3"
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
>
|
>
|
||||||
{{ $t("activity.select_first_a_social_issue") }}
|
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<template v-else-if="socialActionsList.length > 0">
|
<template
|
||||||
<div
|
v-else-if="
|
||||||
v-if="
|
socialActionsList.length > 0 &&
|
||||||
socialIssuesSelected.length ||
|
(socialIssuesSelected.length ||
|
||||||
socialActionsSelected.length
|
socialActionsSelected.length)
|
||||||
"
|
"
|
||||||
|
>
|
||||||
|
<check-social-action
|
||||||
|
v-for="action in socialActionsList"
|
||||||
|
:key="action.id"
|
||||||
|
:action="action"
|
||||||
|
:selection="socialActionsSelected"
|
||||||
|
@updateSelected="updateActionsSelected"
|
||||||
>
|
>
|
||||||
<check-social-action
|
</check-social-action>
|
||||||
v-for="action in socialActionsList"
|
|
||||||
:key="action.id"
|
|
||||||
:action="action"
|
|
||||||
:selection="socialActionsSelected"
|
|
||||||
@update-selected="updateActionsSelected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -80,7 +84,7 @@
|
|||||||
"
|
"
|
||||||
class="inline-choice chill-no-data-statement mt-3"
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
>
|
>
|
||||||
{{ $t("activity.social_action_list_empty") }}
|
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,6 +96,14 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
||||||
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
||||||
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
||||||
|
import {
|
||||||
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SocialIssuesAcc",
|
name: "SocialIssuesAcc",
|
||||||
@@ -100,6 +112,16 @@ export default {
|
|||||||
CheckSocialAction,
|
CheckSocialAction,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
issueIsLoading: false,
|
issueIsLoading: false,
|
||||||
@@ -133,7 +155,7 @@ export default {
|
|||||||
this.actionAreLoaded = false;
|
this.actionAreLoaded = false;
|
||||||
getSocialIssues().then(
|
getSocialIssues().then(
|
||||||
(response) =>
|
(response) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve) => {
|
||||||
this.$store.commit("updateIssuesOther", response.results);
|
this.$store.commit("updateIssuesOther", response.results);
|
||||||
|
|
||||||
/* Add in list the issues already associated (if not yet listed)
|
/* Add in list the issues already associated (if not yet listed)
|
||||||
@@ -208,7 +230,7 @@ export default {
|
|||||||
this.actionIsLoading = true;
|
this.actionIsLoading = true;
|
||||||
getSocialActionByIssue(item.id).then(
|
getSocialActionByIssue(item.id).then(
|
||||||
(actions) =>
|
(actions) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve) => {
|
||||||
actions.results.forEach((action) => {
|
actions.results.forEach((action) => {
|
||||||
this.$store.commit("addActionInList", action);
|
this.$store.commit("addActionInList", action);
|
||||||
}, this);
|
}, this);
|
||||||
@@ -235,7 +257,6 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
span.multiselect__single {
|
span.multiselect__single {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
@@ -2,6 +2,7 @@ import "es6-promise/auto";
|
|||||||
import { createStore } from "vuex";
|
import { createStore } from "vuex";
|
||||||
import { postLocation } from "./api";
|
import { postLocation } from "./api";
|
||||||
import prepareLocations from "./store.locations.js";
|
import prepareLocations from "./store.locations.js";
|
||||||
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== "production";
|
const debug = process.env.NODE_ENV !== "production";
|
||||||
//console.log('window.activity', window.activity);
|
//console.log('window.activity', window.activity);
|
||||||
@@ -23,7 +24,9 @@ const removeIdFromValue = (string, id) => {
|
|||||||
const store = createStore({
|
const store = createStore({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
state: {
|
state: {
|
||||||
|
me: null,
|
||||||
activity: window.activity,
|
activity: window.activity,
|
||||||
|
accompanyingPeriodWorks: [],
|
||||||
socialIssuesOther: [],
|
socialIssuesOther: [],
|
||||||
socialActionsList: [],
|
socialActionsList: [],
|
||||||
availableLocations: [],
|
availableLocations: [],
|
||||||
@@ -39,7 +42,7 @@ const store = createStore({
|
|||||||
const allEntities = [
|
const allEntities = [
|
||||||
...store.getters.suggestedPersons,
|
...store.getters.suggestedPersons,
|
||||||
...store.getters.suggestedRequestor,
|
...store.getters.suggestedRequestor,
|
||||||
...store.getters.suggestedUser,
|
...store.getters.suggestedUsers,
|
||||||
...store.getters.suggestedResources,
|
...store.getters.suggestedResources,
|
||||||
];
|
];
|
||||||
const uniqueIds = [
|
const uniqueIds = [
|
||||||
@@ -78,16 +81,32 @@ const store = createStore({
|
|||||||
state.activity.activityType.thirdPartiesVisible !== 0),
|
state.activity.activityType.thirdPartiesVisible !== 0),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestedUser(state) {
|
suggestedUsers(state) {
|
||||||
const existingUserIds = state.activity.users.map((p) => p.id);
|
const existingUserIds = state.activity.users.map((p) => p.id);
|
||||||
return state.activity.activityType.usersVisible === 0
|
let suggestedUsers =
|
||||||
? []
|
state.activity.activityType.usersVisible === 0
|
||||||
: [state.activity.accompanyingPeriod.user].filter(
|
? []
|
||||||
(u) => u !== null && !existingUserIds.includes(u.id),
|
: [state.activity.accompanyingPeriod.user].filter(
|
||||||
);
|
(u) => u !== null && !existingUserIds.includes(u.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.accompanyingPeriodWorks.forEach((work) => {
|
||||||
|
work.referrers.forEach((r) => {
|
||||||
|
if (!existingUserIds.includes(r.id)) {
|
||||||
|
suggestedUsers.push(r);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Add the current user from the state
|
||||||
|
if (state.me && !existingUserIds.includes(state.me.id)) {
|
||||||
|
suggestedUsers.push(state.me);
|
||||||
|
}
|
||||||
|
// console.log("suggested users", suggestedUsers);
|
||||||
|
|
||||||
|
return suggestedUsers;
|
||||||
},
|
},
|
||||||
suggestedResources(state) {
|
suggestedResources(state) {
|
||||||
const resources = state.activity.accompanyingPeriod.resources;
|
// const resources = state.activity.accompanyingPeriod.resources;
|
||||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||||
(p) => p.id,
|
(p) => p.id,
|
||||||
@@ -111,6 +130,9 @@ const store = createStore({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
setWhoAmI(state, me) {
|
||||||
|
state.me = me;
|
||||||
|
},
|
||||||
// SocialIssueAcc
|
// SocialIssueAcc
|
||||||
addIssueInList(state, issue) {
|
addIssueInList(state, issue) {
|
||||||
//console.log('add issue list', issue.id);
|
//console.log('add issue list', issue.id);
|
||||||
@@ -208,6 +230,9 @@ const store = createStore({
|
|||||||
addAvailableLocationGroup(state, group) {
|
addAvailableLocationGroup(state, group) {
|
||||||
state.availableLocations.push(group);
|
state.availableLocations.push(group);
|
||||||
},
|
},
|
||||||
|
setAccompanyingPeriodWorks(state, works) {
|
||||||
|
state.accompanyingPeriodWorks = works;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addIssueSelected({ commit }, issue) {
|
addIssueSelected({ commit }, issue) {
|
||||||
@@ -326,9 +351,29 @@ const store = createStore({
|
|||||||
}
|
}
|
||||||
commit("updateLocation", value);
|
commit("updateLocation", value);
|
||||||
},
|
},
|
||||||
|
async fetchAccompanyingPeriodWorks({ state, commit }) {
|
||||||
|
const accompanyingPeriodId = state.activity.accompanyingPeriod.id;
|
||||||
|
const url = `/api/1.0/person/accompanying-course/${accompanyingPeriodId}/works.json`;
|
||||||
|
try {
|
||||||
|
const works = await makeFetch("GET", url);
|
||||||
|
// console.log("works", works);
|
||||||
|
commit("setAccompanyingPeriodWorks", works);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch accompanying period works:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getWhoAmI({ commit }) {
|
||||||
|
const url = `/api/1.0/main/whoami.json`;
|
||||||
|
makeFetch("GET", url).then((user) => {
|
||||||
|
commit("setWhoAmI", user);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store.dispatch("getWhoAmI");
|
||||||
|
|
||||||
prepareLocations(store);
|
prepareLocations(store);
|
||||||
|
store.dispatch("fetchAccompanyingPeriodWorks");
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
@@ -1,83 +1,3 @@
|
|||||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
|
||||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
|
||||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
|
||||||
|
|
||||||
{% set person_id = null %}
|
|
||||||
{% if activity.person %}
|
|
||||||
{% set person_id = activity.person.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set accompanying_course_id = null %}
|
|
||||||
{% if activity.accompanyingPeriod %}
|
|
||||||
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
||||||
<div class="item-row">
|
{{ include('@ChillActivity/GenericDoc/activity_document_row.html.twig') }}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
<div class="badge-activity-type">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">
|
|
||||||
{{ activity.type.name | localize_translatable_string }}
|
|
||||||
{% if activity.emergency %}
|
|
||||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.createdAt|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
|
||||||
<li>
|
|
||||||
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,81 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
{% set person_id = null %}
|
||||||
|
{% if activity.person %}
|
||||||
|
{% set person_id = activity.person.id %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set accompanying_course_id = null %}
|
||||||
|
{% if activity.accompanyingPeriod %}
|
||||||
|
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<div class="badge-activity-type">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
{{ activity.type.name | localize_translatable_string }}
|
||||||
|
{% if activity.emergency %}
|
||||||
|
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.createdAt|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||||
|
<li>
|
||||||
|
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@@ -143,7 +143,10 @@ class ListActivitiesByAccompanyingPeriodContext implements
|
|||||||
array_filter(
|
array_filter(
|
||||||
$works,
|
$works,
|
||||||
function ($work) use ($user) {
|
function ($work) use ($user) {
|
||||||
$workUsernames = array_map(static fn (User $user) => $user['username'], $work['referrers'] ?? []);
|
$workUsernames = [];
|
||||||
|
foreach ($work['referrers'] as $referrer) {
|
||||||
|
$workUsernames[] = $referrer['username'];
|
||||||
|
}
|
||||||
|
|
||||||
return \in_array($user->getUserIdentifier(), $workUsernames, true);
|
return \in_array($user->getUserIdentifier(), $workUsernames, true);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\ActivityBundle\Service\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
|
||||||
|
use Chill\ActivityBundle\Service\GenericDoc\Renderers\AccompanyingPeriodActivityGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodActivityGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private StoredObjectRepositoryInterface $storedObjectRepository,
|
||||||
|
private AccompanyingPeriodActivityGenericDocRenderer $renderer,
|
||||||
|
private Environment $twig,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodActivityGenericDocProvider::KEY === $genericDocDTO->key
|
||||||
|
&& 'json' == $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
$storedObject = $this->storedObjectRepository->find($genericDocDTO->identifiers['id']);
|
||||||
|
|
||||||
|
if (null === $storedObject) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $storedObject->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -13,10 +13,12 @@ namespace Chill\ActivityBundle\Service\GenericDoc\Providers;
|
|||||||
|
|
||||||
use Chill\ActivityBundle\Entity\Activity;
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
||||||
|
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
@@ -34,8 +36,47 @@ final readonly class AccompanyingPeriodActivityGenericDocProvider implements Gen
|
|||||||
private EntityManagerInterface $em,
|
private EntityManagerInterface $em,
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
|
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
|
||||||
|
private ActivityRepository $activityRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
if (null === $activity = $this->getRelatedEntity($genericDocDTO->key, $genericDocDTO->identifiers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity->getDocuments()->findFirst(fn (int $key, StoredObject $storedObject) => $storedObject->getId() === $genericDocDTO->identifiers['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key && array_key_exists('activity_id', $identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRelatedEntity(string $key, array $identifiers): ?Activity
|
||||||
|
{
|
||||||
|
return $this->activityRepository->find($identifiers['activity_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $activity = $this->getRelatedEntity($key, $identifiers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($activity->getDate()),
|
||||||
|
$activity->getAccompanyingPeriod(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||||
|
@@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
|||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) {}
|
public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) {}
|
||||||
@@ -29,7 +32,8 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
return '@ChillActivity/GenericDoc/activity_document.html.twig';
|
return ($options['row-only'] ?? false) ? '@ChillActivity/GenericDoc/activity_document_row.html.twig' :
|
||||||
|
'@ChillActivity/GenericDoc/activity_document.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||||
@@ -38,6 +42,7 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
|
|||||||
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
|
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
|
||||||
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
|
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,3 +14,5 @@ export:
|
|||||||
describe_action_with_subject: >-
|
describe_action_with_subject: >-
|
||||||
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}, et un de ces sujets choisis: {reasons}
|
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}, et un de ces sujets choisis: {reasons}
|
||||||
|
|
||||||
|
activity:
|
||||||
|
title: Échange du {date, date, long} - {type}
|
||||||
|
@@ -101,6 +101,33 @@ activity:
|
|||||||
Insert a document: Insérer un document
|
Insert a document: Insérer un document
|
||||||
Remove a document: Supprimer le document
|
Remove a document: Supprimer le document
|
||||||
comment: Commentaire
|
comment: Commentaire
|
||||||
|
deleted: Échange supprimé
|
||||||
|
|
||||||
|
errors: Le formulaire contient des erreurs
|
||||||
|
social_issues: Problématiques sociales
|
||||||
|
choose_other_social_issue: Ajouter une autre problématique sociale...
|
||||||
|
social_actions: Actions d'accompagnement
|
||||||
|
select_first_a_social_issue: Sélectionnez d'abord une problématique sociale
|
||||||
|
social_action_list_empty: Aucune action sociale disponible
|
||||||
|
add_persons: Ajouter des personnes concernées
|
||||||
|
bloc_persons: Usagers
|
||||||
|
bloc_persons_associated: Usagers du parcours
|
||||||
|
bloc_persons_not_associated: Tiers non-pro.
|
||||||
|
bloc_thirdparty: Tiers professionnels
|
||||||
|
bloc_users: T(M)S
|
||||||
|
location: Localisation
|
||||||
|
choose_location: Choisissez une localisation
|
||||||
|
choose_location_type: Choisissez un type de localisation
|
||||||
|
create_new_location: Créer une nouvelle localisation
|
||||||
|
location_fields:
|
||||||
|
name: Nom
|
||||||
|
type: Type
|
||||||
|
phonenumber1: Téléphone
|
||||||
|
phonenumber2: Autre téléphone
|
||||||
|
email: Adresse courriel
|
||||||
|
create_address: Créer une adresse
|
||||||
|
edit_address: Modifier l'adresse
|
||||||
|
|
||||||
No documents: Aucun document
|
No documents: Aucun document
|
||||||
|
|
||||||
# activity filter in list page
|
# activity filter in list page
|
||||||
|
@@ -21,9 +21,7 @@ namespace Chill\CalendarBundle\Command;
|
|||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Service\ShortMessageNotification\ShortMessageForCalendarBuilderInterface;
|
use Chill\CalendarBundle\Service\ShortMessageNotification\ShortMessageForCalendarBuilderInterface;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
|
|
||||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||||
use Chill\MainBundle\Service\ShortMessage\ShortMessageTransporterInterface;
|
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Repository\PersonRepository;
|
use Chill\PersonBundle\Repository\PersonRepository;
|
||||||
use libphonenumber\PhoneNumber;
|
use libphonenumber\PhoneNumber;
|
||||||
@@ -36,6 +34,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
use Symfony\Component\Notifier\TexterInterface;
|
||||||
|
|
||||||
class SendTestShortMessageOnCalendarCommand extends Command
|
class SendTestShortMessageOnCalendarCommand extends Command
|
||||||
{
|
{
|
||||||
@@ -44,9 +43,8 @@ class SendTestShortMessageOnCalendarCommand extends Command
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PersonRepository $personRepository,
|
private readonly PersonRepository $personRepository,
|
||||||
private readonly PhoneNumberUtil $phoneNumberUtil,
|
private readonly PhoneNumberUtil $phoneNumberUtil,
|
||||||
private readonly PhoneNumberHelperInterface $phoneNumberHelper,
|
|
||||||
private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder,
|
private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder,
|
||||||
private readonly ShortMessageTransporterInterface $transporter,
|
private readonly TexterInterface $transporter,
|
||||||
private readonly UserRepositoryInterface $userRepository,
|
private readonly UserRepositoryInterface $userRepository,
|
||||||
) {
|
) {
|
||||||
parent::__construct('chill:calendar:test-send-short-message');
|
parent::__construct('chill:calendar:test-send-short-message');
|
||||||
@@ -152,10 +150,6 @@ class SendTestShortMessageOnCalendarCommand extends Command
|
|||||||
return $phone;
|
return $phone;
|
||||||
});
|
});
|
||||||
|
|
||||||
$phone = $helper->ask($input, $output, $question);
|
|
||||||
|
|
||||||
$question = new ConfirmationQuestion('really send the message to the phone ?');
|
|
||||||
$reallySend = (bool) $helper->ask($input, $output, $question);
|
|
||||||
|
|
||||||
$messages = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar);
|
$messages = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar);
|
||||||
|
|
||||||
@@ -165,8 +159,12 @@ class SendTestShortMessageOnCalendarCommand extends Command
|
|||||||
|
|
||||||
foreach ($messages as $key => $message) {
|
foreach ($messages as $key => $message) {
|
||||||
$output->writeln("The short message for SMS {$key} will be: ");
|
$output->writeln("The short message for SMS {$key} will be: ");
|
||||||
$output->writeln($message->getContent());
|
$output->writeln($message->getSubject());
|
||||||
$message->setPhoneNumber($phone);
|
$output->writeln('The destination number will be:');
|
||||||
|
$output->writeln($message->getPhone());
|
||||||
|
|
||||||
|
$question = new ConfirmationQuestion('really send the message to the phone ?');
|
||||||
|
$reallySend = (bool) $helper->ask($input, $output, $question);
|
||||||
|
|
||||||
if ($reallySend) {
|
if ($reallySend) {
|
||||||
$this->transporter->send($message);
|
$this->transporter->send($message);
|
||||||
|
@@ -15,6 +15,7 @@ use Chill\CalendarBundle\Repository\CalendarRepository;
|
|||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||||||
|
|
||||||
class CalendarAPIController extends ApiController
|
class CalendarAPIController extends ApiController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CalendarRepository $calendarRepository) {}
|
public function __construct(private readonly CalendarRepository $calendarRepository, ManagerRegistry $managerRegistry)
|
||||||
|
{
|
||||||
|
parent::__construct($managerRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route(path: '/api/1.0/calendar/calendar/by-user/{id}.{_format}', name: 'chill_api_single_calendar_list_by-user', requirements: ['_format' => 'json'])]
|
#[Route(path: '/api/1.0/calendar/calendar/by-user/{id}.{_format}', name: 'chill_api_single_calendar_list_by-user', requirements: ['_format' => 'json'])]
|
||||||
public function listByUser(User $user, Request $request, string $_format): JsonResponse
|
public function listByUser(User $user, Request $request, string $_format): JsonResponse
|
||||||
|
@@ -15,6 +15,7 @@ use Chill\CalendarBundle\Repository\CalendarRangeRepository;
|
|||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||||||
|
|
||||||
class CalendarRangeAPIController extends ApiController
|
class CalendarRangeAPIController extends ApiController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository) {}
|
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, ManagerRegistry $managerRegistry)
|
||||||
|
{
|
||||||
|
parent::__construct($managerRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route(path: '/api/1.0/calendar/calendar-range-available/{id}.{_format}', name: 'chill_api_single_calendar_range_available', requirements: ['_format' => 'json'])]
|
#[Route(path: '/api/1.0/calendar/calendar-range-available/{id}.{_format}', name: 'chill_api_single_calendar_range_available', requirements: ['_format' => 'json'])]
|
||||||
public function availableRanges(User $user, Request $request, string $_format): JsonResponse
|
public function availableRanges(User $user, Request $request, string $_format): JsonResponse
|
||||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Repository;
|
namespace Chill\CalendarBundle\Repository;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
@@ -49,4 +50,21 @@ class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryIn
|
|||||||
{
|
{
|
||||||
return CalendarDoc::class;
|
return CalendarDoc::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StoredObject|int $storedObject the StoredObject instance, or the id of the stored object
|
||||||
|
*/
|
||||||
|
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc
|
||||||
|
{
|
||||||
|
$storedObjectId = $storedObject instanceof StoredObject ? $storedObject->getId() : $storedObject;
|
||||||
|
|
||||||
|
$qb = $this->repository->createQueryBuilder('c');
|
||||||
|
$qb->where(
|
||||||
|
$qb->expr()->eq(':storedObject', 'c.storedObject')
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setParameter('storedObject', $storedObjectId);
|
||||||
|
|
||||||
|
return $qb->getQuery()->getOneOrNullResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Repository;
|
namespace Chill\CalendarBundle\Repository;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
|
||||||
interface CalendarDocRepositoryInterface
|
interface CalendarDocRepositoryInterface
|
||||||
{
|
{
|
||||||
@@ -29,5 +30,7 @@ interface CalendarDocRepositoryInterface
|
|||||||
|
|
||||||
public function findOneBy(array $criteria): ?CalendarDoc;
|
public function findOneBy(array $criteria): ?CalendarDoc;
|
||||||
|
|
||||||
|
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc;
|
||||||
|
|
||||||
public function getClassName();
|
public function getClassName();
|
||||||
}
|
}
|
||||||
|
@@ -106,7 +106,10 @@ export default {
|
|||||||
});
|
});
|
||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
addExternals(state, externalEvents: (EventInput & { id: string })[]) {
|
addExternals(
|
||||||
|
state: CalendarRangesState,
|
||||||
|
externalEvents: (EventInput & { id: string })[],
|
||||||
|
) {
|
||||||
const toAdd = externalEvents.filter(
|
const toAdd = externalEvents.filter(
|
||||||
(r) => !state.rangesIndex.has(r.id),
|
(r) => !state.rangesIndex.has(r.id),
|
||||||
);
|
);
|
||||||
@@ -160,7 +163,7 @@ export default {
|
|||||||
state.key = state.key + 1;
|
state.key = state.key + 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateRange(state, range: CalendarRange) {
|
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
||||||
const found = state.ranges.find(
|
const found = state.ranges.find(
|
||||||
(r) => r.calendarRangeId === range.id && r.is === "range",
|
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||||
);
|
);
|
||||||
@@ -207,7 +210,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
createRange(
|
createRange(
|
||||||
ctx,
|
ctx: Context,
|
||||||
{
|
{
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
@@ -253,10 +256,10 @@ export default {
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteRange(ctx, calendarRangeId: number) {
|
deleteRange(ctx: Context, calendarRangeId: number) {
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
|
||||||
makeFetch<undefined, never>("DELETE", url).then((_) => {
|
makeFetch<undefined, never>("DELETE", url).then(() => {
|
||||||
ctx.commit("removeRange", calendarRangeId);
|
ctx.commit("removeRange", calendarRangeId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -347,10 +350,10 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then((_) => Promise.resolve(null));
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
},
|
},
|
||||||
copyFromWeekToAnotherWeek(
|
copyFromWeekToAnotherWeek(
|
||||||
ctx,
|
ctx: Context,
|
||||||
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||||
): Promise<null> {
|
): Promise<null> {
|
||||||
const rangesToCopy: EventInputCalendarRange[] =
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
@@ -371,7 +374,7 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then((_) => Promise.resolve(null));
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as Module<CalendarRangesState, State>;
|
} as Module<CalendarRangesState, State>;
|
||||||
|
@@ -5,71 +5,5 @@
|
|||||||
{% set c = document.calendar %}
|
{% set c = document.calendar %}
|
||||||
|
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row">
|
{{ include('@ChillCalendar/GenericDoc/calendar_document_row.html.twig') }}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.storedObject.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.storedObject.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% if c.accompanyingPeriod is not null and context == 'person' %}
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<span class="badge-calendar">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">
|
|
||||||
{{ 'Calendar'|trans }}
|
|
||||||
{% if c.endDate.diff(c.startDate).days >= 1 %}
|
|
||||||
{{ c.startDate|format_datetime('short', 'short') }}
|
|
||||||
- {{ c.endDate|format_datetime('short', 'short') }}
|
|
||||||
{% else %}
|
|
||||||
{{ c.startDate|format_datetime('short', 'short') }}
|
|
||||||
- {{ c.endDate|format_datetime('none', 'short') }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.storedObject.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.storedObject.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,75 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
{% set c = document.calendar %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.storedObject.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.storedObject.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if c.accompanyingPeriod is not null and context == 'person' %}
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="badge-calendar">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
{{ 'Calendar'|trans }}
|
||||||
|
{% if c.endDate.diff(c.startDate).days >= 1 %}
|
||||||
|
{{ c.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ c.endDate|format_datetime('short', 'short') }}
|
||||||
|
{% else %}
|
||||||
|
{{ c.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ c.endDate|format_datetime('none', 'short') }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.storedObject.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.storedObject.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\CalendarBundle\Service\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
|
||||||
|
use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider;
|
||||||
|
use Chill\CalendarBundle\Service\GenericDoc\Renderers\AccompanyingPeriodCalendarGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodCalendarGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private AccompanyingPeriodCalendarGenericDocRenderer $renderer,
|
||||||
|
private CalendarDocRepositoryInterface $calendarDocRepository,
|
||||||
|
private Environment $twig,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodCalendarGenericDocProvider::KEY === $genericDocDTO->key && 'json' === $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (null === $calendarDoc = $this->calendarDocRepository->find($genericDocDTO->identifiers['id'])) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $calendarDoc->getStoredObject()->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -13,10 +13,12 @@ namespace Chill\CalendarBundle\Service\GenericDoc\Providers;
|
|||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
|
||||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
@@ -38,8 +40,38 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private EntityManagerInterface $em,
|
private EntityManagerInterface $em,
|
||||||
|
private CalendarDocRepositoryInterface $calendarRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->calendarRepository->find($genericDocDTO->identifiers['id'])?->getStoredObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key && array_key_exists('id', $identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $calendarDoc = $this->calendarRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($calendarDoc->getCreatedAt() ?? new \DateTimeImmutable('now')),
|
||||||
|
$calendarDoc->getCalendar()->getAccompanyingPeriod() ?? $calendarDoc->getCalendar()->getPerson()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws MappingException
|
* @throws MappingException
|
||||||
*/
|
*/
|
||||||
@@ -82,7 +114,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
|||||||
[Types::INTEGER]
|
[Types::INTEGER]
|
||||||
);
|
);
|
||||||
|
|
||||||
return $query;
|
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||||
|
@@ -17,6 +17,9 @@ use Chill\CalendarBundle\Service\GenericDoc\Providers\PersonCalendarGenericDocPr
|
|||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(private CalendarDocRepository $repository) {}
|
public function __construct(private CalendarDocRepository $repository) {}
|
||||||
@@ -28,7 +31,8 @@ final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements Gen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
return '@ChillCalendar/GenericDoc/calendar_document.html.twig';
|
return $options['row-only'] ?? false ? '@ChillCalendar/GenericDoc/calendar_document_row.html.twig'
|
||||||
|
: '@ChillCalendar/GenericDoc/calendar_document.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||||
@@ -36,6 +40,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements Gen
|
|||||||
return [
|
return [
|
||||||
'document' => $this->repository->find($genericDocDTO->identifiers['id']),
|
'document' => $this->repository->find($genericDocDTO->identifiers['id']),
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,11 +21,17 @@ namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
|||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Notifier\TexterInterface;
|
||||||
|
|
||||||
class BulkCalendarShortMessageSender
|
class BulkCalendarShortMessageSender
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CalendarForShortMessageProvider $provider, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly MessageBusInterface $messageBus, private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder) {}
|
public function __construct(
|
||||||
|
private readonly CalendarForShortMessageProvider $provider,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly LoggerInterface $logger,
|
||||||
|
private readonly TexterInterface $texter,
|
||||||
|
private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function sendBulkMessageToEligibleCalendars()
|
public function sendBulkMessageToEligibleCalendars()
|
||||||
{
|
{
|
||||||
@@ -36,7 +42,7 @@ class BulkCalendarShortMessageSender
|
|||||||
$smses = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar);
|
$smses = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar);
|
||||||
|
|
||||||
foreach ($smses as $sms) {
|
foreach ($smses as $sms) {
|
||||||
$this->messageBus->dispatch($sms);
|
$this->texter->send($sms);
|
||||||
++$countSms;
|
++$countSms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,12 +19,26 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\MainBundle\Service\ShortMessage\ShortMessage;
|
use libphonenumber\PhoneNumberFormat;
|
||||||
|
use libphonenumber\PhoneNumberUtil;
|
||||||
|
use Symfony\Component\Notifier\Message\SmsMessage;
|
||||||
|
|
||||||
class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBuilderInterface
|
class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBuilderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private readonly \Twig\Environment $engine) {}
|
private readonly PhoneNumberUtil $phoneUtil;
|
||||||
|
|
||||||
|
public function __construct(private readonly \Twig\Environment $engine)
|
||||||
|
{
|
||||||
|
$this->phoneUtil = PhoneNumberUtil::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<SmsMessage>
|
||||||
|
*
|
||||||
|
* @throws \Twig\Error\LoaderError
|
||||||
|
* @throws \Twig\Error\RuntimeError
|
||||||
|
* @throws \Twig\Error\SyntaxError
|
||||||
|
*/
|
||||||
public function buildMessageForCalendar(Calendar $calendar): array
|
public function buildMessageForCalendar(Calendar $calendar): array
|
||||||
{
|
{
|
||||||
if (true !== $calendar->getSendSMS()) {
|
if (true !== $calendar->getSendSMS()) {
|
||||||
@@ -39,16 +53,14 @@ class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Calendar::SMS_PENDING === $calendar->getSmsStatus()) {
|
if (Calendar::SMS_PENDING === $calendar->getSmsStatus()) {
|
||||||
$toUsers[] = new ShortMessage(
|
$toUsers[] = new SmsMessage(
|
||||||
|
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
||||||
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]),
|
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]),
|
||||||
$person->getMobilenumber(),
|
|
||||||
ShortMessage::PRIORITY_LOW
|
|
||||||
);
|
);
|
||||||
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus()) {
|
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus()) {
|
||||||
$toUsers[] = new ShortMessage(
|
$toUsers[] = new SmsMessage(
|
||||||
|
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
||||||
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),
|
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),
|
||||||
$person->getMobilenumber(),
|
|
||||||
ShortMessage::PRIORITY_LOW
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,12 +19,12 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\MainBundle\Service\ShortMessage\ShortMessage;
|
use Symfony\Component\Notifier\Message\SmsMessage;
|
||||||
|
|
||||||
interface ShortMessageForCalendarBuilderInterface
|
interface ShortMessageForCalendarBuilderInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return array|ShortMessage[]
|
* @return list<SmsMessage>
|
||||||
*/
|
*/
|
||||||
public function buildMessageForCalendar(Calendar $calendar): array;
|
public function buildMessageForCalendar(Calendar $calendar): array;
|
||||||
}
|
}
|
||||||
|
@@ -23,17 +23,16 @@ use Chill\CalendarBundle\Service\ShortMessageNotification\BulkCalendarShortMessa
|
|||||||
use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider;
|
use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider;
|
||||||
use Chill\CalendarBundle\Service\ShortMessageNotification\ShortMessageForCalendarBuilderInterface;
|
use Chill\CalendarBundle\Service\ShortMessageNotification\ShortMessageForCalendarBuilderInterface;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Service\ShortMessage\ShortMessage;
|
|
||||||
use Chill\MainBundle\Test\PrepareUserTrait;
|
use Chill\MainBundle\Test\PrepareUserTrait;
|
||||||
use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper;
|
use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use libphonenumber\PhoneNumberUtil;
|
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
use Symfony\Component\Messenger\Envelope;
|
use Symfony\Component\Notifier\Message\SentMessage;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Notifier\Message\SmsMessage;
|
||||||
|
use Symfony\Component\Notifier\TexterInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@@ -101,24 +100,23 @@ final class BulkCalendarShortMessageSenderTest extends KernelTestCase
|
|||||||
$messageBuilder->buildMessageForCalendar(Argument::type(Calendar::class))
|
$messageBuilder->buildMessageForCalendar(Argument::type(Calendar::class))
|
||||||
->willReturn(
|
->willReturn(
|
||||||
[
|
[
|
||||||
new ShortMessage(
|
new SmsMessage(
|
||||||
|
'+32470123456',
|
||||||
'content',
|
'content',
|
||||||
PhoneNumberUtil::getInstance()->parse('+32470123456', 'BE'),
|
|
||||||
ShortMessage::PRIORITY_MEDIUM
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$bus = $this->prophesize(MessageBusInterface::class);
|
$texter = $this->prophesize(TexterInterface::class);
|
||||||
$bus->dispatch(Argument::type(ShortMessage::class))
|
$texter->send(Argument::type(SmsMessage::class))
|
||||||
->willReturn(new Envelope(new \stdClass()))
|
->will(fn ($args): SentMessage => new SentMessage($args[0], 'sms'))
|
||||||
->shouldBeCalledTimes(1);
|
->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
$bulk = new BulkCalendarShortMessageSender(
|
$bulk = new BulkCalendarShortMessageSender(
|
||||||
$provider->reveal(),
|
$provider->reveal(),
|
||||||
$em,
|
$em,
|
||||||
new NullLogger(),
|
new NullLogger(),
|
||||||
$bus->reveal(),
|
$texter->reveal(),
|
||||||
$messageBuilder->reveal()
|
$messageBuilder->reveal()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -23,7 +23,6 @@ use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultShortMessageFor
|
|||||||
use Chill\MainBundle\Entity\Location;
|
use Chill\MainBundle\Entity\Location;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use libphonenumber\PhoneNumberFormat;
|
|
||||||
use libphonenumber\PhoneNumberUtil;
|
use libphonenumber\PhoneNumberUtil;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
@@ -90,10 +89,9 @@ final class DefaultShortMessageForCalendarBuilderTest extends TestCase
|
|||||||
$this->assertCount(1, $sms);
|
$this->assertCount(1, $sms);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'+32470123456',
|
'+32470123456',
|
||||||
$this->phoneNumberUtil->format($sms[0]->getPhoneNumber(), PhoneNumberFormat::E164)
|
$sms[0]->getPhone()
|
||||||
);
|
);
|
||||||
$this->assertEquals('message content', $sms[0]->getContent());
|
$this->assertEquals('message content', $sms[0]->getSubject());
|
||||||
$this->assertEquals('low', $sms[0]->getPriority());
|
|
||||||
|
|
||||||
// if the calendar is canceled
|
// if the calendar is canceled
|
||||||
$calendar
|
$calendar
|
||||||
@@ -105,9 +103,8 @@ final class DefaultShortMessageForCalendarBuilderTest extends TestCase
|
|||||||
$this->assertCount(1, $sms);
|
$this->assertCount(1, $sms);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'+32470123456',
|
'+32470123456',
|
||||||
$this->phoneNumberUtil->format($sms[0]->getPhoneNumber(), PhoneNumberFormat::E164)
|
$sms[0]->getRecipientId(),
|
||||||
);
|
);
|
||||||
$this->assertEquals('message canceled', $sms[0]->getContent());
|
$this->assertEquals('message canceled', $sms[0]->getSubject());
|
||||||
$this->assertEquals('low', $sms[0]->getPriority());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Controller\GenericDocForAccompanyingPeriodListApiController;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
|
use Chill\MainBundle\Pagination\Paginator;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class GenericDocForAccompanyingPeriodListApiControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSmokeTest(): void
|
||||||
|
{
|
||||||
|
$accompanyingPeriod = new AccompanyingPeriod();
|
||||||
|
|
||||||
|
$docs = [
|
||||||
|
new GenericDocDTO('dummy', ['id' => 9], new \DateTimeImmutable('2024-08-01'), $accompanyingPeriod),
|
||||||
|
new GenericDocDTO('dummy', ['id' => 1], new \DateTimeImmutable('2024-09-01'), $accompanyingPeriod),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$manager = $this->createMock(ManagerInterface::class);
|
||||||
|
$manager->method('findDocForAccompanyingPeriod')->with($accompanyingPeriod)->willReturn($docs);
|
||||||
|
$manager->method('countDocForAccompanyingPeriod')->with($accompanyingPeriod)->willReturn(2);
|
||||||
|
|
||||||
|
$paginatorFactory = $this->createMock(PaginatorFactoryInterface::class);
|
||||||
|
$paginatorFactory->method('create')->with(2)->willReturn(new Paginator(
|
||||||
|
2,
|
||||||
|
20,
|
||||||
|
1,
|
||||||
|
'/route',
|
||||||
|
[],
|
||||||
|
$this->createMock(UrlGeneratorInterface::class),
|
||||||
|
'page',
|
||||||
|
'item-per-page'
|
||||||
|
));
|
||||||
|
|
||||||
|
$serializer = $this->createMock(SerializerInterface::class);
|
||||||
|
$serializer->method('serialize')->with($this->isInstanceOf(Collection::class))->willReturn(
|
||||||
|
json_encode(['docs' => []])
|
||||||
|
);
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->once())->method('isGranted')
|
||||||
|
->with(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)->willReturn(true);
|
||||||
|
|
||||||
|
$controller = new GenericDocForAccompanyingPeriodListApiController(
|
||||||
|
$manager,
|
||||||
|
$security,
|
||||||
|
$paginatorFactory,
|
||||||
|
$serializer,
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $controller($accompanyingPeriod);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
$this->assertEquals('{"docs":[]}', $response->getContent());
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle;
|
|||||||
use Chill\DocStoreBundle\DependencyInjection\Compiler\StorageConfigurationCompilerPass;
|
use Chill\DocStoreBundle\DependencyInjection\Compiler\StorageConfigurationCompilerPass;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
@@ -28,6 +29,8 @@ class ChillDocStoreBundle extends Bundle
|
|||||||
->addTag('chill_doc_store.generic_doc_person_provider');
|
->addTag('chill_doc_store.generic_doc_person_provider');
|
||||||
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
||||||
->addTag('chill_doc_store.generic_doc_renderer');
|
->addTag('chill_doc_store.generic_doc_renderer');
|
||||||
|
$container->registerForAutoconfiguration(GenericDocNormalizerInterface::class)
|
||||||
|
->addTag('chill_doc_store.generic_doc_metadata_normalizer');
|
||||||
|
|
||||||
$container->addCompilerPass(new StorageConfigurationCompilerPass());
|
$container->addCompilerPass(new StorageConfigurationCompilerPass());
|
||||||
}
|
}
|
||||||
|
@@ -92,13 +92,14 @@ class DocumentCategoryController extends AbstractController
|
|||||||
|
|
||||||
$nextId = $em
|
$nextId = $em
|
||||||
->createQuery(
|
->createQuery(
|
||||||
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
|
'SELECT (CASE WHEN MAX(c.idInsideBundle) IS NULL THEN 1 ELSE MAX(c.idInsideBundle) + 1 END)
|
||||||
|
FROM ChillDocStoreBundle:DocumentCategory c'
|
||||||
)
|
)
|
||||||
->getSingleResult();
|
->getSingleScalarResult();
|
||||||
|
|
||||||
$documentCategory = new DocumentCategory(
|
$documentCategory = new DocumentCategory(
|
||||||
ChillDocStoreBundle::class,
|
ChillDocStoreBundle::class,
|
||||||
reset($nextId)
|
$nextId
|
||||||
);
|
);
|
||||||
|
|
||||||
$documentCategory
|
$documentCategory
|
||||||
|
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\Controller;
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||||
@@ -25,7 +25,7 @@ final readonly class GenericDocForAccompanyingPeriodController
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||||
private Manager $manager,
|
private ManagerInterface $manager,
|
||||||
private PaginatorFactory $paginator,
|
private PaginatorFactory $paginator,
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private \Twig\Environment $twig,
|
private \Twig\Environment $twig,
|
||||||
@@ -68,6 +68,9 @@ final readonly class GenericDocForAccompanyingPeriodController
|
|||||||
);
|
);
|
||||||
$paginator = $this->paginator->create($nb);
|
$paginator = $this->paginator->create($nb);
|
||||||
|
|
||||||
|
// restrict the number of items for performance reasons
|
||||||
|
$paginator->setItemsPerPage(20);
|
||||||
|
|
||||||
$documents = $this->manager->findDocForAccompanyingPeriod(
|
$documents = $this->manager->findDocForAccompanyingPeriod(
|
||||||
$accompanyingPeriod,
|
$accompanyingPeriod,
|
||||||
$paginator->getCurrentPageFirstItemNumber(),
|
$paginator->getCurrentPageFirstItemNumber(),
|
||||||
|
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the list of GenericDoc for an accompanying period.
|
||||||
|
*/
|
||||||
|
final readonly class GenericDocForAccompanyingPeriodListApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ManagerInterface $manager,
|
||||||
|
private Security $security,
|
||||||
|
private PaginatorFactoryInterface $paginator,
|
||||||
|
private SerializerInterface $serializer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/doc-store/generic-doc/by-period/{id}/index', methods: ['GET'])]
|
||||||
|
public function __invoke(AccompanyingPeriod $accompanyingPeriod): JsonResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) {
|
||||||
|
throw new AccessDeniedHttpException('not allowed to see the documents for accompanying period');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod);
|
||||||
|
$paginator = $this->paginator->create($nb);
|
||||||
|
|
||||||
|
$docs = $this->manager->findDocForAccompanyingPeriod($accompanyingPeriod, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());
|
||||||
|
|
||||||
|
$collection = new Collection($docs, $paginator);
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($collection, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
json: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\Controller;
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||||
@@ -25,7 +25,7 @@ final readonly class GenericDocForPerson
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||||
private Manager $manager,
|
private ManagerInterface $manager,
|
||||||
private PaginatorFactory $paginator,
|
private PaginatorFactory $paginator,
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private \Twig\Environment $twig,
|
private \Twig\Environment $twig,
|
||||||
|
@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Controller;
|
|||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
@@ -27,7 +28,10 @@ class StoredObjectApiController extends ApiController
|
|||||||
private readonly Security $security,
|
private readonly Security $security,
|
||||||
private readonly SerializerInterface $serializer,
|
private readonly SerializerInterface $serializer,
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
) {}
|
ManagerRegistry $managerRegistry,
|
||||||
|
) {
|
||||||
|
parent::__construct($managerRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new stored object.
|
* Creates a new stored object.
|
||||||
|
@@ -46,9 +46,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
|
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
|
||||||
private ?DocGeneratorTemplate $template = null;
|
private ?DocGeneratorTemplate $template = null;
|
||||||
|
|
||||||
#[Assert\Length(min: 2, max: 250)]
|
/**
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
* Store the title of the document, if the title is set before the document.
|
||||||
private string $title = '';
|
*/
|
||||||
|
private string $proxyTitle = '';
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: \Chill\MainBundle\Entity\User::class)]
|
#[ORM\ManyToOne(targetEntity: \Chill\MainBundle\Entity\User::class)]
|
||||||
private ?\Chill\MainBundle\Entity\User $user = null;
|
private ?\Chill\MainBundle\Entity\User $user = null;
|
||||||
@@ -78,9 +79,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this->template;
|
return $this->template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Assert\Length(min: 2, max: 250)]
|
||||||
public function getTitle(): string
|
public function getTitle(): string
|
||||||
{
|
{
|
||||||
return $this->title;
|
return (string) $this->getObject()?->getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUser()
|
public function getUser()
|
||||||
@@ -113,6 +115,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
{
|
{
|
||||||
$this->object = $object;
|
$this->object = $object;
|
||||||
|
|
||||||
|
if ('' !== $this->proxyTitle) {
|
||||||
|
$this->object->setTitle($this->proxyTitle);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +131,11 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
public function setTitle(string $title): self
|
public function setTitle(string $title): self
|
||||||
{
|
{
|
||||||
$this->title = $title;
|
if (null !== $this->getObject()) {
|
||||||
|
$this->getObject()->setTitle($title);
|
||||||
|
} else {
|
||||||
|
$this->proxyTitle = $title;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc\Exception;
|
||||||
|
|
||||||
|
class AssociatedStoredObjectNotFound extends \RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(string $key, array $identifiers, int $code = 0, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('No stored object found for generic doc with key "%s" and identifiers "%s"', $key, json_encode($identifiers)), $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc\Exception;
|
||||||
|
|
||||||
|
class NotNormalizableGenericDocException extends \LogicException {}
|
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc\Exception;
|
||||||
|
|
||||||
|
class UnexpectedValueException extends \UnexpectedValueException {}
|
@@ -13,7 +13,7 @@ namespace Chill\DocStoreBundle\GenericDoc;
|
|||||||
|
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
|
||||||
interface GenericDocForAccompanyingPeriodProviderInterface
|
interface GenericDocForAccompanyingPeriodProviderInterface extends GenericDocProviderInterface
|
||||||
{
|
{
|
||||||
public function buildFetchQueryForAccompanyingPeriod(
|
public function buildFetchQueryForAccompanyingPeriod(
|
||||||
AccompanyingPeriod $accompanyingPeriod,
|
AccompanyingPeriod $accompanyingPeriod,
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a Generic Doc.
|
||||||
|
*/
|
||||||
|
interface GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return true if a generic doc can be normalized by this implementation.
|
||||||
|
*/
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a generic doc into an array.
|
||||||
|
*
|
||||||
|
* @return array{title: string, html?: string, isPresent: bool}
|
||||||
|
*/
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array;
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
|
||||||
|
interface GenericDocProviderInterface
|
||||||
|
{
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this provider supports the given Generic doc for various informations.
|
||||||
|
*
|
||||||
|
* Concerned:
|
||||||
|
*
|
||||||
|
* - @see{self::fetchAssociatedStoredObject}
|
||||||
|
*/
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if the implementation supports key and identifiers.
|
||||||
|
*/
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a GenericDocDTO, given the key and identifiers.
|
||||||
|
*/
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO;
|
||||||
|
}
|
@@ -11,13 +11,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\GenericDoc;
|
namespace Chill\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\NotNormalizableGenericDocException;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Doctrine\DBAL\Exception;
|
use Doctrine\DBAL\Exception;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
|
||||||
final readonly class Manager
|
final readonly class Manager implements ManagerInterface
|
||||||
{
|
{
|
||||||
private FetchQueryToSqlBuilder $builder;
|
private FetchQueryToSqlBuilder $builder;
|
||||||
|
|
||||||
@@ -31,16 +34,16 @@ final readonly class Manager
|
|||||||
* @var iterable<GenericDocForPersonProviderInterface>
|
* @var iterable<GenericDocForPersonProviderInterface>
|
||||||
*/
|
*/
|
||||||
private iterable $providersForPerson,
|
private iterable $providersForPerson,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var iterable<GenericDocNormalizerInterface>
|
||||||
|
*/
|
||||||
|
private iterable $genericDocNormalizers,
|
||||||
private Connection $connection,
|
private Connection $connection,
|
||||||
) {
|
) {
|
||||||
$this->builder = new FetchQueryToSqlBuilder();
|
$this->builder = new FetchQueryToSqlBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param list<string> $places
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function countDocForAccompanyingPeriod(
|
public function countDocForAccompanyingPeriod(
|
||||||
AccompanyingPeriod $accompanyingPeriod,
|
AccompanyingPeriod $accompanyingPeriod,
|
||||||
?\DateTimeImmutable $startDate = null,
|
?\DateTimeImmutable $startDate = null,
|
||||||
@@ -83,13 +86,6 @@ final readonly class Manager
|
|||||||
return $this->countDoc($sql, $params, $types);
|
return $this->countDoc($sql, $params, $types);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param list<string> $places places to search. When empty, search in all places
|
|
||||||
*
|
|
||||||
* @return iterable<GenericDocDTO>
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function findDocForAccompanyingPeriod(
|
public function findDocForAccompanyingPeriod(
|
||||||
AccompanyingPeriod $accompanyingPeriod,
|
AccompanyingPeriod $accompanyingPeriod,
|
||||||
int $offset = 0,
|
int $offset = 0,
|
||||||
@@ -129,10 +125,35 @@ final readonly class Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<string> $places places to search. When empty, search in all places
|
* Fetch a generic doc, if it does exists.
|
||||||
*
|
*
|
||||||
* @return iterable<GenericDocDTO>
|
* Currently implemented only on generic docs linked with accompanying period
|
||||||
*/
|
*/
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
foreach ($this->providersForAccompanyingPeriod as $provider) {
|
||||||
|
if ($provider->supportsKeyAndIdentifiers($key, $identifiers)) {
|
||||||
|
return $provider->buildOneGenericDoc($key, $identifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AssociatedStoredObjectNotFound if no stored object can be found
|
||||||
|
*/
|
||||||
|
public function fetchStoredObject(GenericDocDTO $genericDocDTO): StoredObject
|
||||||
|
{
|
||||||
|
foreach ($this->providersForAccompanyingPeriod as $provider) {
|
||||||
|
if ($provider->supportsGenericDoc($genericDocDTO)) {
|
||||||
|
return $provider->fetchAssociatedStoredObject($genericDocDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssociatedStoredObjectNotFound($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
public function findDocForPerson(
|
public function findDocForPerson(
|
||||||
Person $person,
|
Person $person,
|
||||||
int $offset = 0,
|
int $offset = 0,
|
||||||
@@ -161,6 +182,28 @@ final readonly class Manager
|
|||||||
return $this->places($sql, $params, $types);
|
return $this->places($sql, $params, $types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isGenericDocNormalizable(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
foreach ($this->genericDocNormalizers as $genericDocNormalizer) {
|
||||||
|
if ($genericDocNormalizer->supportsNormalization($genericDocDTO, $format, $context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalizeGenericDoc(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
foreach ($this->genericDocNormalizers as $genericDocNormalizer) {
|
||||||
|
if ($genericDocNormalizer->supportsNormalization($genericDocDTO, $format, $context)) {
|
||||||
|
return $genericDocNormalizer->normalize($genericDocDTO, $format, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotNormalizableGenericDocException();
|
||||||
|
}
|
||||||
|
|
||||||
private function places(string $sql, array $params, array $types): array
|
private function places(string $sql, array $params, array $types): array
|
||||||
{
|
{
|
||||||
if ('' === $sql) {
|
if ('' === $sql) {
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Doctrine\DBAL\Exception;
|
||||||
|
|
||||||
|
interface ManagerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param list<string> $places
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function countDocForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): int;
|
||||||
|
|
||||||
|
public function countDocForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $places places to search. When empty, search in all places
|
||||||
|
*
|
||||||
|
* @return iterable<GenericDocDTO>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function findDocForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, int $offset = 0, int $limit = 20, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): iterable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $places places to search. When empty, search in all places
|
||||||
|
*
|
||||||
|
* @return iterable<GenericDocDTO>
|
||||||
|
*/
|
||||||
|
public function findDocForPerson(Person $person, int $offset = 0, int $limit = 20, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): iterable;
|
||||||
|
|
||||||
|
public function placesForPerson(Person $person): array;
|
||||||
|
|
||||||
|
public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array;
|
||||||
|
|
||||||
|
public function isGenericDocNormalizable(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{title: string, html?: string}
|
||||||
|
*/
|
||||||
|
public function normalizeGenericDoc(GenericDocDTO $genericDocDTO, string $format, array $context = []): array;
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AssociatedStoredObjectNotFound if no stored object can be found
|
||||||
|
*/
|
||||||
|
public function fetchStoredObject(GenericDocDTO $genericDocDTO): StoredObject;
|
||||||
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\UnexpectedValueException;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Renderer\AccompanyingCourseDocumentGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
class AccompanyingCourseDocumentGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly AccompanyingCourseDocumentRepository $repository,
|
||||||
|
private readonly Environment $twig,
|
||||||
|
private readonly AccompanyingCourseDocumentGenericDocRenderer $renderer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (!array_key_exists('id', $genericDocDTO->identifiers)) {
|
||||||
|
throw new UnexpectedValueException('key id not found in identifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
$document = $this->repository->find($genericDocDTO->identifiers['id']);
|
||||||
|
|
||||||
|
if (null === $document) {
|
||||||
|
throw new UnexpectedValueException('document not found with id '.$genericDocDTO->identifiers['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $document->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Renderer\AccompanyingCourseDocumentGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class PersonDocumentGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private PersonDocumentRepository $personDocumentRepository,
|
||||||
|
private AccompanyingCourseDocumentGenericDocRenderer $renderer,
|
||||||
|
private Environment $twig,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return PersonDocumentGenericDocProvider::KEY === $genericDocDTO->key && 'json' === $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (null === $personDocument = $this->personDocumentRepository->find($genericDocDTO->identifiers['id'])) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $personDocument->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -12,10 +12,13 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
@@ -31,17 +34,47 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private EntityManagerInterface $entityManager,
|
private EntityManagerInterface $entityManager,
|
||||||
|
private AccompanyingCourseDocumentRepository $accompanyingCourseDocumentRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id'])?->getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $accompanyingCourseDocument = $this->accompanyingCourseDocumentRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($accompanyingCourseDocument->getDate()),
|
||||||
|
$accompanyingCourseDocument->getCourse(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||||
|
|
||||||
$query = new FetchQuery(
|
$query = new FetchQuery(
|
||||||
self::KEY,
|
self::KEY,
|
||||||
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
|
sprintf('jsonb_build_object(\'id\', acc_course_document.%s)', $classMetadata->getIdentifierColumnNames()[0]),
|
||||||
$classMetadata->getColumnName('date'),
|
$classMetadata->getColumnName('date'),
|
||||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName()
|
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
|
||||||
);
|
);
|
||||||
|
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
@@ -64,7 +97,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
|
|
||||||
$query = new FetchQuery(
|
$query = new FetchQuery(
|
||||||
self::KEY,
|
self::KEY,
|
||||||
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
|
sprintf('jsonb_build_object(\'id\', acc_course_document.%s)', $classMetadata->getIdentifierColumnNames()[0]),
|
||||||
$classMetadata->getColumnName('date'),
|
$classMetadata->getColumnName('date'),
|
||||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
|
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
|
||||||
);
|
);
|
||||||
@@ -110,6 +143,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||||
{
|
{
|
||||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||||
|
$storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class);
|
||||||
|
|
||||||
if (null !== $startDate) {
|
if (null !== $startDate) {
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
@@ -128,9 +162,19 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $content and '' !== $content) {
|
if (null !== $content and '' !== $content) {
|
||||||
|
// add join clause to stored_object table
|
||||||
|
$query->addJoinClause(
|
||||||
|
sprintf(
|
||||||
|
'JOIN %s AS doc_store ON doc_store.%s = acc_course_document.%s',
|
||||||
|
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||||
|
$storedObjectMetadata->getSingleIdentifierColumnName(),
|
||||||
|
$classMetadata->getSingleAssociationJoinColumnName('object')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
sprintf(
|
sprintf(
|
||||||
'(%s ilike ? OR %s ilike ?)',
|
'(doc_store.%s ilike ? OR acc_course_document.%s ilike ?)',
|
||||||
$classMetadata->getColumnName('title'),
|
$classMetadata->getColumnName('title'),
|
||||||
$classMetadata->getColumnName('description')
|
$classMetadata->getColumnName('description')
|
||||||
),
|
),
|
||||||
|
@@ -11,10 +11,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
@@ -27,8 +30,38 @@ final readonly class PersonDocumentGenericDocProvider implements GenericDocForPe
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
|
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
|
||||||
|
private PersonDocumentRepository $personDocumentRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->personDocumentRepository->find($genericDocDTO->identifiers['id'])?->getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key && array_key_exists('id', $identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $document = $this->personDocumentRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($document->getDate()),
|
||||||
|
$document->getPerson()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForPerson(
|
public function buildFetchQueryForPerson(
|
||||||
Person $person,
|
Person $person,
|
||||||
?\DateTimeImmutable $startDate = null,
|
?\DateTimeImmutable $startDate = null,
|
||||||
|
@@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericD
|
|||||||
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -33,6 +36,10 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
|
if ($options['row-only'] ?? false) {
|
||||||
|
return '@ChillDocStore/List/list_item_row.html.twig';
|
||||||
|
}
|
||||||
|
|
||||||
return '@ChillDocStore/List/list_item.html.twig';
|
return '@ChillDocStore/List/list_item.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +51,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
|||||||
'accompanyingCourse' => $doc->getCourse(),
|
'accompanyingCourse' => $doc->getCourse(),
|
||||||
'options' => $options,
|
'options' => $options,
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +61,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
|||||||
'person' => $doc->getPerson(),
|
'person' => $doc->getPerson(),
|
||||||
'options' => $options,
|
'options' => $options,
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,11 +13,25 @@ namespace Chill\DocStoreBundle\GenericDoc\Twig;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a generic doc, to display it into a page.
|
||||||
|
*
|
||||||
|
* @template T of array
|
||||||
|
*/
|
||||||
interface GenericDocRendererInterface
|
interface GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param T $options the options defined by the renderer
|
||||||
|
*/
|
||||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool;
|
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T $options the options defined by the renderer
|
||||||
|
*/
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T $options the options defined by the renderer
|
||||||
|
*/
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\Repository;
|
namespace Chill\DocStoreBundle\Repository;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||||
@@ -136,6 +137,7 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
|
|||||||
private function addFilterClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
private function addFilterClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||||
{
|
{
|
||||||
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
||||||
|
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||||
|
|
||||||
if (null !== $startDate) {
|
if (null !== $startDate) {
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
@@ -154,10 +156,20 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $content and '' !== $content) {
|
if (null !== $content and '' !== $content) {
|
||||||
|
|
||||||
|
$query->addJoinClause(
|
||||||
|
sprintf(
|
||||||
|
'JOIN %s AS doc_store ON doc_store.%s = person_document.%s',
|
||||||
|
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||||
|
$storedObjectMetadata->getSingleIdentifierColumnName(),
|
||||||
|
$personDocMetadata->getSingleAssociationJoinColumnName('object')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
sprintf(
|
sprintf(
|
||||||
'(%s ilike ? OR %s ilike ?)',
|
'(doc_store.%s ilike ? OR person_document.%s ilike ?)',
|
||||||
$personDocMetadata->getColumnName('title'),
|
$storedObjectMetadata->getColumnName('title'),
|
||||||
$personDocMetadata->getColumnName('description')
|
$personDocMetadata->getColumnName('description')
|
||||||
),
|
),
|
||||||
['%'.$content.'%', '%'.$content.'%'],
|
['%'.$content.'%', '%'.$content.'%'],
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
|
export function fetch_generic_docs_by_accompanying_period(
|
||||||
|
periodId: number,
|
||||||
|
): Promise<GenericDocForAccompanyingPeriod[]> {
|
||||||
|
return fetchResults(
|
||||||
|
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||||
|
);
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
||||||
|
@@ -0,0 +1,71 @@
|
|||||||
|
import { DateTime } from "ChillMainAssets/types";
|
||||||
|
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
||||||
|
|
||||||
|
export interface GenericDocMetadata {
|
||||||
|
isPresent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty metadata for a GenericDoc
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
export interface EmptyMetadata extends GenericDocMetadata {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal Metadata for a GenericDoc with a normalizer
|
||||||
|
*/
|
||||||
|
export interface BaseMetadata extends GenericDocMetadata {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
||||||
|
*/
|
||||||
|
export interface GenericDoc {
|
||||||
|
type: "doc_store_generic_doc";
|
||||||
|
uniqueKey: string;
|
||||||
|
key: string;
|
||||||
|
identifiers: object;
|
||||||
|
context: "person" | "accompanying-period";
|
||||||
|
doc_date: DateTime;
|
||||||
|
metadata: GenericDocMetadata;
|
||||||
|
storedObject: StoredObject | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||||
|
context: "accompanying-period";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||||
|
html: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_course_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseActivityDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_course_activity_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseCalendarDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_course_calendar_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCoursePersonDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "person_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_period_work_evaluation_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
@@ -1,8 +1,5 @@
|
|||||||
import {
|
import { DateTime, User } from "ChillMainAssets/types";
|
||||||
DateTime,
|
import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpers";
|
||||||
User,
|
|
||||||
} from "../../../ChillMainBundle/Resources/public/types";
|
|
||||||
import { SignedUrlGet } from "./vuejs/StoredObjectButton/helpers";
|
|
||||||
|
|
||||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||||
|
|
||||||
@@ -138,3 +135,10 @@ export interface ZoomLevel {
|
|||||||
nl?: string;
|
nl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GenericDoc {
|
||||||
|
type: "doc_store_generic_doc";
|
||||||
|
key: string;
|
||||||
|
context: "person" | "accompanying-period";
|
||||||
|
doc_date: DateTime;
|
||||||
|
}
|
@@ -66,7 +66,7 @@ const open_button = ref<HTMLAnchorElement | null>(null);
|
|||||||
function buildDocumentName(): string {
|
function buildDocumentName(): string {
|
||||||
let document_name = props.filename ?? props.storedObject.title;
|
let document_name = props.filename ?? props.storedObject.title;
|
||||||
|
|
||||||
if ("" === document_name) {
|
if ("" === document_name || null === document_name) {
|
||||||
document_name = "document";
|
document_name = "document";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,120 +1,3 @@
|
|||||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
|
||||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
|
||||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
|
||||||
|
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row">
|
{% include '@ChillDocStore/List/list_item_row.html.twig'%}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.object.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.object.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if context == 'person' and accompanyingCourse is defined %}
|
|
||||||
<div>
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% elseif context == 'accompanying-period' and person is defined %}
|
|
||||||
<div>
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.object.type is not empty %}
|
|
||||||
<div>
|
|
||||||
{{ mm.mimeIcon(document.object.type) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.category.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% if document.object.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.object.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
{% if document.date is not null %}
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.date|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if document.description is not empty %}
|
|
||||||
<div class="item-row">
|
|
||||||
<blockquote class="chill-user-quote col">
|
|
||||||
{{ document.description|chill_markdown_to_html }}
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
{% if document.course is defined %}
|
|
||||||
<li>
|
|
||||||
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
|
|
||||||
</li>
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_button_group(document.title) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
|
|
||||||
<li class="delete">
|
|
||||||
<a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', document.course) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_doc_store_accompanying_course_document_duplicate', {'id': document.id}) }}" class="btn btn-duplicate" title="{{ 'Duplicate'|trans|e('html_attr') }}"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_button_group(document.title) }}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
|
|
||||||
<li class="delete">
|
|
||||||
<a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,119 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.object.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.object.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if context == 'person' and accompanyingCourse is defined %}
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% elseif context == 'accompanying-period' and person is defined %}
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.object.type is not empty %}
|
||||||
|
<div>
|
||||||
|
{{ mm.mimeIcon(document.object.type) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.category.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% if document.object.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.object.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
{% if document.date is not null %}
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.date|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if document.description is not empty %}
|
||||||
|
<div class="item-row">
|
||||||
|
<blockquote class="chill-user-quote col">
|
||||||
|
{{ document.description|chill_markdown_to_html }}
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
{% if document.course is defined %}
|
||||||
|
<li>
|
||||||
|
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
|
||||||
|
</li>
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.object|chill_document_button_group(document.title) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
|
||||||
|
<li class="delete">
|
||||||
|
<a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', document.course) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_doc_store_accompanying_course_document_duplicate', {'id': document.id}) }}" class="btn btn-duplicate" title="{{ 'Duplicate'|trans|e('html_attr') }}"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.object|chill_document_button_group(document.title) }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
|
||||||
|
<li class="delete">
|
||||||
|
<a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@@ -24,9 +24,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row g-3">
|
||||||
<div class="col-xs-12 col-sm-6 col-md-4">
|
<div class="col-xs-12 col-sm-6 col-md-4">
|
||||||
<div class="card"">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title">{{ title }}</h2>
|
<h2 class="card-title">{{ title }}</h2>
|
||||||
<h3>{{ 'workflow.public_link.main_document'|trans }}</h3>
|
<h3>{{ 'workflow.public_link.main_document'|trans }}</h3>
|
||||||
@@ -39,5 +39,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% for attachment in attachments %}
|
||||||
|
<div class="col-xs-12 col-sm-6 col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ attachment.proxyStoredObject.title }}</h2>
|
||||||
|
<h3>{{ 'workflow.public_link.attachment'|trans }}</h3>
|
||||||
|
|
||||||
|
<ul class="record_actions slim small">
|
||||||
|
<li>
|
||||||
|
{{ attachment.proxyStoredObject|chill_document_download_only_button(storedObject.title(), false) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\Security\Authorization;
|
namespace Chill\DocStoreBundle\Security\Authorization;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
@@ -26,7 +28,12 @@ class StoredObjectVoter extends Voter
|
|||||||
{
|
{
|
||||||
public const LOG_PREFIX = '[stored object voter] ';
|
public const LOG_PREFIX = '[stored object voter] ';
|
||||||
|
|
||||||
public function __construct(private readonly Security $security, private readonly iterable $storedObjectVoters, private readonly LoggerInterface $logger) {}
|
public function __construct(
|
||||||
|
private readonly Security $security,
|
||||||
|
private readonly iterable $storedObjectVoters,
|
||||||
|
private readonly LoggerInterface $logger,
|
||||||
|
private readonly EntityWorkflowAttachmentRepository $entityWorkflowAttachmentRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
protected function supports($attribute, $subject): bool
|
protected function supports($attribute, $subject): bool
|
||||||
{
|
{
|
||||||
@@ -39,6 +46,16 @@ class StoredObjectVoter extends Voter
|
|||||||
/** @var StoredObject $subject */
|
/** @var StoredObject $subject */
|
||||||
$attributeAsEnum = StoredObjectRoleEnum::from($attribute);
|
$attributeAsEnum = StoredObjectRoleEnum::from($attribute);
|
||||||
|
|
||||||
|
// check if the stored object is attached to any workflow
|
||||||
|
$user = $token->getUser();
|
||||||
|
if ($user instanceof User && StoredObjectRoleEnum::SEE === $attributeAsEnum) {
|
||||||
|
foreach ($this->entityWorkflowAttachmentRepository->findByStoredObject($subject) as $workflowAttachment) {
|
||||||
|
if ($workflowAttachment->getEntityWorkflow()->isUserInvolved($user)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through context-specific voters
|
// Loop through context-specific voters
|
||||||
foreach ($this->storedObjectVoters as $storedObjectVoter) {
|
foreach ($this->storedObjectVoters as $storedObjectVoter) {
|
||||||
if ($storedObjectVoter->supports($attributeAsEnum, $subject)) {
|
if ($storedObjectVoter->supports($attributeAsEnum, $subject)) {
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
class GenericDocNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||||
|
{
|
||||||
|
use NormalizerAwareTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special key to attach a stored object to the generic doc.
|
||||||
|
*
|
||||||
|
* This is present for performance reason: if any other part of the application "knows" about the stored object
|
||||||
|
* related to the GenericDoc, this stored object is use instead of adding costly sql queries.
|
||||||
|
*/
|
||||||
|
public const ATTACHED_STORED_OBJECT_PROXY = 'attached-stored-object-proxy';
|
||||||
|
|
||||||
|
public function __construct(private readonly ManagerInterface $manager) {}
|
||||||
|
|
||||||
|
public function normalize($object, ?string $format = null, array $context = []): array
|
||||||
|
{
|
||||||
|
/* @var GenericDocDTO $object */
|
||||||
|
|
||||||
|
try {
|
||||||
|
$storedObject = $context[self::ATTACHED_STORED_OBJECT_PROXY] ?? $this->manager->fetchStoredObject($object);
|
||||||
|
} catch (AssociatedStoredObjectNotFound) {
|
||||||
|
$storedObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'type' => 'doc_store_generic_doc',
|
||||||
|
'key' => $object->key,
|
||||||
|
'uniqueKey' => $object->key.implode('', array_keys($object->identifiers)).implode('', array_values($object->identifiers)),
|
||||||
|
'identifiers' => $object->identifiers,
|
||||||
|
'context' => $object->getContext(),
|
||||||
|
'doc_date' => $this->normalizer->normalize($object->docDate, $format, $context),
|
||||||
|
'metadata' => [],
|
||||||
|
'storedObject' => $this->normalizer->normalize($storedObject, $format, $context),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->manager->isGenericDocNormalizable($object, $format, $context)) {
|
||||||
|
$data['metadata'] = $this->manager->normalizeGenericDoc($object, $format, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsNormalization($data, ?string $format = null): bool
|
||||||
|
{
|
||||||
|
return 'json' === $format && $data instanceof GenericDocDTO;
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Tests\Controller;
|
|||||||
use Chill\DocStoreBundle\Controller\StoredObjectApiController;
|
use Chill\DocStoreBundle\Controller\StoredObjectApiController;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
@@ -45,7 +46,9 @@ class StoredObjectApiControllerTest extends TestCase
|
|||||||
{"type": "stored-object", "id": 1}
|
{"type": "stored-object", "id": 1}
|
||||||
JSON);
|
JSON);
|
||||||
|
|
||||||
$controller = new StoredObjectApiController($security, $serializer, $entityManager);
|
$managerRegistry = $this->createMock(ManagerRegistry::class);
|
||||||
|
|
||||||
|
$controller = new StoredObjectApiController($security, $serializer, $entityManager, $managerRegistry);
|
||||||
|
|
||||||
$actual = $controller->createStoredObject();
|
$actual = $controller->createStoredObject();
|
||||||
|
|
||||||
|
@@ -11,10 +11,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\Tests\GenericDoc;
|
namespace Chill\DocStoreBundle\Tests\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\NotNormalizableGenericDocException;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
@@ -58,6 +61,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -79,6 +83,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -100,6 +105,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -121,6 +127,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -142,6 +149,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -163,6 +171,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -170,10 +179,77 @@ class ManagerTest extends KernelTestCase
|
|||||||
|
|
||||||
self::assertEquals(['accompanying_course_document_dummy'], $places);
|
self::assertEquals(['accompanying_course_document_dummy'], $places);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIsGenericDocNormalizable(): void
|
||||||
|
{
|
||||||
|
$genericDoc = new GenericDocDTO('test', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(true)], $this->connection);
|
||||||
|
self::assertTrue($manager->isGenericDocNormalizable($genericDoc, 'json'));
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(false)], $this->connection);
|
||||||
|
self::assertFalse($manager->isGenericDocNormalizable($genericDoc, 'json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeGenericDocMetadata(): void
|
||||||
|
{
|
||||||
|
$genericDoc = new GenericDocDTO('test', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(false), $this->buildNormalizer(true)], $this->connection);
|
||||||
|
self::assertEquals(['title' => 'Some title'], $manager->normalizeGenericDoc($genericDoc, 'json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeGenericDocMetadataNoNormalizer(): void
|
||||||
|
{
|
||||||
|
$genericDoc = new GenericDocDTO('test', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(false)], $this->connection);
|
||||||
|
|
||||||
|
$this->expectException(NotNormalizableGenericDocException::class);
|
||||||
|
|
||||||
|
self::assertEquals(['title' => 'Some title'], $manager->normalizeGenericDoc($genericDoc, 'json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildNormalizer(bool $supports): GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
return new class ($supports) implements GenericDocNormalizerInterface {
|
||||||
|
public function __construct(private readonly bool $supports) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return $this->supports;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
return ['title' => 'Some title'];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final readonly class SimpleGenericDocAccompanyingPeriodProvider implements GenericDocForAccompanyingPeriodProviderInterface
|
final readonly class SimpleGenericDocAccompanyingPeriodProvider implements GenericDocForAccompanyingPeriodProviderInterface
|
||||||
{
|
{
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return 'accompanying_course_document_dummy' === $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
return new GenericDocDTO('accompanying_course_document_dummy', $identifiers, new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$query = new FetchQuery(
|
$query = new FetchQuery(
|
||||||
|
@@ -13,6 +13,7 @@ namespace Chill\DocStoreBundle\Tests\GenericDoc\Providers;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
||||||
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
@@ -56,7 +57,8 @@ class AccompanyingCourseDocumentGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
$provider = new AccompanyingCourseDocumentGenericDocProvider(
|
$provider = new AccompanyingCourseDocumentGenericDocProvider(
|
||||||
$security->reveal(),
|
$security->reveal(),
|
||||||
$this->entityManager
|
$this->entityManager,
|
||||||
|
$this->prophesize(AccompanyingCourseDocumentRepository::class)->reveal()
|
||||||
);
|
);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
||||||
|
@@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Tests\GenericDoc\Providers;
|
|||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
@@ -33,11 +34,14 @@ class PersonDocumentGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
|
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
|
||||||
|
|
||||||
|
private PersonDocumentRepository $personDocumentRepository;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
$this->personDocumentACLAwareRepository = self::getContainer()->get(PersonDocumentACLAwareRepositoryInterface::class);
|
$this->personDocumentACLAwareRepository = self::getContainer()->get(PersonDocumentACLAwareRepositoryInterface::class);
|
||||||
|
$this->personDocumentRepository = self::getContainer()->get(PersonDocumentRepository::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +64,8 @@ class PersonDocumentGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
$provider = new PersonDocumentGenericDocProvider(
|
$provider = new PersonDocumentGenericDocProvider(
|
||||||
$security->reveal(),
|
$security->reveal(),
|
||||||
$this->personDocumentACLAwareRepository
|
$this->personDocumentACLAwareRepository,
|
||||||
|
$this->personDocumentRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||||
|
@@ -16,6 +16,7 @@ use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
|
|||||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter;
|
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface;
|
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
@@ -44,7 +45,7 @@ class StoredObjectVoterTest extends TestCase
|
|||||||
->with($this->logicalOr($this->identicalTo('ROLE_USER'), $this->identicalTo('ROLE_ADMIN')))
|
->with($this->logicalOr($this->identicalTo('ROLE_USER'), $this->identicalTo('ROLE_ADMIN')))
|
||||||
->willReturn($securityIsGrantedResult);
|
->willReturn($securityIsGrantedResult);
|
||||||
|
|
||||||
$voter = new StoredObjectVoter($security, $storedObjectVoters, new NullLogger());
|
$voter = new StoredObjectVoter($security, $storedObjectVoters, new NullLogger(), $this->createMock(EntityWorkflowAttachmentRepository::class));
|
||||||
|
|
||||||
self::assertEquals($expected, $voter->vote($token, $subject, [$attribute]));
|
self::assertEquals($expected, $voter->vote($token, $subject, [$attribute]));
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\Tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\DocStoreBundle\Serializer\Normalizer\GenericDocNormalizer;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class GenericDocNormalizerTest extends TestCase
|
||||||
|
{
|
||||||
|
private $normalizer;
|
||||||
|
|
||||||
|
private ManagerInterface $manager;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->manager = $this->createMock(ManagerInterface::class);
|
||||||
|
|
||||||
|
$this->normalizer = new GenericDocNormalizer($this->manager);
|
||||||
|
|
||||||
|
$innerNormalizer = $this->createMock(NormalizerInterface::class);
|
||||||
|
$innerNormalizer->method('normalize')
|
||||||
|
->willReturnCallback(fn ($date) => $date instanceof \DateTimeImmutable ? $date->format(DATE_ATOM) : null);
|
||||||
|
|
||||||
|
$this->normalizer->setNormalizer($innerNormalizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalize()
|
||||||
|
{
|
||||||
|
$docDate = new \DateTimeImmutable('2023-10-01T15:03:01.012345Z');
|
||||||
|
|
||||||
|
$object = new GenericDocDTO(
|
||||||
|
'some_key',
|
||||||
|
['id' => 'id1', 'other_id' => 'id2'],
|
||||||
|
$docDate,
|
||||||
|
new AccompanyingPeriod(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'type' => 'doc_store_generic_doc',
|
||||||
|
'key' => 'some_key',
|
||||||
|
'identifiers' => ['id' => 'id1', 'other_id' => 'id2'],
|
||||||
|
'context' => 'accompanying-period',
|
||||||
|
'doc_date' => $docDate->format(DATE_ATOM),
|
||||||
|
'uniqueKey' => 'some_keyidother_idid1id2',
|
||||||
|
'metadata' => [],
|
||||||
|
'storedObject' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->manager->expects($this->once())->method('isGenericDocNormalizable')
|
||||||
|
->with($object, 'json', [])
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
$actual = $this->normalizer->normalize($object, 'json', []);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@ use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
|||||||
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowWithStoredObjectHandlerInterface;
|
use Chill\MainBundle\Workflow\EntityWorkflowWithStoredObjectHandlerInterface;
|
||||||
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||||
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvideThirdPartiesAssociated;
|
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvideThirdPartiesAssociated;
|
||||||
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvidePersonsAssociated;
|
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvidePersonsAssociated;
|
||||||
@@ -69,7 +70,7 @@ final readonly class AccompanyingCourseDocumentWorkflowHandler implements Entity
|
|||||||
return $this->translator->trans('workflow.Document deleted');
|
return $this->translator->trans('workflow.Document deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->translator->trans('workflow.Document (n°%doc%)', ['%doc%' => $entityWorkflow->getRelatedEntityId()])
|
return $this->translator->trans('entity_display_title.Document (n°%doc%)', ['%doc%' => $entityWorkflow->getRelatedEntityId()])
|
||||||
.' - '.$doc->getTitle();
|
.' - '.$doc->getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +79,15 @@ final readonly class AccompanyingCourseDocumentWorkflowHandler implements Entity
|
|||||||
return $this->repository->find($entityWorkflow->getRelatedEntityId());
|
return $this->repository->find($entityWorkflow->getRelatedEntityId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
if (null === $document = $this->getRelatedEntity($entityWorkflow)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $document->getCourse();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return array[]
|
||||||
*/
|
*/
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user