From 3b56cc818b5348331904f3d3966c3e61592f6714 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 18 Jan 2022 11:08:24 +0100 Subject: [PATCH 001/109] entities + migration created --- .../Entity/Person/PersonResource.php | 255 ++++++++++++++++++ .../Entity/Person/PersonResourceKind.php | 69 +++++ .../migrations/Version20220118100237.php | 231 ++++++++++++++++ 3 files changed, 555 insertions(+) create mode 100644 src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php create mode 100644 src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20220118100237.php diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php new file mode 100644 index 000000000..21adbea5e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php @@ -0,0 +1,255 @@ +comment = new CommentEmbeddable(); + } + + /** + * GETTERS + */ + + public function getId(): ?int + { + return $this->id; + } + + public function getFreeText(): ?string + { + return $this->freeText; + } + + public function getComment(): CommentEmbeddable + { + return $this->comment; + } + + public function getKind(): ?PersonResourceKind + { + return $this->kind; + } + + public function getPersonOwner(): ?Person + { + return $this->personOwner; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function getThirdParty(): ?ThirdParty + { + return $this->thirdParty; + } + + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + public function getUpdatedAt(): ?DateTimeInterface + { + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + + /** + * SETTERS + */ + + public function setFreeText(?string $freeText): self + { + $this->freeText = $freeText; + + if ('' !== $freeText) { + $this->setPerson(null); + $this->setThirdParty(null); + } + + return $this; + } + + public function setComment(CommentEmbeddable $comment): self + { + $this->comment = $comment; + + return $this; + } + + public function setKind(?PersonResourceKind $kind): self + { + $this->kind = $kind; + + return $this; + } + + public function setPersonOwner(?Person $personOwner): self + { + $this->personOwner = $personOwner; + + return $this; + } + + public function setPerson(?Person $person): self + { + $this->person = $person; + + if (null !== $person) { + $this->setFreeText(""); + $this->setThirdParty(null); + } + + return $this; + } + + public function setThirdParty(?ThirdParty $thirdParty): self + { + $this->thirdParty = $thirdParty; + + if (null !== $thirdParty) { + $this->setFreeText(""); + $this->setPerson(null); + } + + return $this; + } + + public function setCreatedAt(DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function setCreatedBy(?User $createdBy): self + { + $this->createdBy = $createdBy; + + return $this; + } + + public function setUpdatedAt(DateTimeInterface $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function setUpdatedBy(?User $updatedBy): self + { + $this->updatedBy = $updatedBy; + + return $this; + } + +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php new file mode 100644 index 000000000..03b2aa858 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php @@ -0,0 +1,69 @@ +id; + } + + public function getIsActive(): bool + { + return $this->isActive; + } + + public function getLabel(): ?array + { + return $this->label; + } + + public function setLabel(array $label): self + { + $this->label = $label; + + return $this; + } + + public function setIsActive(bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220118100237.php b/src/Bundle/ChillPersonBundle/migrations/Version20220118100237.php new file mode 100644 index 000000000..8f58b42b0 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220118100237.php @@ -0,0 +1,231 @@ +addSql('DROP SEQUENCE chill_person_accompanying_period_work_eval_doc_id_seq CASCADE'); + $this->addSql('CREATE SEQUENCE chill_person_resource_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE chill_person_resource_kind_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_person_resource (id INT NOT NULL, person_id INT DEFAULT NULL, kind_id INT DEFAULT NULL, freeText TEXT DEFAULT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_comment TEXT DEFAULT NULL, comment_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_userId INT DEFAULT NULL, personOwner_id INT NOT NULL, thirdParty_id INT DEFAULT NULL, createdBy_id INT NOT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_FA5B7D92D19C515B ON chill_person_resource (personOwner_id)'); + $this->addSql('CREATE INDEX IDX_FA5B7D92217BBB47 ON chill_person_resource (person_id)'); + $this->addSql('CREATE INDEX IDX_FA5B7D923EA5CAB0 ON chill_person_resource (thirdParty_id)'); + $this->addSql('CREATE INDEX IDX_FA5B7D9230602CA9 ON chill_person_resource (kind_id)'); + $this->addSql('CREATE INDEX IDX_FA5B7D923174800F ON chill_person_resource (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_FA5B7D9265FF1AEC ON chill_person_resource (updatedBy_id)'); + $this->addSql('CREATE TABLE chill_person_resource_kind (id INT NOT NULL, label JSON NOT NULL, isActive BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('ALTER TABLE chill_person_resource ADD CONSTRAINT FK_FA5B7D92D19C515B FOREIGN KEY (personOwner_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_resource ADD CONSTRAINT FK_FA5B7D92217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_resource ADD CONSTRAINT FK_FA5B7D923EA5CAB0 FOREIGN KEY (thirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_resource ADD CONSTRAINT FK_FA5B7D9230602CA9 FOREIGN KEY (kind_id) REFERENCES chill_person_resource_kind (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_resource ADD CONSTRAINT FK_FA5B7D923174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_resource ADD CONSTRAINT FK_FA5B7D9265FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('DROP TABLE chill_main_address_legacy'); + // $this->addSql('ALTER INDEX idx_ac74095a550b0c53 RENAME TO IDX_AC74095AD7FA8EF0'); + // $this->addSql('ALTER INDEX idx_55026b0cc54c8c93 RENAME TO IDX_AC74095AC54C8C93'); + // $this->addSql('ALTER INDEX idx_55026b0c217bbb47 RENAME TO IDX_AC74095A217BBB47'); + // $this->addSql('ALTER INDEX idx_55026b0c682b5931 RENAME TO IDX_AC74095A682B5931'); + // $this->addSql('ALTER INDEX idx_55026b0ca76ed395 RENAME TO IDX_AC74095AA76ED395'); + // $this->addSql('ALTER TABLE activityreason ALTER name SET NOT NULL'); + // $this->addSql('ALTER INDEX idx_654a2fcd12469de2 RENAME TO IDX_AF82522312469DE2'); + // $this->addSql('ALTER TABLE activityreasoncategory ALTER name SET NOT NULL'); + // $this->addSql('DROP INDEX internal_center_geom_idx'); + // $this->addSql('DROP INDEX chill_custom_main_address_filtering_idx'); + // $this->addSql('ALTER TABLE chill_main_address DROP customs'); + // $this->addSql('ALTER TABLE chill_main_address ALTER isnoaddress DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_main_address ALTER point TYPE geometry(POINT,4326)'); + // $this->addSql('ALTER TABLE chill_main_address ALTER point DROP DEFAULT'); + // $this->addSql('DROP INDEX chill_internal_address_reference_canonicalized'); + // $this->addSql('DROP INDEX address_refid'); + // $this->addSql('ALTER TABLE chill_main_address_reference ALTER point TYPE geometry(POINT,4326)'); + // $this->addSql('ALTER TABLE chill_main_address_reference ALTER point DROP DEFAULT'); + // $this->addSql('CREATE INDEX address_refid ON chill_main_address_reference (refId) WHERE refid != \'\''); + // $this->addSql('ALTER TABLE chill_main_location ALTER active DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_main_location_type ALTER active DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_main_location_type ALTER editablebyusers DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_main_location_type ALTER editablebyusers SET NOT NULL'); + // $this->addSql('ALTER INDEX idx_154a075fef1a9d84 RENAME TO IDX_589D51B4EF1A9D84'); + // $this->addSql('ALTER INDEX idx_154a075fa76ed395 RENAME TO IDX_589D51B4A76ED395'); + // $this->addSql('DROP INDEX chill_internal_postal_code_canonicalized'); + // $this->addSql('ALTER TABLE chill_main_postal_code ALTER center TYPE geometry(POINT,4326)'); + // $this->addSql('ALTER TABLE chill_main_postal_code ALTER center DROP DEFAULT'); + // $this->addSql('CREATE INDEX search_name_code ON chill_main_postal_code (code, label)'); + // $this->addSql('ALTER INDEX idx_64a4a621504cb38d RENAME TO IDX_E260A868504CB38D'); + // $this->addSql('DROP INDEX participations_no_overlap'); + // $this->addSql('DROP INDEX thirdparty_unique'); + // $this->addSql('DROP INDEX person_unique'); + // $this->addSql('CREATE UNIQUE INDEX thirdparty_unique ON chill_person_accompanying_period_resource (thirdparty_id, accompanyingperiod_id)'); + // $this->addSql('CREATE UNIQUE INDEX person_unique ON chill_person_accompanying_period_resource (person_id, accompanyingperiod_id)'); + // $this->addSql('ALTER TABLE chill_person_accompanying_period_work DROP CONSTRAINT fk_b694fb3616d9d9b'); + // $this->addSql('DROP INDEX idx_b694fb3616d9d9b'); + // $this->addSql('ALTER TABLE chill_person_household DROP CONSTRAINT fk_household_comment_embeddable_user'); + // $this->addSql('DROP INDEX IDX_BE50A270116F5FA9'); + // $this->addSql('DROP INDEX household_members_not_overlaps'); + // $this->addSql('DROP INDEX chill_custom_person_household_members_sharing_idx'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER startdate TYPE DATE'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER startdate DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER enddate TYPE DATE'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER enddate DROP DEFAULT'); + // $this->addSql('COMMENT ON COLUMN chill_person_household_members.startDate IS \'(DC2Type:date_immutable)\''); + // $this->addSql('COMMENT ON COLUMN chill_person_household_members.endDate IS \'(DC2Type:date_immutable)\''); + // $this->addSql('ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(7)'); + // $this->addSql('DROP INDEX mobilenumber_trgm_idx'); + // $this->addSql('DROP INDEX fullnamecanonical_trgm_idx'); + // $this->addSql('DROP INDEX chill_custom_fullnamecanonical_only_trgm_idx_gin'); + // $this->addSql('DROP INDEX phonenumber_trgm_idx'); + // $this->addSql('ALTER TABLE chill_person_person DROP cfdata_old'); + // $this->addSql('ALTER TABLE chill_person_person ALTER maritalstatus_id TYPE VARCHAR(7)'); + // $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_person_person ALTER cfdata SET NOT NULL'); + // $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber DROP DEFAULT'); + // $this->addSql('ALTER INDEX idx_person_center RENAME TO IDX_BF210A145932F377'); + // $this->addSql('ALTER INDEX idx_3370d4403818da5 RENAME TO IDX_BF210A143818DA5'); + // $this->addSql('ALTER INDEX idx_bf210a145521be40 RENAME TO IDX_BF210A14D7D03CE3'); + // $this->addSql('ALTER INDEX idx_3370d4401c9da55 RENAME TO IDX_BF210A141C9DA55'); + // $this->addSql('ALTER TABLE persons_spoken_languages DROP CONSTRAINT FK_7201106F217BBB47'); + // $this->addSql('ALTER TABLE persons_spoken_languages DROP CONSTRAINT FK_7201106F82F1BAF4'); + // $this->addSql('ALTER TABLE persons_spoken_languages ADD CONSTRAINT FK_7201106F217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER TABLE persons_spoken_languages ADD CONSTRAINT FK_7201106F82F1BAF4 FOREIGN KEY (language_id) REFERENCES language (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER TABLE chill_person_relations ALTER isactive DROP NOT NULL'); + // $this->addSql('ALTER TABLE custom_field_long_choice_options ALTER internal_key DROP DEFAULT'); + // $this->addSql('ALTER TABLE customfield ALTER required DROP DEFAULT'); + // $this->addSql('ALTER TABLE customfield ALTER required SET NOT NULL'); + // $this->addSql('ALTER INDEX idx_40fb5d6dfec418b RENAME TO IDX_7A6FDBEFEC418B'); + // $this->addSql('ALTER INDEX idx_286dc95df53b66 RENAME TO IDX_7A48DF7F5DF53B66'); + // $this->addSql('ALTER TABLE customfieldsgroup ALTER options DROP DEFAULT'); + // $this->addSql('ALTER TABLE customfieldsgroup ALTER options SET NOT NULL'); + // $this->addSql('ALTER TABLE chill_vendee.geographical_unit_association ADD CONSTRAINT FK_981D5C95D58FA2A4 FOREIGN KEY (geographicalUnit_id) REFERENCES chill_vendee.geographical_unit (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER TABLE chill_vendee.geographical_unit_association ADD CONSTRAINT FK_981D5C95A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER INDEX idx_a14d8f3d447bbb3b RENAME TO IDX_A14D8F3D96DF1F10'); + // $this->addSql('ALTER TABLE permission_groups ALTER flags DROP DEFAULT'); + // $this->addSql('DROP INDEX "primary"'); + // $this->addSql('ALTER TABLE chill_vendee.security_profile_jobs ADD PRIMARY KEY (profile_id, job_id)'); + // $this->addSql('ALTER TABLE chill_vendee.security_profile_action ALTER profile_id DROP NOT NULL'); + // $this->addSql('ALTER TABLE chill_vendee.security_profile_action ALTER action SET NOT NULL'); + // $this->addSql('DROP INDEX chill_custom_canonicalized_trgm_idx_gist'); + // $this->addSql('DROP INDEX uniq_93f763ae217bbb47'); + // $this->addSql('ALTER TABLE chill_vendee.vendee_person ALTER situationprofessionellestartdate TYPE DATE'); + // $this->addSql('ALTER TABLE chill_vendee.vendee_person ALTER situationprofessionellestartdate DROP DEFAULT'); + // $this->addSql('COMMENT ON COLUMN chill_vendee.vendee_person.situationProfessionelleStartDate IS \'(DC2Type:date_immutable)\''); + // $this->addSql('ALTER TABLE chill_vendee.vendeeperson_entourage ADD CONSTRAINT FK_4D319FFEF57537C3 FOREIGN KEY (vendeeperson_id) REFERENCES chill_vendee.vendee_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER INDEX chill_vendee.idx_1a4b602deef79338 RENAME TO IDX_DD0A4C00EEF79338'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + // $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE chill_person_resource DROP CONSTRAINT FK_FA5B7D9230602CA9'); + $this->addSql('DROP SEQUENCE chill_person_resource_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_person_resource_kind_id_seq CASCADE'); + // $this->addSql('CREATE SEQUENCE chill_person_accompanying_period_work_eval_doc_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + // $this->addSql('CREATE TABLE chill_main_address_legacy (id INT DEFAULT NULL, postcode_id INT DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, streetnumber VARCHAR(255) DEFAULT NULL, validfrom DATE DEFAULT NULL, isnoaddress BOOLEAN DEFAULT NULL, customs JSONB DEFAULT NULL, floor VARCHAR(16) DEFAULT NULL, corridor VARCHAR(16) DEFAULT NULL, steps VARCHAR(16) DEFAULT NULL, buildingname VARCHAR(255) DEFAULT NULL, flat VARCHAR(16) DEFAULT NULL, distribution VARCHAR(255) DEFAULT NULL, extra VARCHAR(255) DEFAULT NULL, validto DATE DEFAULT NULL, point VARCHAR(255) DEFAULT NULL, linkedtothirdparty_id INT DEFAULT NULL)'); + $this->addSql('DROP TABLE chill_person_resource'); + $this->addSql('DROP TABLE chill_person_resource_kind'); + // $this->addSql('ALTER TABLE chill_main_location_type ALTER active SET DEFAULT \'true\''); + // $this->addSql('ALTER TABLE chill_main_location_type ALTER editableByUsers SET DEFAULT \'true\''); + // $this->addSql('ALTER TABLE chill_main_location_type ALTER editableByUsers DROP NOT NULL'); + // $this->addSql('ALTER TABLE persons_spoken_languages DROP CONSTRAINT fk_7201106f217bbb47'); + // $this->addSql('ALTER TABLE persons_spoken_languages DROP CONSTRAINT fk_7201106f82f1baf4'); + // $this->addSql('ALTER TABLE persons_spoken_languages ADD CONSTRAINT fk_7201106f217bbb47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER TABLE persons_spoken_languages ADD CONSTRAINT fk_7201106f82f1baf4 FOREIGN KEY (language_id) REFERENCES language (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('ALTER INDEX idx_589d51b4ef1a9d84 RENAME TO idx_154a075fef1a9d84'); + // $this->addSql('ALTER INDEX idx_589d51b4a76ed395 RENAME TO idx_154a075fa76ed395'); + // $this->addSql('ALTER TABLE chill_vendee.vendee_person ALTER situationProfessionelleStartDate TYPE DATE'); + // $this->addSql('ALTER TABLE chill_vendee.vendee_person ALTER situationProfessionelleStartDate DROP DEFAULT'); + // $this->addSql('COMMENT ON COLUMN chill_vendee.vendee_person.situationprofessionellestartdate IS NULL'); + // $this->addSql('CREATE UNIQUE INDEX uniq_93f763ae217bbb47 ON chill_vendee.vendee_person (person_id)'); + // $this->addSql('ALTER TABLE chill_vendee.geographical_unit_association DROP CONSTRAINT FK_981D5C95D58FA2A4'); + // $this->addSql('ALTER TABLE chill_vendee.geographical_unit_association DROP CONSTRAINT FK_981D5C95A76ED395'); + // $this->addSql('DROP INDEX person_unique'); + // $this->addSql('DROP INDEX thirdparty_unique'); + // $this->addSql('CREATE UNIQUE INDEX person_unique ON chill_person_accompanying_period_resource (person_id, accompanyingperiod_id) WHERE (person_id IS NOT NULL)'); + // $this->addSql('CREATE UNIQUE INDEX thirdparty_unique ON chill_person_accompanying_period_resource (thirdparty_id, accompanyingperiod_id) WHERE (thirdparty_id IS NOT NULL)'); + // $this->addSql('CREATE INDEX participations_no_overlap ON chill_person_accompanying_period_participation (person_id, accompanyingperiod_id)'); + // $this->addSql('ALTER TABLE chill_main_location ALTER active SET DEFAULT \'true\''); + // $this->addSql('ALTER INDEX idx_ac74095aa76ed395 RENAME TO idx_55026b0ca76ed395'); + // $this->addSql('ALTER INDEX idx_ac74095a682b5931 RENAME TO idx_55026b0c682b5931'); + // $this->addSql('ALTER INDEX idx_ac74095a217bbb47 RENAME TO idx_55026b0c217bbb47'); + // $this->addSql('ALTER INDEX idx_ac74095ac54c8c93 RENAME TO idx_55026b0cc54c8c93'); + // $this->addSql('ALTER INDEX idx_ac74095ad7fa8ef0 RENAME TO IDX_AC74095A550B0C53'); + // $this->addSql('ALTER TABLE chill_person_person ADD cfdata_old TEXT DEFAULT NULL'); + // $this->addSql('ALTER TABLE chill_person_person ALTER cFData DROP NOT NULL'); + // $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber SET DEFAULT \'\''); + // $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber SET DEFAULT \'\''); + // $this->addSql('ALTER TABLE chill_person_person ALTER maritalStatus_id TYPE VARCHAR(10)'); + // $this->addSql('COMMENT ON COLUMN chill_person_person.cfdata_old IS \'(DC2Type:array)\''); + // $this->addSql('CREATE INDEX mobilenumber_trgm_idx ON chill_person_person (mobilenumber)'); + // $this->addSql('CREATE INDEX fullnamecanonical_trgm_idx ON chill_person_person (center_id, fullnamecanonical)'); + // $this->addSql('CREATE INDEX chill_custom_fullnamecanonical_only_trgm_idx_gin ON chill_person_person (fullnamecanonical)'); + // $this->addSql('CREATE INDEX phonenumber_trgm_idx ON chill_person_person (phonenumber)'); + // $this->addSql('ALTER INDEX idx_bf210a141c9da55 RENAME TO idx_3370d4401c9da55'); + // $this->addSql('ALTER INDEX idx_bf210a143818da5 RENAME TO idx_3370d4403818da5'); + // $this->addSql('ALTER INDEX idx_bf210a145932f377 RENAME TO idx_person_center'); + // $this->addSql('ALTER INDEX idx_bf210a14d7d03ce3 RENAME TO IDX_BF210A145521BE40'); + // $this->addSql('ALTER TABLE chill_person_household ADD CONSTRAINT fk_household_comment_embeddable_user FOREIGN KEY (comment_members_userid) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // $this->addSql('CREATE INDEX IDX_BE50A270116F5FA9 ON chill_person_household (comment_members_userid)'); + // $this->addSql('CREATE INDEX idx_b694fb3616d9d9b ON chill_person_accompanying_period_work (handlingthierparty_id)'); + // $this->addSql('ALTER TABLE chill_main_address ADD customs JSONB DEFAULT \'[]\''); + // $this->addSql('ALTER TABLE chill_main_address ALTER isNoAddress SET DEFAULT \'false\''); + // $this->addSql('ALTER TABLE chill_main_address ALTER point TYPE VARCHAR(255)'); + // $this->addSql('ALTER TABLE chill_main_address ALTER point DROP DEFAULT'); + // $this->addSql('CREATE INDEX chill_custom_main_address_filtering_idx ON chill_main_address (id, validfrom, validto)'); + // $this->addSql('ALTER TABLE chill_vendee.vendeeperson_entourage DROP CONSTRAINT FK_4D319FFEF57537C3'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER endDate TYPE DATE'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER endDate DROP DEFAULT'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER startDate TYPE DATE'); + // $this->addSql('ALTER TABLE chill_person_household_members ALTER startDate DROP DEFAULT'); + // $this->addSql('COMMENT ON COLUMN chill_person_household_members.enddate IS NULL'); + // $this->addSql('COMMENT ON COLUMN chill_person_household_members.startdate IS NULL'); + // $this->addSql('CREATE INDEX household_members_not_overlaps ON chill_person_household_members (person_id) WHERE (sharedhousehold IS TRUE)'); + // $this->addSql('CREATE INDEX chill_custom_person_household_members_sharing_idx ON chill_person_household_members (person_id, startdate, enddate, household_id) WHERE (sharedhousehold IS TRUE)'); + // $this->addSql('ALTER INDEX chill_vendee.idx_dd0a4c00eef79338 RENAME TO idx_1a4b602deef79338'); + // $this->addSql('CREATE INDEX chill_custom_canonicalized_trgm_idx_gist ON chill_3party.third_party (canonicalized) WHERE (active IS TRUE)'); + // $this->addSql('ALTER INDEX idx_a14d8f3d96df1f10 RENAME TO idx_a14d8f3d447bbb3b'); + // $this->addSql('DROP INDEX address_refid'); + // $this->addSql('ALTER TABLE chill_main_address_reference ALTER point TYPE VARCHAR(255)'); + // $this->addSql('ALTER TABLE chill_main_address_reference ALTER point DROP DEFAULT'); + // $this->addSql('CREATE INDEX chill_internal_address_reference_canonicalized ON chill_main_address_reference (postcode_id, addresscanonical)'); + // $this->addSql('CREATE INDEX address_refid ON chill_main_address_reference (refid) WHERE ((refid)::text <> \'\'::text)'); + // $this->addSql('ALTER INDEX idx_e260a868504cb38d RENAME TO idx_64a4a621504cb38d'); + // $this->addSql('ALTER TABLE chill_vendee.security_profile_action ALTER profile_id SET NOT NULL'); + // $this->addSql('ALTER TABLE chill_vendee.security_profile_action ALTER action DROP NOT NULL'); + // $this->addSql('DROP INDEX security_profile_jobs_pkey'); + // $this->addSql('ALTER TABLE chill_vendee.security_profile_jobs ADD PRIMARY KEY (job_id, profile_id)'); + // $this->addSql('CREATE INDEX internal_center_geom_idx ON chill_vendee.center_polygon (geom)'); + // $this->addSql('DROP INDEX search_name_code'); + // $this->addSql('ALTER TABLE chill_main_postal_code ALTER center TYPE VARCHAR(255)'); + // $this->addSql('ALTER TABLE chill_main_postal_code ALTER center DROP DEFAULT'); + // $this->addSql('CREATE INDEX chill_internal_postal_code_canonicalized ON chill_main_postal_code (canonical) WHERE (origin = 0)'); + // $this->addSql('ALTER TABLE permission_groups ALTER flags SET DEFAULT \'[]\''); + // $this->addSql('ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(10)'); + // $this->addSql('ALTER TABLE chill_person_relations ALTER isActive SET NOT NULL'); + // $this->addSql('ALTER TABLE custom_field_long_choice_options ALTER internal_key SET DEFAULT \'\''); + // $this->addSql('ALTER INDEX idx_7a48df7f5df53b66 RENAME TO idx_286dc95df53b66'); + // $this->addSql('ALTER TABLE customfield ALTER required SET DEFAULT \'false\''); + // $this->addSql('ALTER TABLE customfield ALTER required DROP NOT NULL'); + // $this->addSql('ALTER INDEX idx_7a6fdbefec418b RENAME TO idx_40fb5d6dfec418b'); + // $this->addSql('ALTER TABLE customfieldsgroup ALTER options SET DEFAULT \'{}\''); + // $this->addSql('ALTER TABLE customfieldsgroup ALTER options DROP NOT NULL'); + // $this->addSql('ALTER TABLE activityreason ALTER name DROP NOT NULL'); + // $this->addSql('ALTER INDEX idx_af82522312469de2 RENAME TO idx_654a2fcd12469de2'); + // $this->addSql('ALTER TABLE activityreasoncategory ALTER name DROP NOT NULL'); + } +} From ff0b0678a5de58abc49f76e8cd59a2fde1b655b4 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 18 Jan 2022 11:11:29 +0100 Subject: [PATCH 002/109] changelog entry added --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c78c7a3c..89e67e21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to * vuejs: add validation on required fields for AddPerson, Address and Location components * vuejs: treat 422 validation errors in locations and AddPerson components * [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) +* [person]: possibility to add person resources (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/382) ## Test releases From 6e4bfd45c6ccd4104e5b3244fbb0e6ac18738293 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 18 Jan 2022 13:55:09 +0100 Subject: [PATCH 003/109] route and menu entry added in person menu + start of templates/controller --- .../Controller/PersonResourceController.php | 43 +++++++++++++ .../Menu/PersonMenuBuilder.php | 10 +++ .../Repository/PersonResourceRepository.php | 61 +++++++++++++++++++ .../views/PersonResource/list.html.twig | 32 ++++++++++ .../ChillPersonBundle/config/routes.yaml | 4 ++ .../translations/messages.fr.yml | 5 ++ 6 files changed, 155 insertions(+) create mode 100644 src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php create mode 100644 src/Bundle/ChillPersonBundle/Repository/PersonResourceRepository.php create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php b/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php new file mode 100644 index 000000000..e02b6e4c3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php @@ -0,0 +1,43 @@ +personResourceRepository = $personResourceRepository; + $this->personRepository = $personRepository; + } + + public function listAction($person_id) + { + dump($person_id); + $person = $this->personRepository->find($person_id); + dump($person); + return $this->render( + 'ChillPersonBundle:PersonResource:list.html.twig', + [ + 'person' => $person + ] + ); + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php index d83fa29dd..228309737 100644 --- a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php @@ -96,6 +96,16 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface 'order' => 100, ]); } + + $menu->addChild($this->translator->trans('person_resources_menu'), [ + 'route' => 'chill_person_resource_list', + 'routeParameters' => [ + 'person_id' => $parameters['person']->getId(), + ], + ]) + ->setExtras([ + 'order' => 99999, + ]); } public static function getMenuIds(): array diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonResourceRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonResourceRepository.php new file mode 100644 index 000000000..1874bec05 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/PersonResourceRepository.php @@ -0,0 +1,61 @@ +repository = $entityManager->getRepository(PersonResource::class); + } + + public function find($id): ?PersonResource + { + return $this->repository->find($id); + } + + /** + * @return PersonResource[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return PersonResource[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?PersonResource + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return PersonResource::class; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig new file mode 100644 index 000000000..8e943c43a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig @@ -0,0 +1,32 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_person_resource_list' %} + +{% block title %}{{ 'Person resources'|trans|capitalize ~ ' ' ~ person|chill_entity_render_string }}{% endblock %} + +{% block personcontent %} + +{{ person }} +
+
+
+
Hello
+
there
+
+
+
How
+
are
+
+
+
+
+
you
+
doing
+
+
+
today
+
?
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/config/routes.yaml b/src/Bundle/ChillPersonBundle/config/routes.yaml index bbcb7c39b..3fbf38c0d 100644 --- a/src/Bundle/ChillPersonBundle/config/routes.yaml +++ b/src/Bundle/ChillPersonBundle/config/routes.yaml @@ -55,6 +55,10 @@ chill_person_accompanying_period_re_open: path: /{_locale}/person/{person_id}/accompanying-period/{period_id}/re-open controller: Chill\PersonBundle\Controller\AccompanyingPeriodController::reOpenAction +chill_person_resource_list: + path: /{_locale}/person/resource/{person_id}/general + controller: Chill\PersonBundle\Controller\PersonResourceController::listAction + chill_person_address_list: path: /{_locale}/person/{person_id}/address/list controller: Chill\PersonBundle\Controller\PersonAddressController::listAction diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 5d0e2b16a..e2758034e 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -220,6 +220,11 @@ Delete accompanying period: Supprimer la période d'accompagnement Are you sure you want to remove the accompanying period "%id%" ?: Êtes-vous sûr de vouloir supprimer la période d'accompagnement %id% ? The accompanying course has been successfully removed.: La période d'accompagnement a été supprimée. +# person resource + +person_resources_menu: "Ressources" +Person resources: 'Réssources de la personne' + # pickAPersonType Pick a person: Choisir une personne From 58ac4b01ef9f61a72a23a8d40155d15ea3de7911 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 18 Jan 2022 14:54:03 +0100 Subject: [PATCH 004/109] form created + general layout of templates --- .../Controller/PersonResourceController.php | 9 ++- .../Form/PersonResourceType.php | 79 +++++++++++++++++++ .../views/PersonResource/create.html.twig | 24 ++++++ .../views/PersonResource/list.html.twig | 18 +++-- .../translations/messages.fr.yml | 3 +- 5 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Form/PersonResourceType.php create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php b/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php index e02b6e4c3..6db871e5b 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; +use Chill\PersonBundle\Form\PersonResourceType; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonResourceRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -30,13 +31,15 @@ final class PersonResourceController extends AbstractController public function listAction($person_id) { - dump($person_id); $person = $this->personRepository->find($person_id); - dump($person); + + $form = $this->createForm(PersonResourceType::class); + return $this->render( 'ChillPersonBundle:PersonResource:list.html.twig', [ - 'person' => $person + 'person' => $person, + 'form' => $form->createView() ] ); } diff --git a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php new file mode 100644 index 000000000..8863d705c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php @@ -0,0 +1,79 @@ +categoryRender = $categoryRender; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('kind', EntityType::class, [ + 'label' => 'Type', + 'required' => true, + 'class' => PersonResourceKind::class, + 'query_builder' => static function (EntityRepository $er) { + $qb = $er->createQueryBuilder('pr'); + $qb->where($qb->expr()->eq('pr.isActive', 'TRUE')); + return $qb; + }, + 'choice_label' => function (PersonResourceKind $personResourceKind) { + $options = []; + return $this->categoryRender->renderString($personResourceKind, $options); + } + ]) + ->add('linkedEntity', ChoiceType::class, [ + 'choices' => [ + 'Usager' => 'person', + 'Tiers' => 'thirdparty', + 'Description libre' => 'freeText' + ], + 'expanded' => true, + 'label' => 'Associer un' + ]) + ->add('comment', ChillTextareaType::class, [ + 'label' => 'Note', + 'required' => false + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => PersonResource::class, + ]); + } + + // public function getBlockPrefix(): string + // { + // return 'chill_personbundle_personresource'; + // } + +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig new file mode 100644 index 000000000..9ba297d8b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig @@ -0,0 +1,24 @@ +
+ +

{{ 'Add a person resource'|trans }}

+ + {{ form_start(form, {'attr' : {'id' : 'create-form'}}) }} + + {{ form_row(form.kind) }} + + {{ form_row(form.linkedEntity) }} + + {{ form_row(form.comment) }} + + + + {{ form_end(form) }} + +
\ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig index 8e943c43a..b931c4f95 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig @@ -6,27 +6,29 @@ {% block personcontent %} -{{ person }}
-
Hello
-
there
+
Name of resource
+
kind of resource
-
+ {#
How
are
-
+
#}
-
you
+
comment zone
doing
-
today
-
?
+ {#
today
#} +
Buttons
+ +{% include "@ChillPerson/PersonResource/create.html.twig" %} + {% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index e2758034e..e7d4b3a1b 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -223,7 +223,8 @@ The accompanying course has been successfully removed.: La période d'accompagne # person resource person_resources_menu: "Ressources" -Person resources: 'Réssources de la personne' +Person resources: "Réssources de la personne" +Add a person resource: "Ajouter une réssource" # pickAPersonType Pick a person: Choisir une personne From 001e2f96229007d7737781dae28a355fd32662e8 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 19 Jan 2022 09:48:39 +0100 Subject: [PATCH 005/109] js files put in place + select menus added for selecting person/thirdparty/... show-hide not working yet --- .../Controller/PersonResourceController.php | 24 ++++++++++- .../Entity/Person/PersonResourceKind.php | 10 ++--- .../Form/PersonResourceType.php | 40 ++++++++++++++++--- .../public/page/person_resource/index.js | 33 +++++++++++++++ .../views/PersonResource/create.html.twig | 14 ++++++- .../views/PersonResource/list.html.twig | 6 ++- .../ChillPersonBundle/chill.webpack.config.js | 1 + .../translations/messages.fr.yml | 4 +- 8 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Resources/public/page/person_resource/index.js diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php b/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php index 6db871e5b..799967c82 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonResourceController.php @@ -11,30 +11,50 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; +use Chill\PersonBundle\Entity\Person\PersonResource; use Chill\PersonBundle\Form\PersonResourceType; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonResourceRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; final class PersonResourceController extends AbstractController { private PersonResourceRepository $personResourceRepository; + private PersonRepository $personRepository; + private EntityManagerInterface $em; public function __construct( PersonResourceRepository $personResourceRepository, - PersonRepository $personRepository + PersonRepository $personRepository, + EntityManagerInterface $em ) { $this->personResourceRepository = $personResourceRepository; $this->personRepository = $personRepository; + $this->em = $em; } - public function listAction($person_id) + public function listAction(Request $request, $person_id) { $person = $this->personRepository->find($person_id); + $personResource = new PersonResource(); $form = $this->createForm(PersonResourceType::class); + $form->handleRequest($request); + + if ($request->getMethod() === Request::METHOD_POST && $form->isValid()) { + $this->em->persist($personResource); + $this->em->flush(); + + return $this->redirectToRoute( + 'chill_person_resource', + ['person_id' => $person->getId()] + ); + } + return $this->render( 'ChillPersonBundle:PersonResource:list.html.twig', [ diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php index 03b2aa858..619382f68 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php @@ -31,7 +31,7 @@ class PersonResourceKind /** * @ORM\Column(type="json", length=255) */ - private array $label; + private array $title; /** * @ORM\Column(type="boolean") @@ -48,14 +48,14 @@ class PersonResourceKind return $this->isActive; } - public function getLabel(): ?array + public function getTitle(): ?array { - return $this->label; + return $this->title; } - public function setLabel(array $label): self + public function setTitle(array $title): self { - $this->label = $label; + $this->title = $title; return $this; } diff --git a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php index 8863d705c..fabc30e5e 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php @@ -13,8 +13,12 @@ namespace Chill\PersonBundle\Form; use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; use Chill\MainBundle\Form\Type\ChillTextareaType; +use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person\PersonResource; use Chill\PersonBundle\Entity\Person\PersonResourceKind; +use Chill\PersonBundle\Templating\Entity\PersonRender; +use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -26,10 +30,14 @@ use Symfony\Component\OptionsResolver\OptionsResolver; final class PersonResourceType extends AbstractType { private CategoryRender $categoryRender; + private PersonRender $personRender; + private ThirdPartyRender $thirdPartyRender; - public function __construct(CategoryRender $categoryRender) + public function __construct(CategoryRender $categoryRender, PersonRender $personRender, ThirdPartyRender $thirdPartyRender) { $this->categoryRender = $categoryRender; + $this->personRender = $personRender; + $this->thirdPartyRender = $thirdPartyRender; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -58,6 +66,28 @@ final class PersonResourceType extends AbstractType 'expanded' => true, 'label' => 'Associer un' ]) + ->add('person', EntityType::class, [ + 'label' => 'Usager', + 'class' => Person::class, + 'required' => false, + 'choice_label' => function (Person $person) { + $options = []; + return $this->personRender->renderString($person, $options); + } + ]) + ->add('thirdparty', EntityType::class, [ + 'label' => 'Tiers', + 'class' => ThirdParty::class, + 'required' => false, + 'choice_label' => function (ThirdParty $thirdParty) { + $options = []; + return $this->thirdPartyRender->renderString($thirdParty, $options); + } + ]) + ->add('freetext', ChillTextareaType::class, [ + 'label' => 'Description libre', + 'required' => false + ]) ->add('comment', ChillTextareaType::class, [ 'label' => 'Note', 'required' => false @@ -71,9 +101,9 @@ final class PersonResourceType extends AbstractType ]); } - // public function getBlockPrefix(): string - // { - // return 'chill_personbundle_personresource'; - // } + public function getBlockPrefix(): string + { + return 'chill_personbundle_person_resource'; + } } \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/public/page/person_resource/index.js b/src/Bundle/ChillPersonBundle/Resources/public/page/person_resource/index.js new file mode 100644 index 000000000..8ef2021f1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/page/person_resource/index.js @@ -0,0 +1,33 @@ +import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js'; + +window.addEventListener('DOMContentLoaded', function() { + let + linkedEntityContainer = document.querySelector('#linked-entity'), + personContainer = document.querySelector('#person-entity'), + thirdpartyContainer = document.querySelector('#thirdparty-entity'), + freetextContainer = document.querySelector('#freetext-entity') + ; + + if (null === linkedEntityContainer) { + return; + } + + new ShowHide({ + debug: true, + load_event: null, + froms: [linkedEntityContainer], + container: [personContainer, thirdpartyContainer, freetextContainer], + test: function(containers, arg2, arg3) { + for (let container of containers) { + for (let input of container.querySelectorAll('input')) { + if (!input.checked) { + return true; + } else { + return false; + } + } + } + + }, + }) +}); diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig index 9ba297d8b..74bdeb57e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/create.html.twig @@ -6,7 +6,19 @@ {{ form_row(form.kind) }} - {{ form_row(form.linkedEntity) }} +
+ {{ form_row(form.linkedEntity) }} +
+ +
+ {{ form_row(form.person) }} +
+
+ {{ form_row(form.thirdparty) }} +
+
+ {{ form_row(form.freetext) }} +
{{ form_row(form.comment) }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig index b931c4f95..844f39d41 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/list.html.twig @@ -20,7 +20,7 @@
comment zone
-
doing
+
....
{#
today
#} @@ -31,4 +31,8 @@ {% include "@ChillPerson/PersonResource/create.html.twig" %} +{% endblock %} + +{% block js %} + {# {{ encore_entry_script_tags('page_person_resource') }} #} {% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/chill.webpack.config.js b/src/Bundle/ChillPersonBundle/chill.webpack.config.js index 7a8b79c7c..9cf6717fd 100644 --- a/src/Bundle/ChillPersonBundle/chill.webpack.config.js +++ b/src/Bundle/ChillPersonBundle/chill.webpack.config.js @@ -18,4 +18,5 @@ module.exports = function(encore, entries) encore.addEntry('page_person', __dirname + '/Resources/public/page/person/index.js'); encore.addEntry('page_accompanying_course_index_person_locate', __dirname + '/Resources/public/page/accompanying_course_index/person_locate.js'); encore.addEntry('page_accompanying_course_index_masonry', __dirname + '/Resources/public/page/accompanying_course_index/masonry.js'); + encore.addEntry('page_person_resource', __dirname + '/Resources/public/page/person_resource/index.js'); }; diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index e7d4b3a1b..7165d7084 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -223,8 +223,8 @@ The accompanying course has been successfully removed.: La période d'accompagne # person resource person_resources_menu: "Ressources" -Person resources: "Réssources de la personne" -Add a person resource: "Ajouter une réssource" +Person resources: "Ressources de la personne" +Add a person resource: "Ajouter une ressource" # pickAPersonType Pick a person: Choisir une personne From 3471bdec0db2a252ef0235b007c8128db7c21811 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 19 Jan 2022 10:17:22 +0100 Subject: [PATCH 006/109] accompanyingPeriod: add job --- .../Entity/AccompanyingPeriod.php | 22 ++++++++++ .../migrations/Version20220119091025.php | 40 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 06e4f0c8b..13a597efc 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -20,6 +20,7 @@ use Chill\MainBundle\Entity\HasScopesInterface; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserJob; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; @@ -112,6 +113,15 @@ class AccompanyingPeriod implements */ public const STEP_DRAFT = 'DRAFT'; + + /** + * @ORM\ManyToOne( + * targetEntity=UserJob::class + * ) + * @Groups({"read", "write"}) + */ + private ?UserJob $job = null; + /** * @ORM\ManyToOne( * targetEntity=Address::class @@ -394,6 +404,18 @@ class AccompanyingPeriod implements return $array; } + public function getJob(): ?UserJob + { + return $this->job; + } + + public function setJob(?UserJob $job): self + { + $this->job = $job; + + return $this; + } + public function addComment(Comment $comment): self { $this->comments[] = $comment; diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php b/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php new file mode 100644 index 000000000..a8f125fdf --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php @@ -0,0 +1,40 @@ +addSql('ALTER TABLE chill_person_accompanying_period ADD job_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A868BE04EA9 FOREIGN KEY (job_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_E260A868BE04EA9 ON chill_person_accompanying_period (job_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT FK_E260A868BE04EA9'); + $this->addSql('DROP INDEX IDX_E260A868BE04EA9'); + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP job_id'); + } +} From 9f7f6e33e8688c907408bcca4952ed832c32ae17 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 19 Jan 2022 10:34:33 +0100 Subject: [PATCH 007/109] user-job: make an endpoint for userJob + doc swagger --- .../ChillMainExtension.php | 20 ++++++++++++++++ .../ChillMainBundle/chill.api.specs.yaml | 23 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 772f4d8ee..27d70e725 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -391,6 +391,26 @@ class ChillMainExtension extends Extension implements ], ], ], + [ + 'class' => \Chill\MainBundle\Entity\UserJob::class, + 'name' => 'user_job', + 'base_path' => '/api/1.0/main/user-job', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + ], + ], + ], [ 'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class, 'class' => \Chill\MainBundle\Entity\AddressReference::class, diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml index 753cb855c..4f2e3648e 100644 --- a/src/Bundle/ChillMainBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -116,7 +116,15 @@ components: type: number minItems: 2 maxItems: 2 - + UserJob: + type: object + properties: + id: + type: integer + label: + type: object + type: + type: string paths: /1.0/search.json: @@ -771,5 +779,16 @@ paths: description: "accepted" 403: description: "unauthorized" - + /1.0/main/user-job.json: + get: + tags: + - user + summary: Return a list of all user jobs + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/UserJob' From 175fa7bf2ffd77bbd6e7df9a534b0f55399083bf Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 19 Jan 2022 11:09:02 +0100 Subject: [PATCH 008/109] php code fix --- .../Entity/AccompanyingPeriod.php | 41 +++++++++---------- .../migrations/Version20220119091025.php | 16 ++++---- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 13a597efc..3d7d1a7b4 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -113,15 +113,6 @@ class AccompanyingPeriod implements */ public const STEP_DRAFT = 'DRAFT'; - - /** - * @ORM\ManyToOne( - * targetEntity=UserJob::class - * ) - * @Groups({"read", "write"}) - */ - private ?UserJob $job = null; - /** * @ORM\ManyToOne( * targetEntity=Address::class @@ -208,6 +199,14 @@ class AccompanyingPeriod implements */ private $intensity = self::INTENSITY_OCCASIONAL; + /** + * @ORM\ManyToOne( + * targetEntity=UserJob::class + * ) + * @Groups({"read", "write"}) + */ + private ?UserJob $job = null; + /** * @var DateTime * @@ -404,18 +403,6 @@ class AccompanyingPeriod implements return $array; } - public function getJob(): ?UserJob - { - return $this->job; - } - - public function setJob(?UserJob $job): self - { - $this->job = $job; - - return $this; - } - public function addComment(Comment $comment): self { $this->comments[] = $comment; @@ -647,6 +634,11 @@ class AccompanyingPeriod implements return $this->intensity; } + public function getJob(): ?UserJob + { + return $this->job; + } + /** * Get the location, taking precedence into account. * @@ -1080,6 +1072,13 @@ class AccompanyingPeriod implements return $this; } + public function setJob(?UserJob $job): self + { + $this->job = $job; + + return $this; + } + /** * Set openingDate. * diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php b/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php index a8f125fdf..c047b3dca 100644 --- a/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220119091025.php @@ -15,10 +15,17 @@ use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** - * Add userJob to AccompanyingPeriod + * Add userJob to AccompanyingPeriod. */ final class Version20220119091025 extends AbstractMigration { + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT FK_E260A868BE04EA9'); + $this->addSql('DROP INDEX IDX_E260A868BE04EA9'); + $this->addSql('ALTER TABLE chill_person_accompanying_period DROP job_id'); + } + public function getDescription(): string { return 'Add userJob to AccompanyingPeriod'; @@ -30,11 +37,4 @@ final class Version20220119091025 extends AbstractMigration $this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A868BE04EA9 FOREIGN KEY (job_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('CREATE INDEX IDX_E260A868BE04EA9 ON chill_person_accompanying_period (job_id)'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT FK_E260A868BE04EA9'); - $this->addSql('DROP INDEX IDX_E260A868BE04EA9'); - $this->addSql('ALTER TABLE chill_person_accompanying_period DROP job_id'); - } } From 16c86daafb49e31473f15dd548c34d7f0afd4ef6 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 19 Jan 2022 13:22:43 +0100 Subject: [PATCH 009/109] accompanying course: add selector for accompanying course job --- .../public/vuejs/AccompanyingCourse/api.js | 13 ++--- .../components/Referrer.vue | 57 ++++++++++++++++++- .../vuejs/AccompanyingCourse/js/i18n.js | 4 ++ .../vuejs/AccompanyingCourse/store/index.js | 28 +++++++-- 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js index 8e4134205..ab95229a2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js @@ -15,21 +15,15 @@ const getAccompanyingCourse = (id) => { }); }; -const getUsers = () => { - const url = `/api/1.0/main/user.json`; - - return fetchResults(url); -}; +const getUsers = () => fetchResults('/api/1.0/main/user.json'); const getReferrersSuggested = (course) => { const url = `/api/1.0/person/accompanying-course/${course.id}/referrers-suggested.json`; - return fetchResults(url); } -/* -* Endpoint -*/ +const getUserJobs = () => fetchResults('/api/1.0/main/user-job.json'); + const getSocialIssues = () => { const url = `/api/1.0/person/social-work/social-issue.json`; return fetch(url) @@ -54,4 +48,5 @@ export { getAccompanyingCourse, getUsers, getReferrersSuggested, + getUserJobs }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue index 0556e342a..f801ca355 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue @@ -15,13 +15,33 @@ :searchable="true" :placeholder="$t('referrer.placeholder')" v-model="value" - v-bind:options="users" + :options="users" :select-label="$t('multiselect.select_label')" :deselect-label="$t('multiselect.deselect_label')" :selected-label="$t('multiselect.selected_label')" @select="updateReferrer"> + + + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue index 8e033234c..487fb230e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue @@ -3,15 +3,15 @@

{{ $t('household_members_editor.positioning.persons_to_positionnate')}}

-
+
-
+
- +

{{ conc.person.text }}

+
+
+
{{ $t('household_members_editor.positioning.comment') }}
+ +
+
@@ -46,12 +52,14 @@ import MemberDetails from './MemberDetails.vue'; import {mapGetters, mapState} from "vuex"; import CurrentHousehold from "./CurrentHousehold"; import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue'; +import PersonComment from './PersonComment'; export default { name: "Positioning", components: { CurrentHousehold, PersonRenderBox, + PersonComment, }, computed: { ...mapState([ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js index e09fddf0f..66246058f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/js/i18n.js @@ -52,6 +52,7 @@ const appMessages = { positioning: { persons_to_positionnate: 'Usagers à positionner', holder: "Titulaire", + comment: "Commentaire", }, app: { next: 'Suivant', @@ -77,7 +78,12 @@ const appMessages = { dates: { start_date: "Début de validité", end_date: "Fin de validité", - dates_title: "Période de validité", + dates_title: "Depuis le", + }, + composition: { + composition: "Composition familiale", + household_composition: "Composition du ménage", + number_of_children: "Nombre d'enfants mineurs au sein du ménage", }, confirmation: { save: "Enregistrer", diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js index cd43ee29c..cc5ab497c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js @@ -1,5 +1,6 @@ import { createStore } from 'vuex'; import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js'; +import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js' import { fetchHouseholdByAddressReference } from 'ChillPersonAssets/lib/household.js'; import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; @@ -54,8 +55,11 @@ const store = createStore({ */ householdSuggestionByAccompanyingPeriod: [], // TODO rename into householdsSuggestion showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1, + householdCompositionType: null, + numberOfChildren: 0, addressesSuggestion: [], showAddressSuggestion: true, + householdCompositionTypes: [], warnings: [], errors: [] }, @@ -250,7 +254,8 @@ const store = createStore({ payload_conc, payload = { concerned: [], - destination: null + destination: null, + composition: null, } ; @@ -261,7 +266,6 @@ const store = createStore({ }; if (getters.isHouseholdNew && state.household.current_address !== null) { - console.log(state.household); payload.destination.forceAddress = { id: state.household.current_address.address_id }; } } @@ -290,6 +294,19 @@ const store = createStore({ payload.concerned.push(payload_conc); } + if (getters.isHouseholdNew) { + payload.composition = { + household_composition_type: { + type: state.householdCompositionType.type, + id: state.householdCompositionType.id, + }, + number_of_children: state.numberOfChildren, + start_date: { + datetime: datetimeToISO(state.startDate), + }, + }; + } + return payload; }, }, @@ -409,6 +426,15 @@ const store = createStore({ setErrors(state, errors) { state.errors = errors; }, + setHouseholdCompositionTypes(state, types) { + state.householdCompositionTypes = types; + }, + setHouseholdCompositionType(state, id) { + state.householdCompositionType = state.householdCompositionTypes.find(t => t.id = id); + }, + setNumberOfChildren(state, number) { + state.numberOfChildren = Number.parseInt(number); + }, addAddressesSuggestion(state, addresses) { let existingIds = state.addressesSuggestion .map(a => a.address_id); @@ -570,4 +596,8 @@ if (concerned.length > 0) { }); } +fetchResults(`/api/1.0/person/houehold/composition/type.json`).then(types => { + store.commit('setHouseholdCompositionTypes', types); +}) + export { store }; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig index 2febc7967..1f9c09845 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig @@ -1,23 +1,25 @@ {%- set countPersonLocation = accompanyingCourse.availablePersonLocation|length -%} {%- set hasPersonLocation = countPersonLocation > 0 -%}
-
-
- +
+
+ +

+ {{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}

+ {% if not hasPersonLocation %} +

+ {{ 'Associate at least one member with an household, and set an address to this household'|trans }}

+ {% endif %}
-

- {{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}

- {% if not hasPersonLocation %} -

- {{ 'Associate at least one member with an household, and set an address to this household'|trans }}

- {% endif %} -
+
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/members_editor.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/members_editor.html.twig index ee578203a..8b0134f6b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/members_editor.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/members_editor.html.twig @@ -4,12 +4,14 @@ {% block title 'household.Edit household members'|trans %} {% block content %} -
+
+
-

{{ block('title') }}

-
+

{{ block('title') }}

+
-
+
+
{% endblock %} {% block js %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig index bbb8ff52f..a00c8df36 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig @@ -51,7 +51,42 @@
{% if form is null %} - + {% set currentComposition = household.currentComposition %} + {% if currentComposition is not null %} +
+
+ {{ currentComposition.householdCompositionType.label|localize_translatable_string }} +
+

+ {{ 'household_composition.numberOfChildren children in household'|trans({'numberOfChildren': currentComposition.numberOfChildren}) }} +

+

+ {{ 'household_composition.Since'|trans({'startDate': currentComposition.startDate}) }} +

+ +
+ {% else %} +
+

+ {{ 'household_composition.Currently no composition'|trans }} +

+ +
+ {% endif %} {% if household.waitingForBirth or not household.commentMembers.isEmpty() %}
{% if household.waitingForBirth %} @@ -176,13 +211,26 @@ {{ 'household.Hide memberships'|trans }} + + {% macro buttonsOldMembers(member) %} + {% set household = member.person.getCurrentHousehold %} + {% if household is not null %} +
  • + +
  • + {% endif %} + {% endmacro %} +
    {% for m in old_members %} - {% include '@ChillPerson/Household/_render_member.html.twig' with { 'member': m } %} + {% include '@ChillPerson/Household/_render_member.html.twig' with { + 'member': m, + 'customButtons': { 'before': _self.buttonsOldMembers(m) } + } %} {% endfor %}
    diff --git a/src/Bundle/ChillPersonBundle/Resources/views/HouseholdComposition/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/HouseholdComposition/index.html.twig new file mode 100644 index 000000000..594863a64 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/HouseholdComposition/index.html.twig @@ -0,0 +1,96 @@ +{% extends '@ChillPerson/Household/layout.html.twig' %} + +{% block title 'household_composition.Compositions'|trans %} + +{% block block_post_menu %} +
    +{% endblock %} + +{% block content %} +
    +

    {{ block('title') }}

    + + {% if compositions|length == 0 %} +

    {{ 'household_composition.No composition yet'|trans }}

    + {% else %} +
    + {% for c in compositions %} + {% if c.id != editId %} +
    +
    +
    +

    {{ c.householdCompositionType.label|localize_translatable_string }}

    +

    {{ 'household_composition.numberOfChildren'|trans }}: {{ c.numberOfChildren }}

    +
    +
    {{ 'household_composition.Since'|trans({'startDate': c.startDate}) }}
    +
    +
    +
    + {% if c.endDate is null %} + {{ 'household_composition.Still active'|trans }} + {% else %} + {{ 'household_composition.Until'|trans({'endDate': c.endDate })}} + {% endif %} +
    +
    + {% if c.comment.comment is not empty %} +
    + {{ c.comment|chill_entity_render_box }} +
    + {% endif %} + {% if is_granted('CHILL_PERSON_HOUSEHOLD_EDIT', c.household) %} +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    + {% endif %} +
    + {% else %} + {{ form_start(form) }} + + {{ form_widget(form) }} + + + {{ form_end(form) }} + {% endif %} + {% endfor %} +
    + {% endif %} + +
    + {{ form_start(form) }} + + {{ form_widget(form) }} + +
      +
    • + +
    • +
    + {{ form_end(form) }} +
    + + {% if editId == -1 %} +
      +
    • + +
    • +
    + {% endif %} +
    +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php index b702e798e..cdc62c2cf 100644 --- a/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/HouseholdVoter.php @@ -11,7 +11,66 @@ declare(strict_types=1); namespace Chill\PersonBundle\Security\Authorization; -class HouseholdVoter +use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\HouseholdMember; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Security; +use UnexpectedValueException; +use function in_array; + +class HouseholdVoter extends Voter { - public const SHOW = PersonVoter::SEE; + public const EDIT = 'CHILL_PERSON_HOUSEHOLD_EDIT'; + + public const SEE = 'CHILL_PERSON_HOUSEHOLD_SEE'; + + /** + * @deprecated use @see{self::SEE} instead + */ + public const SHOW = self::SEE; + + private const ALL = [ + self::EDIT, self::SEE, + ]; + + private Security $security; + + public function __construct(Security $security) + { + $this->security = $security; + } + + protected function supports($attribute, $subject) + { + return $subject instanceof Household + && in_array($attribute, self::ALL, true); + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool + { + switch ($attribute) { + case self::SEE: + return $this->checkAssociatedMembersRole($subject, PersonVoter::SEE); + + case self::EDIT: + return $this->checkAssociatedMembersRole($subject, PersonVoter::UPDATE); + + default: + throw new UnexpectedValueException('attribute not supported'); + } + } + + private function checkAssociatedMembersRole(Household $household, string $attribute): bool + { + foreach ($household->getCurrentMembers()->map(static function (HouseholdMember $member) { + return $member->getPerson(); + }) as $person) { + if ($this->security->isGranted($attribute, $person)) { + return true; + } + } + + return false; + } } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php index e2fb0f7dc..757a0bfcb 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Serializer\Normalizer; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\HouseholdComposition; +use Chill\PersonBundle\Entity\Household\HouseholdCompositionType; use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Household\MembersEditor; @@ -22,6 +24,7 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use UnexpectedValueException; use function array_key_exists; class MembersEditorNormalizer implements DenormalizerAwareInterface, DenormalizerInterface @@ -148,6 +151,23 @@ class MembersEditorNormalizer implements DenormalizerAwareInterface, Denormalize ); } + if (null !== $data['composition']) { + $compositionType = $this->denormalizer->denormalize($data['composition']['household_composition_type'], HouseholdCompositionType::class, $format, $context); + $numberOfChildren = $data['composition']['number_of_children']; + $startDate = $this->denormalizer->denormalize($data['composition']['start_date'], DateTimeImmutable::class, $format, $context); + + if (null === $compositionType) { + throw new UnexpectedValueException('composition type cannot be null'); + } + + $householdComposition = (new HouseholdComposition()) + ->setHouseholdCompositionType($compositionType) + ->setNumberOfChildren($numberOfChildren) + ->setStartDate($startDate); + + $household->addComposition($householdComposition); + } + return $editor; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php index f2427bfe8..4706a14d8 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php @@ -169,6 +169,7 @@ final class HouseholdMemberControllerTest extends WebTestCase ], ], 'destination' => null, + 'composition' => null, ] ) ); @@ -223,6 +224,7 @@ final class HouseholdMemberControllerTest extends WebTestCase 'type' => 'household', 'id' => $householdId, ], + 'composition' => null, ] ) ); @@ -272,6 +274,7 @@ final class HouseholdMemberControllerTest extends WebTestCase 'destination' => [ 'type' => 'household', ], + 'composition' => null, ] ) ); diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php new file mode 100644 index 000000000..f63c12a2c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php @@ -0,0 +1,47 @@ +addComposition(($first = new HouseholdComposition()) + ->setStartDate(new DateTimeImmutable('2021-12-01'))); + + $this->assertNull($first->getEndDate()); + + $household->addComposition(($second = new HouseholdComposition()) + ->setStartDate(new DateTimeImmutable('2021-12-31'))); + + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $first->getEndDate()); + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); + + $household->addComposition(($inside = new HouseholdComposition()) + ->setStartDate(new DateTimeImmutable('2021-12-15'))); + + $this->assertEquals(new DateTimeImmutable('2021-12-15'), $first->getEndDate()); + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate()); + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/security.yaml b/src/Bundle/ChillPersonBundle/config/services/security.yaml index f54d4cdfb..0094e180b 100644 --- a/src/Bundle/ChillPersonBundle/config/services/security.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/security.yaml @@ -25,3 +25,7 @@ services: autoconfigure: true tags: - { name: security.voter } + + Chill\PersonBundle\Security\Authorization\HouseholdVoter: + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220121121310.php b/src/Bundle/ChillPersonBundle/migrations/Version20220121121310.php new file mode 100644 index 000000000..01fe951e1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220121121310.php @@ -0,0 +1,51 @@ +addSql('DROP SEQUENCE chill_person_household_composition_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_person_household_composition_type_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_person_household_composition'); + $this->addSql('DROP TABLE chill_person_household_composition_type'); + } + + public function getDescription(): string + { + return 'Create table for household composition'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE SEQUENCE chill_person_household_composition_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE chill_person_household_composition_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_person_household_composition (id INT NOT NULL, household_id INT NOT NULL, endDate DATE DEFAULT NULL, numberOfChildren INT DEFAULT NULL, startDate DATE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_comment TEXT DEFAULT NULL, comment_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_userId INT DEFAULT NULL, householdCompositionType_id INT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_E62BEE83E79FF843 ON chill_person_household_composition (household_id)'); + $this->addSql('CREATE INDEX IDX_E62BEE83CDEA4FCF ON chill_person_household_composition (householdCompositionType_id)'); + $this->addSql('CREATE INDEX IDX_E62BEE833174800F ON chill_person_household_composition (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_E62BEE8365FF1AEC ON chill_person_household_composition (updatedBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_person_household_composition.endDate IS \'(DC2Type:date_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_household_composition.startDate IS \'(DC2Type:date_immutable)\''); + $this->addSql('CREATE TABLE chill_person_household_composition_type (id INT NOT NULL, active BOOLEAN NOT NULL, label JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE83E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE83CDEA4FCF FOREIGN KEY (householdCompositionType_id) REFERENCES chill_person_household_composition_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE833174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_household_composition ADD CONSTRAINT FK_E62BEE8365FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('COMMENT ON COLUMN chill_person_household_composition.createdAt IS \'(DC2Type:date_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_household_composition.updatedAt IS \'(DC2Type:date_immutable)\''); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index fcf70c350..ecafb367e 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -98,3 +98,17 @@ household: from: Depuis to: Jusqu'au person history: Ménages + +household_composition: + Since: >- + Depuis le {startDate, date, long} + Still active: Toujours actif + Until: >- + Jusqu'au {endDate, date, long} + numberOfChildren children in household: >- + {numberOfChildren, plural, + =0 {Aucun enfant dans le ménage} + one {1 enfant dans le ménage} + few {# enfants dans le ménage} + other {# enfants dans le ménage} + } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 5d0e2b16a..20de73705 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -498,3 +498,14 @@ period_notification: You are getting a notification for a period which does not exists any more: Cette notification ne correspond pas à une période d'accompagnement valide. You are getting a notification for a period you are not allowed to see: La notification fait référence à une période d'accompagnement à laquelle vous n'avez pas accès. This is the minimal period details: Période d'accompagnement n° + +household_composition: + No composition yet: Aucune composition familiale renseignée + Compositions: Composition familiale + endDate: Date de fin + numberOfChildren: Nombre d'enfants mineurs au sein du ménage + Household composition: Composition du ménage + Composition added: Information sur la composition familiale ajoutée + Currently no composition: Aucune composition famiale renseignée. + Add a composition: Ajouter une composition familiale + Update composition: Modifier la composition familiale From c7dbaae8d61feff50f1523d6503ecb899fb40553 Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Mon, 24 Jan 2022 13:17:46 +0000 Subject: [PATCH 041/109] 105 worflow --- CHANGELOG.md | 7 + .../views/Activity/_list_item.html.twig | 14 +- .../Resources/views/Activity/list.html.twig | 4 - .../Resources/views/Activity/show.html.twig | 2 +- .../_workflow.html.twig | 52 +++ .../_workflow.title.html.twig | 19 + .../AccompanyingCourseDocument/show.html.twig | 100 ++-- ...ompanyingCourseDocumentWorkflowHandler.php | 74 +++ .../ChillDocStoreBundle/config/services.yaml | 5 + .../translations/messages.fr.yml | 1 + .../ChillMainBundle/ChillMainBundle.php | 3 + .../Controller/WorkflowApiController.php | 118 +++++ .../Controller/WorkflowController.php | 257 ++++++++++ .../Entity/Workflow/EntityWorkflow.php | 439 ++++++++++++++++++ .../Entity/Workflow/EntityWorkflowComment.php | 77 +++ .../Entity/Workflow/EntityWorkflowStep.php | 336 ++++++++++++++ .../Form/EntityWorkflowCommentType.php | 27 ++ .../DataTransformer/UserToJsonTransformer.php | 10 +- .../ChillMainBundle/Form/WorkflowStepType.php | 111 +++++ .../Form/WorkflowTransitionType.php | 36 ++ .../Workflow/EntityWorkflowRepository.php | 137 ++++++ .../Resources/public/chill/chillmain.scss | 68 ++- .../Resources/public/chill/scss/buttons.scss | 3 + .../public/chill/scss/flex_table.scss | 9 + .../public/chill/scss/notification.scss | 12 + .../public/chill/scss/render_box.scss | 3 + .../public/lib/entity-workflow/api.js | 12 + .../public/module/bootstrap/index.js | 14 +- .../module/entity-workflow-pick/index.js | 49 ++ .../module/entity-workflow-subscribe/index.js | 32 ++ .../public/module/pick-entity/index.js | 2 +- .../public/module/wopi-link/index.js | 29 ++ .../public/page/workflow-show/index.js | 30 ++ .../EntityWorkflowVueSubscriber.vue | 101 ++++ .../EntityWorkflow/ListWorkflow.vue | 36 ++ .../EntityWorkflow/PickWorkflow.vue | 50 ++ .../public/vuejs/_components/Modal.vue | 13 +- .../public/vuejs/_components/OpenWopiLink.vue | 243 ++++++++++ .../Resources/views/Admin/layout.html.twig | 2 +- .../views/Entity/CommentEmbeddable.html.twig | 24 +- .../views/Menu/verticalMenu.html.twig | 14 +- .../views/Notification/_list_item.html.twig | 8 +- ...ension_counter_notifications_for.html.twig | 14 +- .../views/Notification/list.html.twig | 2 +- .../views/Notification/show.html.twig | 4 +- .../views/Workflow/_attachment.html.twig | 120 +++++ .../views/Workflow/_comment.html.twig | 13 + .../views/Workflow/_decision.html.twig | 48 ++ .../_extension_list_workflow_for.html.twig | 13 + .../views/Workflow/_follow.html.twig | 8 + .../views/Workflow/_history.html.twig | 59 +++ .../Workflow/_notification_include.html.twig | 12 + .../Resources/views/Workflow/index.html.twig | 56 +++ .../Resources/views/Workflow/list.html.twig | 97 ++++ .../views/Workflow/macro_breadcrumb.html.twig | 45 ++ ...n_transition_completed_content.fr.txt.twig | 13 + ..._on_transition_completed_title.fr.txt.twig | 5 + .../Routing/MenuBuilder/UserMenuBuilder.php | 9 + .../Authorization/EntityWorkflowVoter.php | 70 +++ .../Entity/Workflow/EntityWorkflowTest.php | 81 ++++ .../EntityWorkflowHandlerInterface.php | 39 ++ .../Workflow/EntityWorkflowManager.php | 48 ++ ...ntityWorkflowTransitionEventSubscriber.php | 114 +++++ .../NotificationOnTransition.php | 103 ++++ .../Exception/HandlerNotFoundException.php | 18 + .../Workflow/Helper/MetadataExtractor.php | 74 +++ .../WorkflowNotificationHandler.php | 45 ++ .../RelatedEntityWorkflowSupportsStrategy.php | 35 ++ .../Templating/WorkflowTwigExtension.php | 29 ++ .../WorkflowTwigExtensionRuntime.php | 79 ++++ .../Validator/EntityWorkflowCreation.php | 35 ++ .../EntityWorkflowCreationValidator.php | 71 +++ .../ChillMainBundle/chill.webpack.config.js | 4 + .../ChillMainBundle/config/services.yaml | 14 + .../ChillMainBundle/config/services/form.yaml | 2 + .../config/services/security.yaml | 2 + .../migrations/Version20220112123436.php | 72 +++ .../migrations/Version20220114132105.php | 47 ++ .../migrations/Version20220114165950.php | 37 ++ .../translations/messages.fr.yml | 40 +- ...mpanyingPeriodWorkEvaluationRepository.php | 61 +++ .../vuejs/AccompanyingCourseWorkEdit/App.vue | 20 +- .../components/AddEvaluation.vue | 35 +- .../components/FormEvaluation.vue | 36 +- .../vuejs/AccompanyingCourseWorkEdit/store.js | 4 +- .../Comment/index.html.twig | 6 +- .../views/AccompanyingCourse/index.html.twig | 19 +- .../AccompanyingCourseWork/_item.html.twig | 127 +++++ .../AccompanyingCourseWork/edit.html.twig | 23 +- .../AccompanyingCourseWork/index.html.twig | 132 +----- ...st_recent_by_accompanying_period.html.twig | 9 +- .../views/AccompanyingPeriod/_list.html.twig | 4 - .../AccompanyingPeriod/_list_item.html.twig | 20 +- .../Resources/views/Macro/updatedBy.html.twig | 12 + .../views/Person/household_history.html.twig | 2 +- .../views/Person/list_with_period.html.twig | 247 +++++----- .../Resources/views/Person/view.html.twig | 6 +- .../_accompanying_period_work.html.twig | 17 + .../_accompanying_period_work.title.html.twig | 19 + .../views/Workflow/_evaluation.html.twig | 99 ++++ .../Workflow/_evaluation.title.html.twig | 19 + .../AccompanyingPeriodWorkEvaluationVoter.php | 51 ++ .../AccompanyingPeriodWorkVoter.php | 57 +++ ...mpanyingPeriodWorkEvaluationNormalizer.php | 74 +++ .../AccompanyingPeriodWorkNormalizer.php | 85 ++++ .../Normalizer/WorkflowNormalizer.php | 70 +++ ...ingPeriodWorkEvaluationWorkflowHandler.php | 71 +++ .../AccompanyingPeriodWorkWorkflowHandler.php | 70 +++ .../ChillPersonBundle/config/services.yaml | 5 + .../config/services/security.yaml | 8 + 110 files changed, 5176 insertions(+), 392 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.title.html.twig create mode 100644 src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php create mode 100644 src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php create mode 100644 src/Bundle/ChillMainBundle/Controller/WorkflowController.php create mode 100644 src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php create mode 100644 src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php create mode 100644 src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php create mode 100644 src/Bundle/ChillMainBundle/Form/EntityWorkflowCommentType.php create mode 100644 src/Bundle/ChillMainBundle/Form/WorkflowStepType.php create mode 100644 src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php create mode 100644 src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php create mode 100644 src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-pick/index.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-subscribe/index.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OpenWopiLink.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_comment.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_extension_list_workflow_for.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_follow.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/_notification_include.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/list.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig create mode 100644 src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Exception/HandlerNotFoundException.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/RelatedEntityWorkflowSupportsStrategy.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtension.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreation.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreationValidator.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220112123436.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220114132105.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220114165950.php create mode 100644 src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Macro/updatedBy.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Workflow/_accompanying_period_work.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Workflow/_accompanying_period_work.title.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation.title.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkEvaluationVoter.php create mode 100644 src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php create mode 100644 src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php create mode 100644 src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php create mode 100644 src/Bundle/ChillPersonBundle/Serializer/Normalizer/WorkflowNormalizer.php create mode 100644 src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php create mode 100644 src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3a8da23..9fe5fc94b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to * [notification] add `[Chill]` in the subject of each notification, automatically * [notification] add a counter for notification in activity list and accompanying period list, and search results * [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before. +* [workflow] add breadcrumb to show steps +* [popover] add popover html popup mechanism (used by workflow breadcrumb) +* [templates] improve updatedBy macro in item metadatas ## Test releases @@ -28,6 +31,10 @@ and this project adheres to * [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246) * [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176) * [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380) +* [workflow] + * add My workflow section with my opened subscriptions + * apply workflow on documents, accompanyingCourseWork and Evaluations +* [wopi-link] a new vue component allow to open wopi link in a fullscreen chill-themed modal ### test release 2022-01-17 diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig index 09a7ab2a3..6b2e33dfb 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/_list_item.html.twig @@ -143,9 +143,17 @@
    -
      - {{ recordAction }} -
    +
    + {% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %} + {% if notif_counter.total > 0 %} + {{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }} + {% endif %} +
    +
    +
      + {{ recordAction }} +
    +
    diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig index 6b0639a27..7ae24fa4b 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig @@ -1,10 +1,6 @@ {% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %} {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %} {% if no_action is not defined or no_action == false %} - {% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %} - {% if notif_counter.total > 0 %} -
  • {{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }}
  • - {% endif %}
  • {{ 'Activity data'|trans }} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig new file mode 100644 index 000000000..4ec11a8e8 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig @@ -0,0 +1,52 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} + +
    +
    +
    +
    + +
    +
    +

    {{ document.title }}

    + {{ document.object.type }} + + {% if document.description is not empty %} +
    + {{ document.description }} +
    + {% endif %} + +
    +
    +
    +
    + +{% if display_action is defined and display_action == true %} +
      +
    • + {{ m.download_button(document.object, document.title) }} +
    • +
    • + + {# + data-button is optional ! + OPTIONS: + 'changeIcon' string + 'changeClass' string + 'noText' boolean + + #}{% set button = { + 'changeIcon': 'fa-unlock', + } %} + + {# vue component #} + +
    • +
    +{% endif %} \ No newline at end of file diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.title.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.title.html.twig new file mode 100644 index 000000000..e3c1dc50a --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.title.html.twig @@ -0,0 +1,19 @@ +{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as m %} + +
    +
    + {% if concerne is defined and concerne == true %} + {{ 'Concerne'|trans }}: + {% endif %} + + {{ 'workflow.Document (n°%doc%)'|trans({'%doc%': document.id}) }} + + {% if description is defined and description == true %} + {{ ' — ' ~ document.title }} + {% endif %} +
    + + {% if breadcrumb is defined and breadcrumb == true %} + {{ m.breadcrumb(_context) }} + {% endif %} +
    \ No newline at end of file diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig index 97299675a..9d6175e12 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig @@ -6,61 +6,71 @@ {% block title %} {# {{ 'Detail of document of %name%'|trans({ '%name%': accompanyingCourse|chill_entity_render_string } ) }} #} -{% endblock %} - - -{% block js %} - {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} + {{ 'Document %title%' | trans({ '%title%': document.title }) }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_entity_workflow_pick') }} {% endblock %} {% block content %} - -

    {{ 'Document %title%' | trans({ '%title%': document.title }) }}

    - -
    -
    {{ 'Title'|trans }}
    -
    {{ document.title }}
    - +
    +{% endblock %} + +{% block block_post_menu %} +
    + {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} + {% if workflows_frame is not empty %} + {{ workflows_frame|raw }} + {% endif %} +
    +{% endblock %} -
    {{ 'Description' | trans }}
    -
    - {% if document.description is empty %} - {{ 'Any description'|trans }} - {% else %} -
    - {{ document.description|chill_markdown_to_html }} -
    - {% endif %} -
    - -
    - -
      -
    • - - {{ 'Back to the list' | trans }} - -
    • - -
    • - {{ m.download_button(document.object, document.title) }} -
    • - - {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %} -
    • - - {{ 'Edit' | trans }} - -
    • - {% endif %} - {% endblock %} +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_entity_workflow_pick') }} +{% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php new file mode 100644 index 000000000..a615ddda2 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php @@ -0,0 +1,74 @@ +repository = $em->getRepository(AccompanyingCourseDocument::class); + } + + public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingCourseDocument + { + return $this->repository->find($entityWorkflow->getRelatedEntityId()); + } + + public function getRoleShow(EntityWorkflow $entityWorkflow): ?string + { + return null; + } + + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string + { + return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig'; + } + + public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array + { + return [ + 'entity_workflow' => $entityWorkflow, + 'document' => $this->getRelatedEntity($entityWorkflow), + ]; + } + + public function getTemplateTitle(EntityWorkflow $entityWorkflow, array $options = []): string + { + return '@ChillDocStore/AccompanyingCourseDocument/_workflow.title.html.twig'; + } + + public function getTemplateTitleData(EntityWorkflow $entityWorkflow, array $options = []): array + { + return $this->getTemplateData($entityWorkflow, $options); + } + + public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool + { + return $entityWorkflow->getRelatedEntityClass() === AccompanyingCourseDocument::class; + } + + public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool + { + return true; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 2766cb1c7..6685d22eb 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -27,3 +27,8 @@ services: autoconfigure: true tags: - { name: chill.role } + + Chill\DocStoreBundle\Workflow\: + resource: './../Workflow/' + autoconfigure: true + autowire: true diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 947471531..3ee10721c 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -9,6 +9,7 @@ Create new document: Créer un nouveau document New document for %name%: Nouveau document pour %name% Editing document for %name%: Modification d'un document pour %name% Edit Document: Modification d'un document +Update document: Modifier le document Existing document: Document existant No document to download: Aucun document à télécharger 'Choose a document category': Choisissez une catégorie de document diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php index 7a4e563bb..07daf65ea 100644 --- a/src/Bundle/ChillMainBundle/ChillMainBundle.php +++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php @@ -31,6 +31,7 @@ use Chill\MainBundle\Security\Resolver\ScopeResolverInterface; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass; use Chill\MainBundle\Templating\UI\NotificationCounterInterface; +use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -56,6 +57,8 @@ class ChillMainBundle extends Bundle ->addTag('chill_main.notification_handler'); $container->registerForAutoconfiguration(NotificationCounterInterface::class) ->addTag('chill.count_notification.user'); + $container->registerForAutoconfiguration(EntityWorkflowHandlerInterface::class) + ->addTag('chill_main.workflow_handler'); $container->addCompilerPass(new SearchableServicesCompilerPass()); $container->addCompilerPass(new ConfigConsistencyCompilerPass()); diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php new file mode 100644 index 000000000..dda787708 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php @@ -0,0 +1,118 @@ +entityManager = $entityManager; + $this->security = $security; + } + + /** + * @Route("/api/1.0/main/workflow/{id}/subscribe", methods={"POST"}) + */ + public function subscribe(EntityWorkflow $entityWorkflow, Request $request): Response + { + return $this->handleSubscription($entityWorkflow, $request, 'subscribe'); + } + + /** + * @Route("/api/1.0/main/workflow/{id}/unsubscribe", methods={"POST"}) + */ + public function unsubscribe(EntityWorkflow $entityWorkflow, Request $request): Response + { + return $this->handleSubscription($entityWorkflow, $request, 'unsubscribe'); + } + + private function handleSubscription(EntityWorkflow $entityWorkflow, Request $request, string $action): JsonResponse + { + if (!$this->security->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + throw new AccessDeniedException(); + } + + if (!$request->query->has('subscribe')) { + throw new BadRequestHttpException('missing subscribe parameter'); + } + + $user = $this->security->getUser(); + + switch ($request->query->get('subscribe')) { + case 'final': + switch ($action) { + case 'subscribe': + $entityWorkflow->addSubscriberToFinal($user); + + break; + + case 'unsubscribe': + $entityWorkflow->removeSubscriberToFinal($user); + + break; + + default: + throw new LogicException(); + } + + break; + + case 'step': + switch ($action) { + case 'subscribe': + $entityWorkflow->addSubscriberToStep($user); + + break; + + case 'unsubscribe': + $entityWorkflow->removeSubscriberToStep($user); + + break; + + default: + throw new LogicException(); + } + + break; + + default: + throw new BadRequestHttpException('subscribe parameter must be equal to "step" or "final"'); + } + + $this->entityManager->flush(); + + return new JsonResponse( + [ + 'step' => $entityWorkflow->isUserSubscribedToStep($user), + 'final' => $entityWorkflow->isUserSubscribedToFinal($user), + ], + JsonResponse::HTTP_OK, + [], + false + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php new file mode 100644 index 000000000..33f1bd774 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php @@ -0,0 +1,257 @@ +entityWorkflowManager = $entityWorkflowManager; + $this->entityWorkflowRepository = $entityWorkflowRepository; + $this->validator = $validator; + $this->paginatorFactory = $paginatorFactory; + $this->registry = $registry; + $this->entityManager = $entityManager; + $this->translator = $translator; + } + + /** + * @Route("/{_locale}/main/workflow/create", name="chill_main_workflow_create") + */ + public function create(Request $request): Response + { + if (!$request->query->has('entityClass')) { + throw new BadRequestHttpException('Missing entityClass parameter'); + } + + if (!$request->query->has('entityId')) { + throw new BadRequestHttpException('missing entityId parameter'); + } + + if (!$request->query->has('workflow')) { + throw new BadRequestHttpException('missing workflow parameter'); + } + + $entityWorkflow = new EntityWorkflow(); + $entityWorkflow + ->setRelatedEntityClass($request->query->get('entityClass')) + ->setRelatedEntityId($request->query->getInt('entityId')) + ->setWorkflowName($request->query->get('workflow')); + + $errors = $this->validator->validate($entityWorkflow, null, ['creation']); + + if (count($errors) > 0) { + $msg = []; + + foreach ($errors as $error) { + /** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */ + $msg[] = $error->getMessage(); + } + + return new Response(implode("\n", $msg), Response::HTTP_UNPROCESSABLE_ENTITY); + } + + $this->denyAccessUnlessGranted(EntityWorkflowVoter::CREATE, $entityWorkflow); + + $em = $this->getDoctrine()->getManager(); + $em->persist($entityWorkflow); + $em->flush(); + + return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]); + } + + /** + * @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest") + */ + public function myWorkflowsDest(Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + $total = $this->entityWorkflowRepository->countByDest($this->getUser()); + $paginator = $this->paginatorFactory->create($total); + + $workflows = $this->entityWorkflowRepository->findByDest( + $this->getUser(), + ['createdAt' => 'DESC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return $this->render( + '@ChillMain/Workflow/list.html.twig', + [ + 'workflows' => $this->buildHandler($workflows), + 'paginator' => $paginator, + 'step' => 'dest', + ] + ); + } + + /** + * @Route("/{_locale}/main/workflow/list/subscribed", name="chill_main_workflow_list_subscribed") + */ + public function myWorkflowsSubscribed(Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + $total = $this->entityWorkflowRepository->countBySubscriber($this->getUser()); + $paginator = $this->paginatorFactory->create($total); + + $workflows = $this->entityWorkflowRepository->findBySubscriber( + $this->getUser(), + ['createdAt' => 'DESC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return $this->render( + '@ChillMain/Workflow/list.html.twig', + [ + 'workflows' => $this->buildHandler($workflows), + 'paginator' => $paginator, + 'step' => 'subscribed', + ] + ); + } + + /** + * @Route("/{_locale}/main/workflow/{id}/show", name="chill_main_workflow_show") + */ + public function show(EntityWorkflow $entityWorkflow, Request $request): Response + { + $this->denyAccessUnlessGranted(EntityWorkflowVoter::SEE, $entityWorkflow); + + $handler = $this->entityWorkflowManager->getHandler($entityWorkflow); + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) { + // possible transition + $transitionForm = $this->createForm( + WorkflowStepType::class, + $entityWorkflow->getCurrentStep(), + ['transition' => true, 'entity_workflow' => $entityWorkflow] + ); + + $transitionForm->handleRequest($request); + + if ($transitionForm->isSubmitted() && $transitionForm->isValid()) { + if (!$workflow->can($entityWorkflow, $transition = $transitionForm['transition']->getData()->getName())) { + $blockers = $workflow->buildTransitionBlockerList($entityWorkflow, $transition); + $msgs = array_map(function (TransitionBlocker $tb) { + return $this->translator->trans( + $tb->getMessage(), + $tb->getParameters() + ); + }, iterator_to_array($blockers)); + + throw $this->createAccessDeniedException( + sprintf( + "not allowed to apply transition {$transition}: %s", + implode(', ', $msgs) + ) + ); + } + + $workflow->apply($entityWorkflow, $transition); + + foreach ($transitionForm['future_dest_users']->getData() as $user) { + $entityWorkflow->getCurrentStep()->addDestUser($user); + } + + $this->entityManager->flush(); + + return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]); + } + + if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) { + $this->addFlash('error', $this->translator->trans('This form contains errors')); + } + } + + /* + $commentForm = $this->createForm(EntityWorkflowCommentType::class, $newComment = new EntityWorkflowComment()); + $commentForm->handleRequest($request); + + if ($commentForm->isSubmitted() && $commentForm->isValid()) { + $this->entityManager->persist($newComment); + $this->entityManager->flush(); + + $this->addFlash('success', $this->translator->trans('workflow.Comment added')); + + return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]); + } elseif ($commentForm->isSubmitted() && !$commentForm->isValid()) { + $this->addFlash('error', $this->translator->trans('This form contains errors')); + } + */ + + return $this->render( + '@ChillMain/Workflow/index.html.twig', + [ + 'handler_template' => $handler->getTemplate($entityWorkflow), + 'handler_template_title' => $handler->getTemplateTitle($entityWorkflow), + 'handler_template_data' => $handler->getTemplateData($entityWorkflow), + 'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null, + 'entity_workflow' => $entityWorkflow, + //'comment_form' => $commentForm->createView(), + ] + ); + } + + private function buildHandler(array $workflows): array + { + $lines = []; + + foreach ($workflows as $workflow) { + $handler = $this->entityWorkflowManager->getHandler($workflow); + $lines[] = [ + 'handler' => $handler, + 'entity_workflow' => $workflow, + ]; + } + + return $lines; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php new file mode 100644 index 000000000..b56435f5d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -0,0 +1,439 @@ +subscriberToFinal = new ArrayCollection(); + $this->subscriberToStep = new ArrayCollection(); + $this->comments = new ArrayCollection(); + $this->steps = new ArrayCollection(); + + $initialStep = new EntityWorkflowStep(); + $initialStep + ->setCurrentStep('initial'); + $this->addStep($initialStep); + } + + public function addComment(EntityWorkflowComment $comment): self + { + if (!$this->comments->contains($comment)) { + $this->comments[] = $comment; + $comment->setEntityWorkflow($this); + } + + return $this; + } + + /** + * @internal You should prepare a step and run a workflow transition instead of manually adding a step + */ + public function addStep(EntityWorkflowStep $step): self + { + if (!$this->steps->contains($step)) { + $this->steps[] = $step; + $step->setEntityWorkflow($this); + + if ($this->isFinalize()) { + $step->setFinalizeAfter(true); + } + } + + return $this; + } + + public function addSubscriberToFinal(User $user): self + { + if (!$this->subscriberToFinal->contains($user)) { + $this->subscriberToFinal[] = $user; + } + + return $this; + } + + public function addSubscriberToStep(User $user): self + { + if (!$this->subscriberToStep->contains($user)) { + $this->subscriberToStep[] = $user; + } + + return $this; + } + + public function getComments(): Collection + { + return $this->comments; + } + + public function getCurrentStep(): ?EntityWorkflowStep + { + $step = $this->steps->last(); + + if (false !== $step) { + return $step; + } + + return null; + } + + public function getCurrentStepCreatedAt(): ?DateTimeInterface + { + if (null !== $previous = $this->getPreviousStepIfAny()) { + return $previous->getTransitionAt(); + } + + return null; + } + + public function getCurrentStepCreatedBy(): ?User + { + if (null !== $previous = $this->getPreviousStepIfAny()) { + return $previous->getTransitionBy(); + } + + return null; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getRelatedEntityClass(): string + { + return $this->relatedEntityClass; + } + + public function getRelatedEntityId(): int + { + return $this->relatedEntityId; + } + + /** + * Method used by MarkingStore. + * + * get a string representation of the step + */ + public function getStep(): string + { + return $this->getCurrentStep()->getCurrentStep(); + } + + public function getStepAfter(EntityWorkflowStep $step): ?EntityWorkflowStep + { + $iterator = $this->steps->getIterator(); + + if ($iterator instanceof Iterator) { + $iterator->rewind(); + + while ($iterator->valid()) { + $curStep = $iterator->current(); + + if ($curStep === $step) { + $iterator->next(); + + if ($iterator->valid()) { + return $iterator->current(); + } + + return null; + } + $iterator->next(); + } + + return null; + } + + throw new RuntimeException(); + } + + /** + * @return ArrayCollection|Collection + */ + public function getSteps() + { + return $this->steps; + } + + public function getStepsChained(): array + { + $iterator = $this->steps->getIterator(); + $previous = $next = $current = null; + $steps = []; + + $iterator->rewind(); + + while ($iterator->valid()) { + $previous = $current; + $steps[] = $current = $iterator->current(); + $current->setPrevious($previous); + + $iterator->next(); + + if ($iterator->valid()) { + $next = $iterator->current(); + } else { + $next = null; + } + + $current->setNext($next); + } + + return $steps; + } + + /** + * @return ArrayCollection|Collection + */ + public function getSubscriberToFinal() + { + return $this->subscriberToFinal; + } + + /** + * @return ArrayCollection|Collection + */ + public function getSubscriberToStep() + { + return $this->subscriberToStep; + } + + /** + * get the step which is transitionning. Should be called only by event which will + * concern the transition. + */ + public function getTransitionningStep(): ?EntityWorkflowStep + { + return $this->transitionningStep; + } + + public function getWorkflowName(): string + { + return $this->workflowName; + } + + public function isFinalize(): bool + { + $steps = $this->getStepsChained(); + + if (1 === count($steps)) { + // the initial step cannot be finalized + return false; + } + + /** @var EntityWorkflowStep $last */ + $last = end($steps); + + return $last->getPrevious()->isFinalizeAfter(); + } + + public function isFreeze(): bool + { + $steps = $this->getStepsChained(); + + if (1 === count($steps)) { + // the initial step cannot be finalized + return false; + } + + /** @var EntityWorkflowStep $last */ + $last = end($steps); + + return $last->getPrevious()->isFreezeAfter(); + } + + public function isUserSubscribedToFinal(User $user): bool + { + return $this->subscriberToFinal->contains($user); + } + + public function isUserSubscribedToStep(User $user): bool + { + return $this->subscriberToStep->contains($user); + } + + public function prepareStepBeforeTransition(EntityWorkflowStep $step): self + { + $this->transitionningStep = $step; + + return $this; + } + + public function removeComment(EntityWorkflowComment $comment): self + { + if ($this->comments->removeElement($comment)) { + $comment->setEntityWorkflow(null); + } + + return $this; + } + + public function removeStep(EntityWorkflowStep $step): self + { + if ($this->steps->removeElement($step)) { + $step->setEntityWorkflow(null); + } + + return $this; + } + + public function removeSubscriberToFinal(User $user): self + { + $this->subscriberToFinal->removeElement($user); + + return $this; + } + + public function removeSubscriberToStep(User $user): self + { + $this->subscriberToStep->removeElement($user); + + return $this; + } + + public function setRelatedEntityClass(string $relatedEntityClass): EntityWorkflow + { + $this->relatedEntityClass = $relatedEntityClass; + + return $this; + } + + public function setRelatedEntityId(int $relatedEntityId): EntityWorkflow + { + $this->relatedEntityId = $relatedEntityId; + + return $this; + } + + /** + * Method use by marking store. + * + * @return $this + */ + public function setStep(string $step): self + { + $newStep = new EntityWorkflowStep(); + $newStep->setCurrentStep($step); + + // copy the freeze + if ($this->getCurrentStep()->isFreezeAfter()) { + $newStep->setFreezeAfter(true); + } + + $this->addStep($newStep); + + return $this; + } + + public function setWorkflowName(string $workflowName): EntityWorkflow + { + $this->workflowName = $workflowName; + + return $this; + } + + private function getPreviousStepIfAny(): ?EntityWorkflowStep + { + if (1 === count($this->steps)) { + return null; + } + + return $this->steps->get($this->steps->count() - 2); + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php new file mode 100644 index 000000000..b041a5aa3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowComment.php @@ -0,0 +1,77 @@ +comment; + } + + public function getEntityWorkflow(): ?EntityWorkflow + { + return $this->entityWorkflow; + } + + public function getId(): ?int + { + return $this->id; + } + + public function setComment(string $comment): self + { + $this->comment = $comment; + + return $this; + } + + /** + * @internal use @see{EntityWorkflow::addComment} + */ + public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): self + { + $this->entityWorkflow = $entityWorkflow; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php new file mode 100644 index 000000000..6bd39ae0c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php @@ -0,0 +1,336 @@ +destUser = new ArrayCollection(); + } + + public function addDestEmail(string $email): self + { + if (!in_array($email, $this->destEmail, true)) { + $this->destEmail[] = $email; + } + + return $this; + } + + public function addDestUser(User $user): self + { + if (!$this->destUser->contains($user)) { + $this->destUser[] = $user; + } + + return $this; + } + + public function getComment(): string + { + return $this->comment; + } + + public function getCurrentStep(): ?string + { + return $this->currentStep; + } + + public function getDestEmail(): array + { + return $this->destEmail; + } + + /** + * @return ArrayCollection|Collection + */ + public function getDestUser() + { + return $this->destUser; + } + + public function getEntityWorkflow(): ?EntityWorkflow + { + return $this->entityWorkflow; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getNext(): ?EntityWorkflowStep + { + return $this->next; + } + + public function getPrevious(): ?EntityWorkflowStep + { + return $this->previous; + } + + public function getTransitionAfter(): ?string + { + return $this->transitionAfter; + } + + public function getTransitionAt(): ?DateTimeImmutable + { + return $this->transitionAt; + } + + public function getTransitionBy(): ?User + { + return $this->transitionBy; + } + + public function getTransitionByEmail(): ?string + { + return $this->transitionByEmail; + } + + public function isFinalizeAfter(): bool + { + return $this->finalizeAfter; + } + + public function isFreezeAfter(): bool + { + return $this->freezeAfter; + } + + public function removeDestEmail(string $email): self + { + $this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) { + return $email !== $existing; + }); + + return $this; + } + + public function removeDestUser(User $user): self + { + $this->destUser->removeElement($user); + + return $this; + } + + public function setComment(?string $comment): EntityWorkflowStep + { + $this->comment = (string) $comment; + + return $this; + } + + public function setCurrentStep(?string $currentStep): EntityWorkflowStep + { + $this->currentStep = $currentStep; + + return $this; + } + + public function setDestEmail(array $destEmail): EntityWorkflowStep + { + $this->destEmail = $destEmail; + + return $this; + } + + /** + * @internal use @see(EntityWorkflow::addStep} instead + */ + public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): EntityWorkflowStep + { + $this->entityWorkflow = $entityWorkflow; + + return $this; + } + + public function setFinalizeAfter(bool $finalizeAfter): EntityWorkflowStep + { + $this->finalizeAfter = $finalizeAfter; + + return $this; + } + + public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep + { + $this->freezeAfter = $freezeAfter; + + return $this; + } + + /** + * @return EntityWorkflowStep + * + * @internal + */ + public function setNext(?EntityWorkflowStep $next): self + { + $this->next = $next; + + return $this; + } + + /** + * @return EntityWorkflowStep + * + * @internal + */ + public function setPrevious(?EntityWorkflowStep $previous): self + { + $this->previous = $previous; + + return $this; + } + + public function setTransitionAfter(?string $transitionAfter): EntityWorkflowStep + { + $this->transitionAfter = $transitionAfter; + + return $this; + } + + public function setTransitionAt(?DateTimeImmutable $transitionAt): EntityWorkflowStep + { + $this->transitionAt = $transitionAt; + + return $this; + } + + public function setTransitionBy(?User $transitionBy): EntityWorkflowStep + { + $this->transitionBy = $transitionBy; + + return $this; + } + + public function setTransitionByEmail(?string $transitionByEmail): EntityWorkflowStep + { + $this->transitionByEmail = $transitionByEmail; + + return $this; + } + + /** + * @Assert\Callback + * + * @param mixed $payload + */ + public function validateOnCreation(ExecutionContextInterface $context, $payload): void + { + return; + + if ($this->isFinalizeAfter()) { + if (0 !== count($this->getDestUser())) { + $context->buildViolation('workflow.No dest users when the workflow is finalized') + ->atPath('finalizeAfter') + ->addViolation(); + } + } else { + if (0 === count($this->getDestUser())) { + $context->buildViolation('workflow.The next step must count at least one dest') + ->atPath('finalizeAfter') + ->addViolation(); + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/Form/EntityWorkflowCommentType.php b/src/Bundle/ChillMainBundle/Form/EntityWorkflowCommentType.php new file mode 100644 index 000000000..c32e09dfd --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/EntityWorkflowCommentType.php @@ -0,0 +1,27 @@ +add('comment', ChillTextareaType::class, [ + 'required' => false, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/UserToJsonTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/UserToJsonTransformer.php index df670f891..ca87ea4f5 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/UserToJsonTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/UserToJsonTransformer.php @@ -36,14 +36,20 @@ class UserToJsonTransformer implements DataTransformerInterface public function reverseTransform($value) { + $denormalized = json_decode($value, true); + if ($this->multiple) { + if (null === $denormalized) { + return []; + } + return array_map( function ($item) { return $this->denormalizeOne($item); }, - json_decode($value, true) + $denormalized ); } - return $this->denormalizeOne(json_decode($value, true)); + return $this->denormalizeOne($denormalized); } /** diff --git a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php new file mode 100644 index 000000000..6600e330a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php @@ -0,0 +1,111 @@ +entityWorkflowManager = $entityWorkflowManager; + $this->registry = $registry; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + /** @var \Chill\MainBundle\Entity\Workflow\EntityWorkflow $entityWorkflow */ + $entityWorkflow = $options['entity_workflow']; + $handler = $this->entityWorkflowManager->getHandler($entityWorkflow); + + if (true === $options['transition']) { + if (null === $options['entity_workflow']) { + throw new LogicException('if transition is true, entity_workflow should be defined'); + } + + $transitions = $this->registry + ->get($options['entity_workflow'], $entityWorkflow->getWorkflowName()) + ->getEnabledTransitions($entityWorkflow); + + $choices = array_combine( + array_map(static function (Transition $transition) { return $transition->getName(); }, $transitions), + $transitions + ); + + $builder + ->add('transition', ChoiceType::class, [ + 'label' => 'workflow.Transition', + 'mapped' => false, + 'multiple' => false, + 'expanded' => true, + 'choices' => $choices, + 'choice_label' => static function (Transition $transition) { + return implode(', ', $transition->getTos()); + }, + ]) + ->add('future_dest_users', PickUserDynamicType::class, [ + 'label' => 'workflow.dest for next steps', + 'multiple' => true, + 'mapped' => false, + ]); + } + + if ( + $handler->supportsFreeze($entityWorkflow) + && !$entityWorkflow->isFreeze() + ) { + $builder + ->add('freezeAfter', CheckboxType::class, [ + 'required' => false, + 'label' => 'workflow.Freeze', + 'help' => 'workflow.The associated element will be freezed', + ]); + } + + $builder + ->add('finalizeAfter', CheckboxType::class, [ + 'required' => false, + 'label' => 'workflow.Finalize', + 'help' => 'workflow.The workflow will be finalized', + ]) + ->add('comment', ChillTextareaType::class, [ + 'required' => false, + 'label' => 'Comment', + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefined('class', EntityWorkflowStep::class) + ->setRequired('transition') + ->setAllowedTypes('transition', 'bool') + ->setRequired('entity_workflow') + ->setAllowedTypes('entity_workflow', EntityWorkflow::class); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php b/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php new file mode 100644 index 000000000..1692928dd --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php @@ -0,0 +1,36 @@ +add('current_step', WorkflowStepType::class, [ + 'transition' => true, + 'entity_workflow' => $options['entity_workflow'], + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired('entity_workflow') + ->setAllowedTypes('entity_workflow', EntityWorkflow::class); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php new file mode 100644 index 000000000..cac5c9996 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php @@ -0,0 +1,137 @@ +repository = $entityManager->getRepository(EntityWorkflow::class); + } + + public function countByDest(User $user): int + { + $qb = $this->buildQueryByDest($user)->select('count(ew)'); + + return (int) $qb->getQuery()->getSingleScalarResult(); + } + + public function countBySubscriber(User $user): int + { + $qb = $this->buildQueryBySubscriber($user)->select('count(ew)'); + + return (int) $qb->getQuery()->getSingleScalarResult(); + } + + public function find($id): ?EntityWorkflow + { + return $this->repository->find($id); + } + + /** + * @return array|EntityWorkflow[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @param null|mixed $limit + * @param null|mixed $offset + * + * @return array|EntityWorkflow[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findByDest(User $user, ?array $orderBy = null, $limit = null, $offset = null): array + { + $qb = $this->buildQueryByDest($user)->select('ew'); + + foreach ($orderBy as $key => $sort) { + $qb->addOrderBy('ew.' . $key, $sort); + } + + $qb->setMaxResults($limit)->setFirstResult($offset); + + return $qb->getQuery()->getResult(); + } + + public function findBySubscriber(User $user, ?array $orderBy = null, $limit = null, $offset = null): array + { + $qb = $this->buildQueryBySubscriber($user)->select('ew'); + + foreach ($orderBy as $key => $sort) { + $qb->addOrderBy('ew.' . $key, $sort); + } + + $qb->setMaxResults($limit)->setFirstResult($offset); + + return $qb->getQuery()->getResult(); + } + + public function findOneBy(array $criteria): ?EntityWorkflow + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return EntityWorkflow::class; + } + + private function buildQueryByDest(User $user): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('ew'); + + $qb->join('ew.steps', 'step'); + + $qb->where( + $qb->expr()->andX( + $qb->expr()->isMemberOf(':user', 'step.destUser'), + $qb->expr()->isNull('step.transitionAfter') + ) + ); + + $qb->setParameter('user', $user); + + return $qb; + } + + private function buildQueryBySubscriber(User $user): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('ew'); + + $qb->where( + $qb->expr()->orX( + $qb->expr()->isMemberOf(':user', 'ew.subscriberToStep'), + $qb->expr()->isMemberOf(':user', 'ew.subscriberToFinal'), + ) + ); + + $qb->setParameter('user', $user); + + return $qb; + } +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss index 4829f9634..39f8b3f8a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss @@ -1,5 +1,5 @@ // Access to Bootstrap variables and mixins -@import '~ChillMainAssets/module/bootstrap/shared'; +@import 'ChillMainAssets/module/bootstrap/shared'; // Chill variables @import './scss/chill_variables'; @@ -277,11 +277,17 @@ table.table-bordered { } } +/// meta-data +div.updatedBy, +div.metadata { + span.user, span.date { + text-decoration: underline dotted; + } +} div.metadata { font-size: smaller; color: $gray-600; span.user, span.date { - text-decoration: underline dotted; &:hover { color: $gray-700; } @@ -424,7 +430,63 @@ span.item-key { //text-decoration: dotted underline; } +/// Workflows +div.workflow { + section.step { + border: 1px solid $chill-l-gray; + padding: 1em 2em; + div.flex-table { + margin: 1.5em -2em; + } + } + div.to-decision, + div.decided { + font-variant: all-small-caps; + margin-left: 1em; + } + div.to-decision { + font-weight: 300; + } + div.decided { + font-weight: 600; + } + div.breadcrumb { + display: initial; + margin-bottom: 0; + padding-right: 0.5em; + background-color: tint-color($chill-yellow, 90%); + border: 1px solid $chill-yellow; + color: $primary; + border-radius: 1.5em; + font-size: 12pt; + font-weight: 500; + font-variant: small-caps; + span, a { + cursor: pointer; + text-decoration: none; + &:hover { + font-weight: 700; + } + } + } +} + +// Override bootstrap popover styles +div.popover { + box-shadow: 0 0 10px -5px $dark; + .popover-arrow {} + .popover-header {} + .popover-body {} + + // Specific worflow breadcrumb popover + &.workflow-transition { + .popover-header { + font-variant: small-caps; + } + } +} + // increase toast message z-index (above all modals) div.v-toast { z-index: 10000!important; -} \ No newline at end of file +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/buttons.scss index ad0584d74..1eb437a2a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/buttons.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/buttons.scss @@ -18,6 +18,7 @@ $chill-theme-buttons: ( "show": $chill-blue, "view": $chill-blue, "misc": $gray-300, + "download": $gray-300, "cancel": $gray-300, "choose": $gray-300, "notify": $gray-300, @@ -78,6 +79,7 @@ $chill-theme-buttons: ( &.btn-choose::before, &.btn-notify::before, &.btn-tpchild::before, + &.btn-download::before, &.btn-cancel::before { font: normal normal normal 14px/1 ForkAwesome; margin-right: 0.5em; @@ -105,6 +107,7 @@ $chill-theme-buttons: ( &.btn-unlink::before { content: "\f127"; } // fa-chain-broken &.btn-notify::before { content: "\f1d8"; } // fa-paper-plane &.btn-tpchild::before { content: "\f007"; } // fa-user + &.btn-download::before { content: "\f019"; } // fa-download } diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss index 20fe927a6..67160863d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss @@ -41,6 +41,15 @@ div.flex-table { margin-right: 5px; } } + + div.item-meta { + flex-grow: 1 !important; + flex-shrink: 1 !important; + width: unset !important; + display: flex; + flex-direction: column; + justify-content: center; + } } /* diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/notification.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/notification.scss index 93e9bd152..15f6deb3a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/notification.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/notification.scss @@ -68,6 +68,7 @@ div.notification-show { } // Override bootstrap accordion +div#workflow-fold, div#notification-fold { .accordion-button { padding: 0; @@ -78,3 +79,14 @@ div#notification-fold { } } } + +// Counter +div.notification-counter { + span { + &:not(:first-child) { + &::before { + content: '/ '; + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss index 6100bc842..37d4f97c4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/render_box.scss @@ -106,6 +106,8 @@ section.chill-entity { // used for comment-embeddable &.entity-comment-embeddable { width: 100%; + + /* already defined !! div.metadata { font-size: smaller; color: $gray-600; @@ -116,5 +118,6 @@ section.chill-entity { } } } + */ } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.js b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.js new file mode 100644 index 000000000..a89dd66f5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.js @@ -0,0 +1,12 @@ +const buildLinkCreate = function(workflowName, relatedEntityClass, relatedEntityId) { + let params = new URLSearchParams(); + params.set('entityClass', relatedEntityClass); + params.set('entityId', relatedEntityId); + params.set('workflow', workflowName); + + return `/fr/main/workflow/create?`+params.toString(); +}; + +export { + buildLinkCreate, +}; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js index 936587dec..a9d34e01d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js @@ -9,9 +9,10 @@ import Dropdown from 'bootstrap/js/src/dropdown'; import Modal from 'bootstrap/js/dist/modal'; import Collapse from 'bootstrap/js/src/collapse'; import Carousel from 'bootstrap/js/src/carousel'; +import Popover from 'bootstrap/js/src/popover'; // -// ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section +// Carousel: ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section // Initialize options, and show/hide controls in first/last slides // let ACHeaderSlider = document.querySelector('#ACHeaderSlider'); @@ -48,3 +49,14 @@ if (ACHeaderSlider) { } }) } + +// +// Popover: used in workflow breadcrumb, +// (expected in: contextual help, notification-box, workflow-box ) +// +const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); +const popoverList = triggerList.map(function (el) { + return new Popover(el, { + html: true, + }); +}); \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-pick/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-pick/index.js new file mode 100644 index 000000000..d6d657719 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-pick/index.js @@ -0,0 +1,49 @@ +import { createApp } from "vue"; +import PickWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue'; +import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue'; + +// pick workflow +document.querySelectorAll('[data-pick-workflow]') + .forEach(function(el) { + const app = { + components: { + PickWorkflowVue + }, + template: + '', + data() { + return { + relatedEntityClass: el.dataset.relatedEntityClass, + relatedEntityId: Number.parseInt(el.dataset.relatedEntityId), + workflowsAvailables: JSON.parse(el.dataset.workflowsAvailables), + } + } + }; + createApp(app).mount(el); + }) +; + +// list workflow +document.querySelectorAll('[data-list-workflows]') + .forEach(function (el) { + const app = { + components: { + ListWorkflowVue, + }, + template: + '', + data() { + return { + workflows: JSON.parse(el.dataset.workflows), + } + } + }; + createApp(app).mount(el); + }) +; \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-subscribe/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-subscribe/index.js new file mode 100644 index 000000000..c0e482aab --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/entity-workflow-subscribe/index.js @@ -0,0 +1,32 @@ +import {createApp} from "vue"; +import EntityWorkflowVueSubscriber from 'ChillMainAssets/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue'; +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'; +import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n'; + +const i18n = _createI18n(appMessages); + +let containers = document.querySelectorAll('[data-entity-workflow-subscribe]'); + +containers.forEach(container => { + let app = { + components: { + EntityWorkflowVueSubscriber, + }, + template: '', + data() { + return { + entityWorkflowId: Number.parseInt(container.dataset.entityWorkflowId), + subscriberStep: container.dataset.subscribeStep === "1", + subscriberFinal: container.dataset.subscribeFinal === "1", + } + }, + methods: { + onUpdate(status) { + this.subscriberStep = status.step; + this.subscriberFinal = status.final; + } + } + } + + createApp(app).use(i18n).mount(container); +}) diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 83a890cd9..329ac4e6c 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -31,7 +31,7 @@ window.addEventListener('DOMContentLoaded', function(e) { return { multiple: isMultiple, types: JSON.parse(el.dataset.types), - picked, + picked: picked === null ? [] : picked, uniqid: el.dataset.uniqid, } }, diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js new file mode 100644 index 000000000..f39b6c83b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js @@ -0,0 +1,29 @@ +import { createApp } from 'vue'; +import OpenWopiLink from 'ChillMainAssets/vuejs/_components/OpenWopiLink'; +import {_createI18n} from "ChillMainAssets/vuejs/_js/i18n"; + +const i18n = _createI18n({}); + +window.addEventListener('DOMContentLoaded', function (e) { + document.querySelectorAll('span[data-module="wopi-link"]') + .forEach(function (el) { + createApp({ + template: '', + components: { + OpenWopiLink + }, + data() { + return { + wopiUrl: el.dataset.wopiUrl, + title: el.dataset.docTitle, + type: el.dataset.docType, + button: el.dataset.button ? JSON.parse(el.dataset.button) : {} + } + } + }) + .use(i18n) + .mount(el) + ; + }) + ; +}); \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js b/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js new file mode 100644 index 000000000..2e2d4e89c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js @@ -0,0 +1,30 @@ +import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js'; + +window.addEventListener('DOMContentLoaded', function() { + let + finalizeAfterContainer = document.querySelector('#finalizeAfter'), + futureDestUsersContainer = document.querySelector('#futureDestUsers') + ; + + if (null === finalizeAfterContainer) { + return; + } + + new ShowHide({ + load_event: null, + froms: [finalizeAfterContainer], + container: [futureDestUsersContainer], + test: function(containers, arg2, arg3) { + for (let container of containers) { + for (let input of container.querySelectorAll('input')) { + if (!input.checked) { + return true; + } else { + return false; + } + } + } + + }, + }) +}); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue new file mode 100644 index 000000000..3e3d5405e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue new file mode 100644 index 000000000..4a3346972 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue @@ -0,0 +1,36 @@ + + + \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue new file mode 100644 index 000000000..3a39c0eff --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index b806c4b3e..1a577274b 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -15,7 +15,7 @@ - diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig index 391158a00..5750a3a2c 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig @@ -2,7 +2,7 @@ @@ -58,7 +58,7 @@
  • - {% if c.full_content is defined and c.full_content == 'true' %} + {% if c.full_content is defined and c.full_content == true %} {{ c.notification.message|chill_markdown_to_html }} {% else %} {{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }} @@ -66,7 +66,7 @@ {% endif %}
    - {% if c.action_button is not defined or c.action_button != 'false' %} + {% if c.action_button is not defined or c.action_button != false %}
    • @@ -102,7 +102,7 @@
      - {% if fold_item is defined and fold_item != 'false' %} + {% if fold_item is defined and fold_item != false %}
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig index 690f2187d..ed4a05c34 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig @@ -40,8 +40,8 @@ 'template': handler.getTemplate(notification), 'template_data': handler.getTemplateData(notification) }, - 'action_button': 'false', - 'full_content': 'true' + 'action_button': false, + 'full_content': true } %}
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig new file mode 100644 index 000000000..b825e4d43 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_attachment.html.twig @@ -0,0 +1,120 @@ +{# TODO Adapt condition #} +{% if random(1) == 0 %} + + {# For a document #} +

      {{ 'Document'|trans ~ 'target'|trans }}

      + +
      +
      + +
      +
      +

      Imprimé unique, parcours n°14635

      + Document PDF (6.2 Mo) +

      + Description du document. Sed euismod nisi porta lorem mollis aliquam. Non curabitur gravida arcu ac tortor. +

      +
      +
      + +{% else %} + + {# For an action #} +

      {{ 'Accompanying Course Action'|trans ~ 'target'|trans }}

      + +
      + {# dynamic insertion + ::: TODO delete all static insertion, remove condition and pass work object in inclusion + #}{% if dynamic is defined %} + + {% set work = '' %} + {% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with { 'w': work } %} + + {% else %} + + {# BEGIN static insertion #} +
      +
      +

      + + Exercer un AEB > Conclure l'AEB +
        +
      • Date de début : 25/11/2021
      • +
      • Date de fin : 10/03/2022
      • +
      +
      +

      +
      +
      +
      +
      +

      Référent

      +

      Fred

      +
      +
      +

      Usagers du parcours

      + +
      +
      +

      Problématique sociale

      +
      + +
      +
      +
      +
      +
      + + + + + + + + + + + +

      Objectif - motif - dispositif

      Résultats - orientations

      +

      Aucun objectif - motif - dispositif

      +
      +
        +
      • Résultat : Arrêt à l'initiative du ménage pour déménagement
      • +
      • Orientation vers une MASP
      • +
      +
      +
      +
      +
      + Dernière mise à jour par + Fred(Responsable tous les territoires)(ASE),
      + le 3 décembre 2021 à 15:19 +
      +
      +
      + {# END static insertion #} + + {% endif %} +
      + +{% endif %} + +
        +
      • + +
      • +
      • + {% set x = random(1) %} + +
      • +
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_comment.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_comment.html.twig new file mode 100644 index 000000000..ec7cf8875 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_comment.html.twig @@ -0,0 +1,13 @@ +

      {{ 'Join a comment'|trans }}

      + +{{ form_start(comment_form) }} + +{{ form_widget(comment_form.comment) }} + +
        +
      • + +
      • +
      + +{{ form_end(comment_form) }} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig new file mode 100644 index 000000000..5751449c3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig @@ -0,0 +1,48 @@ +

      {{ 'Decision'|trans }}

      + +{% if transition_form is not null %} + {{ form_start(transition_form) }} + + {{ form_row(transition_form.transition) }} + +
      + {{ form_row(transition_form.finalizeAfter) }} +
      + + {% if transition_form.freezeAfter is defined %} + {{ form_row(transition_form.freezeAfter) }} + {% endif %} + +
      + {{ form_row(transition_form.future_dest_users) }} +
      + +

      {{ form_label(transition_form.comment) }}

      + + {{ form_widget(transition_form.comment) }} + +
        +
      • + +
      • +
      + + {{ form_end(transition_form) }} +{% else %} +
      + + {% if entity_workflow.currentStep.isFinalizeAfter %} +

      {{ 'workflow.This workflow is finalized'|trans }}

      + {% else %} +

      {{ 'workflow.You are not allowed to apply a transition on this workflow'|trans }}

      +

      {{ 'workflow.Only those users are allowed'|trans }}:

      + +
        + {% for u in entity_workflow.currentStep.destUser -%} +
      • {{ u|chill_entity_render_box }}
      • + {%- endfor %} +
      + {% endif %} +
      + +{% endif %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_extension_list_workflow_for.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_extension_list_workflow_for.html.twig new file mode 100644 index 000000000..67037465b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_extension_list_workflow_for.html.twig @@ -0,0 +1,13 @@ +{% if is_granted('CHILL_MAIN_WORKFLOW_CREATE', blank_workflow) %} + {# vue component #} +
      +{% endif %} + +{% if entity_workflows|length > 0 %} + {# vue component #} +
      +{% endif %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_follow.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_follow.html.twig new file mode 100644 index 000000000..7a14e83fe --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_follow.html.twig @@ -0,0 +1,8 @@ +

      {{ 'Follow workflow'|trans }}

      + +{# vue component #} +
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig new file mode 100644 index 000000000..faa94e3a7 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig @@ -0,0 +1,59 @@ +

      {{ 'Workflow history'|trans }}

      + +
      + {% for step in entity_workflow.stepsChained %} +
      +
      + {% if loop.first and step.next is null %} +
      + {{ 'workflow.No transitions'|trans }} +
      + {% endif %} + +
      +
      + {% if not loop.first %} + + {% endif %} + {{ step.currentStep }} +
      + {# +
      + + Refusé +
      + #} +
      +
      + {% if step.next is not null %} +
      +
      + {% if step.transitionBy is not null %} +
      + {{ step.transitionBy|chill_entity_render_box }} +
      + {% endif %} +
      + {{ step.transitionAt|format_datetime('long', 'medium') }} +
      +
      +
      +
      + + {{ step.next.currentStep }} +
      +
      +
      + {% endif %} + {% if step.comment is not empty %} +
      +
      + {{ step.comment|chill_markdown_to_html }} +
      +
      + {% endif %} +
      + + {% endfor %} + +
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_notification_include.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_notification_include.html.twig new file mode 100644 index 000000000..c20f51193 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_notification_include.html.twig @@ -0,0 +1,12 @@ +
      +

      + {{ 'workflow_'|trans }} +

      + {% include handler.templateTitle(l.entity_workflow) with handler.templateTitleData(entity_workflow)|merge({ + 'description': true, + 'breadcrumb': true, + 'add_classes': 'ms-3 h3' + }) %} + + +
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig new file mode 100644 index 000000000..588e2de0b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig @@ -0,0 +1,56 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %} + {{ 'Workflow'|trans }} +{% endblock %} + +{% block js %} + {{ parent() }} + + {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_pickentity_type') }} + {{ encore_entry_script_tags('mod_entity_workflow_subscribe') }} + {{ encore_entry_script_tags('page_workflow_show') }} + {{ encore_entry_script_tags('mod_wopi_link') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_pickentity_type') }} + {{ encore_entry_link_tags('mod_entity_workflow_subscribe') }} + {{ encore_entry_link_tags('page_workflow_show') }} + {{ encore_entry_link_tags('mod_wopi_link') }} +{% endblock %} + +{% block content %} +
      +

      {{ block('title') }}

      + + {# handler_template: + - src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation.html.twig + - src/Bundle/ChillPersonBundle/Resources/views/Workflow/_accompanying_period_work.html.twig + - src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig + #} +
      +
      + {% include handler_template_title with handler_template_data|merge({'breadcrumb': true }) %} +
      + {% include handler_template with handler_template_data|merge({'display_action': true }) %} +
      + +
      {% include '@ChillMain/Workflow/_follow.html.twig' %}
      +
      {% include '@ChillMain/Workflow/_decision.html.twig' %}
      {# +
      {% include '@ChillMain/Workflow/_comment.html.twig' %}
      #} +
      {% include '@ChillMain/Workflow/_history.html.twig' %}
      + + {# useful ? + + #} +
      +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/list.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/list.html.twig new file mode 100644 index 000000000..6da68e390 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/list.html.twig @@ -0,0 +1,97 @@ +{% extends 'ChillMainBundle::layout.html.twig' %} + +{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %} + +{% block title %} + {{ 'workflow.My workflows'|trans }} +{% endblock %} + +{% block content %} +
      + +

      {{ block('title') }}

      + + + + {% if workflows|length == 0 %} +

      {{ 'workflow.No workflow'|trans }}

      + {% else %} +
      + {% for l in workflows %} +
      +
      + + {{ macro.breadcrumb(l) }} + +
      +
      + +
      + {% include l.handler.template(l.entity_workflow) with l.handler.templateData(l.entity_workflow)|merge({ + 'display_action': false + }) %} +
      +
      +
      +

      + {% if l.entity_workflow.isUserSubscribedToStep(app.user) %} + + {{ 'workflow.you subscribed to all steps'|trans }} + {% endif %} +

      +

      + {% if l.entity_workflow.isUserSubscribedToFinal(app.user) %} + + {{ 'workflow.you subscribed to final step'|trans }} + {% endif %} +

      +
      + +
      + +
      +
      + {% endfor %} +
      + {% endif %} +
      +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig new file mode 100644 index 000000000..436394012 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig @@ -0,0 +1,45 @@ +{% macro popoverContent(step) %} +
        +
      • + {{ 'By'|trans ~ ' : ' }} + {{ step.transitionBy|chill_entity_render_box }} +
      • +
      • + {{ 'Le'|trans ~ ' : ' }} + {{ step.transitionAt|format_datetime('short', 'short') }} +
      • +
      +{% endmacro %} + +{% macro breadcrumb(_ctx) %} + +{% endmacro %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig new file mode 100644 index 000000000..1e8469968 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig @@ -0,0 +1,13 @@ +{{ dest.label }}, + +Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ workflow.text }} +{%- if is_dest %} + +Vous êtes invités à valider cette étape au plus tôt. +{% endif %} + +Vous pouvez visualiser le workflow sur cette page: + +{{ absolute_url(path('chill_main_workflow_show', {'id': entity_workflow.id})) }} + +Cordialement, diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig new file mode 100644 index 000000000..4960b6138 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig @@ -0,0 +1,5 @@ +{%- if is_dest -%} +Un suivi {{ workflow.text }} demande votre attention +{%- else -%} +Un suivi {{ workflow.text }} a atteint une nouvelle étape: {{ place.text }} +{%- endif -%} diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php index ae2eb1f2c..9b6d89e83 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php @@ -69,6 +69,15 @@ class UserMenuBuilder implements LocalMenuBuilderInterface 'counter' => $nbNotifications, ]); + $menu + ->addChild( + $this->translator->trans('workflow.My workflows'), + ['route' => 'chill_main_workflow_list_dest'] + ) + ->setExtras([ + 'order' => 700, + ]); + $menu ->addChild( 'Change password', diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php new file mode 100644 index 000000000..9542d3acb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php @@ -0,0 +1,70 @@ +manager = $manager; + $this->security = $security; + } + + protected function supports($attribute, $subject) + { + return $subject instanceof EntityWorkflow && in_array($attribute, self::getRoles(), true); + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + switch ($attribute) { + case self::CREATE: + case self::SEE: + $handler = $this->manager->getHandler($subject); + + $entityAttribute = $handler->getRoleShow($subject); + + if (null === $entityAttribute) { + return true; + } + + return $this->security->isGranted($entityAttribute, $handler->getRelatedEntity($subject)); + + default: + throw new UnexpectedValueException("attribute {$attribute} not supported"); + } + } + + private static function getRoles(): array + { + return [ + self::SEE, + self::CREATE, + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php new file mode 100644 index 000000000..c89101c5b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php @@ -0,0 +1,81 @@ +getCurrentStep()->setFinalizeAfter(true); + $entityWorkflow->setStep('final'); + + $this->assertTrue($entityWorkflow->isFinalize()); + } + + public function testIsFinalizeWith4Steps() + { + $entityWorkflow = new EntityWorkflow(); + + $this->assertFalse($entityWorkflow->isFinalize()); + + $entityWorkflow->setStep('two'); + + $this->assertFalse($entityWorkflow->isFinalize()); + + $entityWorkflow->setStep('previous_final'); + + $this->assertFalse($entityWorkflow->isFinalize()); + + $entityWorkflow->getCurrentStep()->setFinalizeAfter(true); + $entityWorkflow->setStep('final'); + + $this->assertTrue($entityWorkflow->isFinalize()); + } + + public function testIsFreeze() + { + $entityWorkflow = new EntityWorkflow(); + + $this->assertFalse($entityWorkflow->isFreeze()); + + $entityWorkflow->setStep('step_one'); + + $this->assertFalse($entityWorkflow->isFreeze()); + + $entityWorkflow->setStep('step_three'); + + $this->assertFalse($entityWorkflow->isFreeze()); + + $entityWorkflow->getCurrentStep()->setFreezeAfter(true); + + $this->assertFalse($entityWorkflow->isFreeze()); + + $entityWorkflow->setStep('freezed'); + + $this->assertTrue($entityWorkflow->isFreeze()); + + $entityWorkflow->setStep('after_freeze'); + + $this->assertTrue($entityWorkflow->isFreeze()); + + $this->assertTrue($entityWorkflow->getCurrentStep()->isFreezeAfter()); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php new file mode 100644 index 000000000..2e97f77af --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php @@ -0,0 +1,39 @@ +handlers = $handlers; + $this->registry = $registry; + } + + public function getHandler(EntityWorkflow $entityWorkflow, array $options = []): EntityWorkflowHandlerInterface + { + foreach ($this->handlers as $handler) { + if ($handler->supports($entityWorkflow, $options)) { + return $handler; + } + } + + throw new HandlerNotFoundException(); + } + + public function getSupportedWorkflows(EntityWorkflow $entityWorkflow): array + { + return $this->registry->all($entityWorkflow); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php new file mode 100644 index 000000000..fba36924a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php @@ -0,0 +1,114 @@ +chillLogger = $chillLogger; + $this->security = $security; + $this->userRender = $userRender; + } + + public static function getSubscribedEvents(): array + { + return [ + 'workflow.transition' => 'onTransition', + 'workflow.guard' => [ + ['guardEntityWorkflow', 0], + ], + ]; + } + + public function guardEntityWorkflow(GuardEvent $event) + { + if (!$event->getSubject() instanceof EntityWorkflow) { + return; + } + + /** @var EntityWorkflow $entityWorkflow */ + $entityWorkflow = $event->getSubject(); + + if ($entityWorkflow->isFinalize()) { + $event->addTransitionBlocker( + new TransitionBlocker( + 'workflow.The workflow is finalized', + 'd6306280-7535-11ec-a40d-1f7bee26e2c0' + ) + ); + + return; + } + + if (!$entityWorkflow->getCurrentStep()->getDestUser()->contains($this->security->getUser())) { + if (!$event->getMarking()->has('initial')) { + $event->addTransitionBlocker(new TransitionBlocker( + 'workflow.You are not allowed to apply a transition on this workflow. Only those users are allowed: %users%', + 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc', + [ + '%users%' => implode( + ', ', + $entityWorkflow->getCurrentStep()->getDestUser()->map(function (User $u) { + return $this->userRender->renderString($u, []); + })->toArray() + ), + ] + )); + } + } + } + + public function onTransition(Event $event) + { + if (!$event->getSubject() instanceof EntityWorkflow) { + return; + } + + /** @var EntityWorkflow $entityWorkflow */ + $entityWorkflow = $event->getSubject(); + $step = $entityWorkflow->getCurrentStep(); + + $step + ->setTransitionAfter($event->getTransition()->getName()) + ->setTransitionAt(new DateTimeImmutable('now')) + ->setTransitionBy($this->security->getUser()); + + $this->chillLogger->info('[workflow] apply transition on entityWorkflow', [ + 'relatedEntityClass' => $entityWorkflow->getRelatedEntityClass(), + 'relatedEntityId' => $entityWorkflow->getRelatedEntityId(), + 'transition' => $event->getTransition()->getName(), + 'by_user' => $this->security->getUser(), + 'entityWorkflow' => $entityWorkflow->getId(), + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php new file mode 100644 index 000000000..fd251bca6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php @@ -0,0 +1,103 @@ +entityManager = $entityManager; + $this->engine = $engine; + $this->metadataExtractor = $metadataExtractor; + $this->registry = $registry; + $this->security = $security; + } + + public static function getSubscribedEvents(): array + { + return [ + 'workflow.completed' => 'onCompleted', + ]; + } + + public function onCompleted(Event $event): void + { + if (!$event->getSubject() instanceof EntityWorkflow) { + return; + } + + /** @var EntityWorkflow $entityWorkflow */ + $entityWorkflow = $event->getSubject(); + + $dests = array_merge( + $entityWorkflow->getSubscriberToStep()->toArray(), + $entityWorkflow->isFinalize() ? $entityWorkflow->getSubscriberToFinal()->toArray() : [], + $entityWorkflow->getCurrentStep()->getDestUser()->toArray() + ); + + $place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow); + $workflow = $this->metadataExtractor->buildArrayPresentationForWorkflow( + $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()) + ); + + $visited = []; + + foreach ($dests as $subscriber) { + if ( + $this->security->getUser() === $subscriber + || in_array($subscriber->getId(), $visited, true) + ) { + continue; + } + + $context = [ + 'entity_workflow' => $entityWorkflow, + 'dest' => $subscriber, + 'place' => $place, + 'workflow' => $workflow, + 'is_dest' => $entityWorkflow->getCurrentStep()->getDestUser()->contains($subscriber), + ]; + + $notification = new Notification(); + $notification + ->setRelatedEntityId($entityWorkflow->getId()) + ->setRelatedEntityClass(EntityWorkflow::class) + ->setTitle($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig', $context)) + ->setMessage($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig', $context)) + ->addAddressee($subscriber); + $this->entityManager->persist($notification); + + $visited[] = $subscriber->getId(); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Exception/HandlerNotFoundException.php b/src/Bundle/ChillMainBundle/Workflow/Exception/HandlerNotFoundException.php new file mode 100644 index 000000000..99ad5b88c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Exception/HandlerNotFoundException.php @@ -0,0 +1,18 @@ +registry = $registry; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function availableWorkflowFor(string $relatedEntityClass, ?int $relatedEntityId = 0): array + { + $blankEntityWorkflow = new EntityWorkflow(); + $blankEntityWorkflow + ->setRelatedEntityId($relatedEntityId) + ->setRelatedEntityClass($relatedEntityClass); + + // build the list of available workflows, and extract their names from metadata + $workflows = $this->registry->all($blankEntityWorkflow); + $workflowsList = []; + + foreach ($workflows as $workflow) { + $metadata = $workflow->getMetadataStore()->getWorkflowMetadata(); + $text = array_key_exists('label', $metadata) ? + $this->translatableStringHelper->localize($metadata['label']) : $workflow->getName(); + + $workflowsList[] = ['name' => $workflow->getName(), 'text' => $text]; + } + + return $workflowsList; + } + + public function buildArrayPresentationForPlace(EntityWorkflow $entityWorkflow): array + { + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + $markingMetadata = $workflow->getMetadataStore()->getPlaceMetadata($entityWorkflow->getCurrentStep()->getCurrentStep()); + + $text = array_key_exists('label', $markingMetadata) ? + $this->translatableStringHelper->localize($markingMetadata['label']) : $entityWorkflow->getCurrentStep()->getCurrentStep(); + + return ['name' => $entityWorkflow->getCurrentStep()->getCurrentStep(), 'text' => $text]; + } + + public function buildArrayPresentationForWorkflow(WorkflowInterface $workflow): array + { + $metadata = $workflow->getMetadataStore()->getWorkflowMetadata(); + $text = array_key_exists('label', $metadata) ? + $this->translatableStringHelper->localize($metadata['label']) : $workflow->getName(); + + return ['name' => $workflow->getName(), 'text' => $text]; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php b/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php new file mode 100644 index 000000000..756d79fbf --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php @@ -0,0 +1,45 @@ +entityWorkflowRepository->find($notification->getRelatedEntityId()); + + return [ + 'entity_workflow' => $entityWorkflow, + 'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow), + ]; + } + + public function supports(Notification $notification, array $options = []): bool + { + return $notification->getRelatedEntityClass() === EntityWorkflow::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/RelatedEntityWorkflowSupportsStrategy.php b/src/Bundle/ChillMainBundle/Workflow/RelatedEntityWorkflowSupportsStrategy.php new file mode 100644 index 000000000..310c0126b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/RelatedEntityWorkflowSupportsStrategy.php @@ -0,0 +1,35 @@ +getMetadataStore()->getWorkflowMetadata()['related_entity'] + as $relatedEntityClass) { + if ($subject->getRelatedEntityClass() === $relatedEntityClass) { + return true; + } + } + + return false; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtension.php b/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtension.php new file mode 100644 index 000000000..09dce0c6e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtension.php @@ -0,0 +1,29 @@ + true, 'is_safe' => ['html']] + ), + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php b/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php new file mode 100644 index 000000000..0f034267f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php @@ -0,0 +1,79 @@ +entityWorkflowManager = $entityWorkflowManager; + $this->registry = $registry; + $this->repository = $repository; + $this->metadataExtractor = $metadataExtractor; + $this->normalizer = $normalizer; + } + + public function listWorkflows(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string + { + $blankEntityWorkflow = new EntityWorkflow(); + $blankEntityWorkflow + ->setRelatedEntityId($relatedEntityId) + ->setRelatedEntityClass($relatedEntityClass); + + $workflowsList = $this->metadataExtractor->availableWorkflowFor($relatedEntityClass, $relatedEntityId); + + // get the related entity already created + $entityWorkflows = []; + + foreach ($entityWorkflowsNaked = $this->repository->findBy( + ['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId] + ) as $entityWorkflow) { + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $entityWorkflows[] = [ + 'entity_workflow' => $entityWorkflow, + 'workflow' => $this->metadataExtractor->buildArrayPresentationForWorkflow($workflow), + 'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow), + ]; + } + + return $environment->render('@ChillMain/Workflow/_extension_list_workflow_for.html.twig', [ + 'entity_workflows_json' => $this->normalizer->normalize($entityWorkflowsNaked, 'json', ['groups' => 'read']), + 'entity_workflows' => $entityWorkflows, + 'blank_workflow' => $blankEntityWorkflow, + 'workflows_availables' => $workflowsList, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreation.php b/src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreation.php new file mode 100644 index 000000000..dc01f9019 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Validator/EntityWorkflowCreation.php @@ -0,0 +1,35 @@ +entityWorkflowManager = $entityWorkflowManager; + } + + /** + * @param EntityWorkflow $value + * @param Constraint|EntityWorkflowCreation $constraint + */ + public function validate($value, Constraint $constraint) + { + if (!$value instanceof EntityWorkflow) { + throw new UnexpectedValueException($value, EntityWorkflow::class); + } + + if (!$constraint instanceof EntityWorkflowCreation) { + throw new UnexpectedTypeException($constraint, EntityWorkflowCreation::class); + } + + try { + $handler = $this->entityWorkflowManager->getHandler($value); + } catch (HandlerNotFoundException $e) { + $this->context->buildViolation($constraint->messageHandlerNotFound) + ->addViolation(); + + return; + } + + if (null === $handler->getRelatedEntity($value)) { + $this->context->buildViolation($constraint->messageEntityNotFound) + ->addViolation(); + } + + $workflows = $this->entityWorkflowManager->getSupportedWorkflows($value); + + $matched = array_filter($workflows, static function (WorkflowInterface $workflow) use ($value) { + return $workflow->getName() === $value->getWorkflowName(); + }); + + if (0 === count($matched)) { + $this->context->buildViolation($constraint->messageWorkflowNotAvailable) + ->addViolation(); + } + } +} diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 47aa32865..113d326c1 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -52,6 +52,7 @@ module.exports = function(encore, entries) // Page entrypoints encore.addEntry('page_login', __dirname + '/Resources/public/page/login/index.js'); encore.addEntry('page_location', __dirname + '/Resources/public/page/location/index.js'); + encore.addEntry('page_workflow_show', __dirname + '/Resources/public/page/workflow-show/index.js'); buildCKEditor(encore); @@ -64,6 +65,9 @@ module.exports = function(encore, entries) encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js'); encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js'); encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js'); + encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js'); + encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js'); + encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js'); // Vue entrypoints encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js'); diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index adf38988e..dd2407d2b 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -26,6 +26,20 @@ services: tags: - { name: 'doctrine.event_subscriber' } + # workflow related + Chill\MainBundle\Workflow\: + resource: '../Workflow/' + autowire: true + autoconfigure: true + + Chill\MainBundle\Workflow\EntityWorkflowManager: + autoconfigure: true + autowire: true + arguments: + $handlers: !tagged_iterator chill_main.workflow_handler + + # other stuffes + chill.main.helper.translatable_string: class: Chill\MainBundle\Templating\TranslatableStringHelper diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index 0d0a17201..7efc9a69f 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -141,3 +141,5 @@ services: autoconfigure: true Chill\MainBundle\Form\Type\LocationFormType: ~ + + Chill\MainBundle\Form\WorkflowStepType: ~ diff --git a/src/Bundle/ChillMainBundle/config/services/security.yaml b/src/Bundle/ChillMainBundle/config/services/security.yaml index 30eed05c0..b884a92fc 100644 --- a/src/Bundle/ChillMainBundle/config/services/security.yaml +++ b/src/Bundle/ChillMainBundle/config/services/security.yaml @@ -26,6 +26,8 @@ services: Chill\MainBundle\Security\Authorization\NotificationVoter: ~ + Chill\MainBundle\Security\Authorization\EntityWorkflowVoter: ~ + Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory' chill.main.security.authorization.helper: diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220112123436.php b/src/Bundle/ChillMainBundle/migrations/Version20220112123436.php new file mode 100644 index 000000000..7358a3b4b --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220112123436.php @@ -0,0 +1,72 @@ +addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_final DROP CONSTRAINT FK_C2CE504C7D99CE94'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_step DROP CONSTRAINT FK_ECB8F5417D99CE94'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP CONSTRAINT FK_440AA6FEFB054143'); + $this->addSql('ALTER TABLE chill_main_entity_workflow_step_user DROP CONSTRAINT FK_A9F001FA7E6AF9D4'); + $this->addSql('DROP SEQUENCE chill_main_workflow_entity_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_main_workflow_entity_step_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_workflow_entity'); + $this->addSql('DROP TABLE chill_main_workflow_entity_subscriber_to_final'); + $this->addSql('DROP TABLE chill_main_workflow_entity_subscriber_to_step'); + $this->addSql('DROP TABLE chill_main_workflow_entity_step'); + $this->addSql('DROP TABLE chill_main_entity_workflow_step_user'); + } + + public function getDescription(): string + { + return 'Create tables for workflow'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE SEQUENCE chill_main_workflow_entity_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE chill_main_workflow_entity_step_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_main_workflow_entity (id INT NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, relatedEntityClass VARCHAR(255) NOT NULL, relatedEntityId INT NOT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, workflowName TEXT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_5F087D553174800F ON chill_main_workflow_entity (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_5F087D5565FF1AEC ON chill_main_workflow_entity (updatedBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_workflow_entity.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_main_workflow_entity.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE chill_main_workflow_entity_subscriber_to_final (entityworkflow_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflow_id, user_id))'); + $this->addSql('CREATE INDEX IDX_C2CE504C7D99CE94 ON chill_main_workflow_entity_subscriber_to_final (entityworkflow_id)'); + $this->addSql('CREATE INDEX IDX_C2CE504CA76ED395 ON chill_main_workflow_entity_subscriber_to_final (user_id)'); + $this->addSql('CREATE TABLE chill_main_workflow_entity_subscriber_to_step (entityworkflow_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflow_id, user_id))'); + $this->addSql('CREATE INDEX IDX_ECB8F5417D99CE94 ON chill_main_workflow_entity_subscriber_to_step (entityworkflow_id)'); + $this->addSql('CREATE INDEX IDX_ECB8F541A76ED395 ON chill_main_workflow_entity_subscriber_to_step (user_id)'); + $this->addSql('CREATE TABLE chill_main_workflow_entity_step (id INT NOT NULL, currentStep TEXT NOT NULL, destEmail JSON NOT NULL, finalizeAfter BOOLEAN DEFAULT \'false\' NOT NULL, freezeAfter BOOLEAN DEFAULT \'false\' NOT NULL, transitionAfter TEXT DEFAULT NULL, transitionByEmail TEXT DEFAULT NULL, transitionAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, entityWorkflow_id INT DEFAULT NULL, transitionBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_440AA6FEFB054143 ON chill_main_workflow_entity_step (entityWorkflow_id)'); + $this->addSql('CREATE INDEX IDX_440AA6FE8829EF37 ON chill_main_workflow_entity_step (transitionBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_step.transitionAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE chill_main_entity_workflow_step_user (entityworkflowstep_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflowstep_id, user_id))'); + $this->addSql('CREATE INDEX IDX_A9F001FA7E6AF9D4 ON chill_main_entity_workflow_step_user (entityworkflowstep_id)'); + $this->addSql('CREATE INDEX IDX_A9F001FAA76ED395 ON chill_main_entity_workflow_step_user (user_id)'); + $this->addSql('ALTER TABLE chill_main_workflow_entity ADD CONSTRAINT FK_5F087D553174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity ADD CONSTRAINT FK_5F087D5565FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_final ADD CONSTRAINT FK_C2CE504C7D99CE94 FOREIGN KEY (entityworkflow_id) REFERENCES chill_main_workflow_entity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_final ADD CONSTRAINT FK_C2CE504CA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_step ADD CONSTRAINT FK_ECB8F5417D99CE94 FOREIGN KEY (entityworkflow_id) REFERENCES chill_main_workflow_entity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_step ADD CONSTRAINT FK_ECB8F541A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD CONSTRAINT FK_440AA6FEFB054143 FOREIGN KEY (entityWorkflow_id) REFERENCES chill_main_workflow_entity (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD CONSTRAINT FK_440AA6FE8829EF37 FOREIGN KEY (transitionBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_entity_workflow_step_user ADD CONSTRAINT FK_A9F001FA7E6AF9D4 FOREIGN KEY (entityworkflowstep_id) REFERENCES chill_main_workflow_entity_step (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_entity_workflow_step_user ADD CONSTRAINT FK_A9F001FAA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220114132105.php b/src/Bundle/ChillMainBundle/migrations/Version20220114132105.php new file mode 100644 index 000000000..2e7d7a99c --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220114132105.php @@ -0,0 +1,47 @@ +addSql('DROP SEQUENCE chill_main_workflow_entity_comment_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_workflow_entity_comment'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP comment'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step_user RENAME TO chill_main_entity_workflow_step_user'); + } + + public function getDescription(): string + { + return 'Add comment to entity workflow'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE SEQUENCE chill_main_workflow_entity_comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE TABLE chill_main_workflow_entity_comment (id INT NOT NULL, comment TEXT NOT NULL DEFAULT \'\', createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdBy_id INT DEFAULT NULL, entityWorkflow_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_2655252F3174800F ON chill_main_workflow_entity_comment (createdBy_id)'); + $this->addSql('CREATE INDEX IDX_2655252FFB054143 ON chill_main_workflow_entity_comment (entityWorkflow_id)'); + $this->addSql('CREATE INDEX IDX_2655252F65FF1AEC ON chill_main_workflow_entity_comment (updatedBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_comment.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_comment.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_main_workflow_entity_comment ADD CONSTRAINT FK_2655252F3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_comment ADD CONSTRAINT FK_2655252FFB054143 FOREIGN KEY (entityWorkflow_id) REFERENCES chill_main_workflow_entity (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_comment ADD CONSTRAINT FK_2655252F65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD comment TEXT NOT NULL DEFAULT \'\''); + $this->addSql('ALTER TABLE chill_main_entity_workflow_step_user RENAME TO chill_main_workflow_entity_step_user'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220114165950.php b/src/Bundle/ChillMainBundle/migrations/Version20220114165950.php new file mode 100644 index 000000000..5489d3f1c --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220114165950.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE chill_main_workflow_entity_step ALTER transitionat SET NOT NULL'); + } + + public function getDescription(): string + { + return 'remove not null on transition at'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER transitionat DROP NOT NULL'); + $this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER transitionat SET DEFAULT NULL'); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 434909d9f..5c214367c 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -36,8 +36,9 @@ Choose an user: Choisir un utilisateur "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?": "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?" No value: Aucune information Last updated by: Dernière mise à jour par -Last updated on: Dernière mise à jour le on: "le " +Last updated on: Dernière mise à jour le +by_user: "par " Edit: Modifier Update: Mettre à jour @@ -354,6 +355,43 @@ For: Pour Created for: Créé pour Created by: Créé par + +# Workflows 💊 +Workflow: Workflow — chemin de décision +Workflow n°%id%: 'Workflow (n°%id%)' +workflow_: Workflow +target: ' (cible)' +Decision: Décision +Join a comment: Laisser un commentaire +Follow workflow: Suivre la décision +Workflow history: Historique de la décision + +workflow: + Created by: Créé par + Transition: Prochaine étape + dest for next steps: Utilisateurs qui valideront la prochaine étape + Freeze: Geler + The associated element will be freezed: L'élément associé sera gelé et ne pourra plus être modifié après cette décision. + Finalize: Étape finale + The workflow will be finalized: Le suivi est clôturé lors de cette décision. + No transitions: Aucune transition + Comment added: Commentaire ajouté + This workflow is finalized: Ce suivi est finalisé. + You are not allowed to apply a transition on this workflow: Vous n'êtes pas autorisé à appliquer une décision pour ce suivi + Only those users are allowed: Seuls ces utilisateurs sont autorisés + My workflows: Mes workflows + No workflow: Aucun workflow + Evaluation (n°%eval%): "Évaluation (n°%eval%)" + Document (n°%doc%): "Document (n°%doc%)" + Work (n°%w%): "Action d'accompagnement (n°%w%)" + subscribed: Souscrit + dest: Destinataire de l'étape finale + you subscribed to all steps: Vous recevrez une notification à chaque étape + you subscribed to final step: Vous recevrez une notification à l'étape finale + +Subscribe final: Recevoir une notification à l'étape finale +Subscribe all steps: Recevoir une notification à chaque étape + notification: Notification: Notification My own notifications: Mes notifications diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php new file mode 100644 index 000000000..06634b3cc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php @@ -0,0 +1,61 @@ +repository = $entityManager->getRepository(AccompanyingPeriodWorkEvaluation::class); + } + + public function find($id): ?AccompanyingPeriodWorkEvaluation + { + return $this->repository->find($id); + } + + /** + * @return array|AccompanyingPeriodWorkEvaluation[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @param null|mixed $limit + * @param null|mixed $offset + * + * @return array|AccompanyingPeriodWorkEvaluation[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?AccompanyingPeriodWorkEvaluation + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return AccompanyingPeriodWorkEvaluation::class; + } +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index b5e5d3221..e43608895 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -241,7 +241,17 @@
    -
      +
        +