diff --git a/CHANGELOG.md b/CHANGELOG.md index 412ea41f..9212ccd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-281](https://github.com/itk-dev/economics/pull/281) + Reworked index page columns and added project lead filter. + Added createdAt/updatedAt as read-only text on edit page. + Moved cybersecurity price to cybersecurity agreement entity. * [PR-280](https://github.com/itk-dev/economics/pull/280) Extended service agreement overview with new and updated fields. Tweaked form styling. diff --git a/migrations/Version20260410104113.php b/migrations/Version20260410131200.php similarity index 65% rename from migrations/Version20260410104113.php rename to migrations/Version20260410131200.php index 20b0c919..bb0e401d 100644 --- a/migrations/Version20260410104113.php +++ b/migrations/Version20260410131200.php @@ -10,7 +10,7 @@ /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20260410104113 extends AbstractMigration +final class Version20260410131200 extends AbstractMigration { public function getDescription(): string { @@ -20,12 +20,14 @@ public function getDescription(): string public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE service_agreement ADD system_owner_notices JSON NOT NULL COMMENT \'(DC2Type:json)\', ADD is_eol TINYINT(1) NOT NULL, ADD leantime_url VARCHAR(255) DEFAULT NULL, ADD client_contact_name VARCHAR(255) DEFAULT NULL, ADD client_contact_email VARCHAR(255) DEFAULT NULL, ADD dedicated_server TINYINT(1) NOT NULL, ADD server_size VARCHAR(255) DEFAULT NULL, ADD cybersecurity_price DOUBLE PRECISION DEFAULT NULL, ADD git_repos LONGTEXT DEFAULT NULL, ADD created_by VARCHAR(255) DEFAULT NULL, ADD updated_by VARCHAR(255) DEFAULT NULL, ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL, DROP system_owner_notice, CHANGE valid_to valid_to DATETIME DEFAULT NULL'); + $this->addSql('ALTER TABLE cybersecurity_agreement ADD price DOUBLE PRECISION DEFAULT NULL'); + $this->addSql('ALTER TABLE service_agreement ADD system_owner_notices JSON NOT NULL COMMENT \'(DC2Type:json)\', ADD is_eol TINYINT(1) NOT NULL, ADD leantime_url VARCHAR(255) DEFAULT NULL, ADD client_contact_name VARCHAR(255) DEFAULT NULL, ADD client_contact_email VARCHAR(255) DEFAULT NULL, ADD dedicated_server TINYINT(1) NOT NULL, ADD server_size VARCHAR(255) DEFAULT NULL, ADD git_repos LONGTEXT DEFAULT NULL, ADD created_by VARCHAR(255) DEFAULT NULL, ADD updated_by VARCHAR(255) DEFAULT NULL, ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL, DROP system_owner_notice, CHANGE valid_to valid_to DATETIME DEFAULT NULL'); } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE service_agreement ADD system_owner_notice VARCHAR(255) NOT NULL, DROP system_owner_notices, DROP is_eol, DROP leantime_url, DROP client_contact_name, DROP client_contact_email, DROP dedicated_server, DROP server_size, DROP cybersecurity_price, DROP git_repos, DROP created_by, DROP updated_by, DROP created_at, DROP updated_at, CHANGE valid_to valid_to DATETIME NOT NULL'); + $this->addSql('ALTER TABLE cybersecurity_agreement DROP price'); + $this->addSql('ALTER TABLE service_agreement ADD system_owner_notice VARCHAR(255) NOT NULL, DROP system_owner_notices, DROP is_eol, DROP leantime_url, DROP client_contact_name, DROP client_contact_email, DROP dedicated_server, DROP server_size, DROP git_repos, DROP created_by, DROP updated_by, DROP created_at, DROP updated_at, CHANGE valid_to valid_to DATETIME NOT NULL'); } } diff --git a/src/Controller/ServiceAgreementController.php b/src/Controller/ServiceAgreementController.php index 232658dd..894ff6f6 100644 --- a/src/Controller/ServiceAgreementController.php +++ b/src/Controller/ServiceAgreementController.php @@ -28,6 +28,7 @@ final class ServiceAgreementController extends AbstractController public function index(Request $request, ServiceAgreementRepository $serviceAgreementRepository, CybersecurityAgreementRepository $cybersecurityAgreementRepository): Response { $serviceAgreementFilterData = new ServiceAgreementFilterData(); + $serviceAgreementFilterData->active = true; $form = $this->createForm(ServiceAgreementFilterType::class, $serviceAgreementFilterData); $form->handleRequest($request); diff --git a/src/Entity/CybersecurityAgreement.php b/src/Entity/CybersecurityAgreement.php index ec20ef27..34a9d473 100644 --- a/src/Entity/CybersecurityAgreement.php +++ b/src/Entity/CybersecurityAgreement.php @@ -20,6 +20,9 @@ class CybersecurityAgreement #[ORM\Column(nullable: true)] private ?float $quarterlyHours = null; + #[ORM\Column(nullable: true)] + private ?float $price = null; + #[ORM\Column(length: 255, nullable: true)] private ?string $note = null; @@ -52,6 +55,18 @@ public function setQuarterlyHours(float $quarterlyHours): static return $this; } + public function getPrice(): ?float + { + return $this->price; + } + + public function setPrice(?float $price): static + { + $this->price = $price; + + return $this; + } + public function getNote(): ?string { return $this->note; diff --git a/src/Entity/ServiceAgreement.php b/src/Entity/ServiceAgreement.php index eb5a6d27..f2834f67 100644 --- a/src/Entity/ServiceAgreement.php +++ b/src/Entity/ServiceAgreement.php @@ -69,9 +69,6 @@ class ServiceAgreement extends AbstractBaseEntity #[ORM\Column(enumType: ServerSizeEnum::class, nullable: true)] private ?ServerSizeEnum $serverSize = null; - #[ORM\Column(nullable: true)] - private ?float $cybersecurityPrice = null; - #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $gitRepos = null; @@ -291,18 +288,6 @@ public function setServerSize(?ServerSizeEnum $serverSize): static return $this; } - public function getCybersecurityPrice(): ?float - { - return $this->cybersecurityPrice; - } - - public function setCybersecurityPrice(?float $cybersecurityPrice): static - { - $this->cybersecurityPrice = $cybersecurityPrice; - - return $this; - } - public function getGitRepos(): ?string { return $this->gitRepos; diff --git a/src/Form/CybersecurityAgreementType.php b/src/Form/CybersecurityAgreementType.php index d66b37ff..b611f9d0 100644 --- a/src/Form/CybersecurityAgreementType.php +++ b/src/Form/CybersecurityAgreementType.php @@ -23,6 +23,15 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'row_attr' => ['class' => 'form-row'], 'required' => false, ]) + ->add('price', NumberType::class, [ + 'label' => 'service_agreement.cybersecurity_price', + 'label_attr' => ['class' => 'label'], + 'attr' => ['class' => 'form-element'], + 'help_attr' => ['class' => 'form-help'], + 'row_attr' => ['class' => 'form-row'], + 'html5' => true, + 'required' => false, + ]) ->add('note', TextareaType::class, [ 'label' => 'service_agreement.note', 'label_attr' => ['class' => 'label'], diff --git a/src/Form/ServiceAgreementFilterType.php b/src/Form/ServiceAgreementFilterType.php index b00c45bc..fa88e66e 100644 --- a/src/Form/ServiceAgreementFilterType.php +++ b/src/Form/ServiceAgreementFilterType.php @@ -2,8 +2,11 @@ namespace App\Form; +use App\Entity\Worker; use App\Enum\HostingProviderEnum; use App\Model\Invoices\ServiceAgreementFilterData; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\SearchType; @@ -56,6 +59,18 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'service_agreement.no' => false, ], 'attr' => ['class' => 'form-element'], + ]) + ->add('projectLead', EntityType::class, [ + 'required' => false, + 'class' => Worker::class, + 'label' => 'service_agreement.project_lead', + 'label_attr' => ['class' => 'label'], + 'attr' => ['class' => 'form-element'], + 'placeholder' => '', + 'query_builder' => fn (EntityRepository $er) => $er->createQueryBuilder('w') + ->innerJoin('App\Entity\ServiceAgreement', 'sa', 'WITH', 'sa.projectLead = w') + ->groupBy('w.id') + ->orderBy('w.name', 'ASC'), ]); } diff --git a/src/Form/ServiceAgreementType.php b/src/Form/ServiceAgreementType.php index f0ccfda5..1ef8b1bd 100644 --- a/src/Form/ServiceAgreementType.php +++ b/src/Form/ServiceAgreementType.php @@ -178,15 +178,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'help_attr' => ['class' => 'form-help'], 'row_attr' => ['class' => 'form-row'], ]) - ->add('cybersecurityPrice', NumberType::class, [ - 'label' => 'service_agreement.cybersecurity_price', - 'label_attr' => ['class' => 'label'], - 'attr' => ['class' => 'form-element'], - 'help_attr' => ['class' => 'form-help'], - 'row_attr' => ['class' => 'form-row'], - 'html5' => true, - 'required' => false, - ]) ->add('gitRepos', TextareaType::class, [ 'label' => 'service_agreement.git_repos', 'label_attr' => ['class' => 'label'], diff --git a/src/Model/Invoices/ServiceAgreementFilterData.php b/src/Model/Invoices/ServiceAgreementFilterData.php index 1d47132f..ec70554f 100644 --- a/src/Model/Invoices/ServiceAgreementFilterData.php +++ b/src/Model/Invoices/ServiceAgreementFilterData.php @@ -2,6 +2,7 @@ namespace App\Model\Invoices; +use App\Entity\Worker; use App\Enum\HostingProviderEnum; class ServiceAgreementFilterData @@ -11,4 +12,5 @@ class ServiceAgreementFilterData public ?bool $cybersecurityAgreement = null; public ?HostingProviderEnum $hostingProvider = null; public ?bool $active = null; + public ?Worker $projectLead = null; } diff --git a/src/Repository/ServiceAgreementRepository.php b/src/Repository/ServiceAgreementRepository.php index 645e7937..28e8ed46 100644 --- a/src/Repository/ServiceAgreementRepository.php +++ b/src/Repository/ServiceAgreementRepository.php @@ -59,6 +59,11 @@ public function getFilteredPagination(ServiceAgreementFilterData $serviceAgreeme ->setParameter('active', $serviceAgreementFilterData->active); } + if (!is_null($serviceAgreementFilterData->projectLead)) { + $qb->andWhere('service_agreement.projectLead = :projectLead') + ->setParameter('projectLead', $serviceAgreementFilterData->projectLead); + } + return $this->paginator->paginate( $qb, $page, diff --git a/templates/service_agreement/edit.html.twig b/templates/service_agreement/edit.html.twig index ea7d1eeb..5bf617ba 100644 --- a/templates/service_agreement/edit.html.twig +++ b/templates/service_agreement/edit.html.twig @@ -5,8 +5,13 @@ {% block content %}

{{ 'service_agreement.edit'|trans }}

+
+

{{ 'service_agreement.created_at'|trans }}: {{ service_agreement.createdAt ? service_agreement.createdAt|date('d/m Y H:i') : '-' }}

+

{{ 'service_agreement.updated_at'|trans }}: {{ service_agreement.updatedAt ? service_agreement.updatedAt|date('d/m Y H:i') : '-' }}

+
+ {{ form_start(form) }} -
+
{{ form_rest(form) }}
diff --git a/templates/service_agreement/index.html.twig b/templates/service_agreement/index.html.twig index 9582686a..e257245b 100644 --- a/templates/service_agreement/index.html.twig +++ b/templates/service_agreement/index.html.twig @@ -10,7 +10,7 @@ link_url: 'app_service_agreement_new', }) }} - {% set number_of_columns = 11 %} + {% set number_of_columns = 14 %} {{ form_start(form) }}
@@ -19,6 +19,7 @@ {{ form_row(form.cybersecurityAgreement) }} {{ form_row(form.hostingProvider) }} {{ form_row(form.active) }} + {{ form_row(form.projectLead) }}
@@ -30,16 +31,17 @@ {{ knp_pagination_sortable(service_agreements, '#', 'service_agreement.id') }} {{ knp_pagination_sortable(service_agreements, 'service_agreement.project'|trans, 'project.name') }} + {{ 'service_agreement.leantime_url'|trans }} {{ knp_pagination_sortable(service_agreements, 'service_agreement.client'|trans, 'client.name') }} - {{ knp_pagination_sortable(service_agreements, 'service_agreement.cybersecurity_agreement'|trans, 'cybersecurityAgreement.id') }} {{ knp_pagination_sortable(service_agreements, 'service_agreement.hosting_provider'|trans, 'service_agreement.hostingProvider') }} - {{ knp_pagination_sortable(service_agreements, 'service_agreement.document_url'|trans, 'service_agreement.documentUrl') }} - {{ knp_pagination_sortable(service_agreements, 'service_agreement.operations_price'|trans, 'service_agreement.price') }} + {{ 'service_agreement.server_size'|trans }} + {{ knp_pagination_sortable(service_agreements, 'service_agreement.operations_price_short'|trans, 'service_agreement.price') }} + {{ knp_pagination_sortable(service_agreements, 'CS', 'cybersecurityAgreement.id') }} + {{ 'service_agreement.cybersecurity_price_short'|trans }} {{ 'service_agreement.cyber_security_agreement.quarterly_hours'|trans }} + {{ 'service_agreement.note'|trans }} + {{ 'service_agreement.client_contact_name_short'|trans }} {{ knp_pagination_sortable(service_agreements, 'service_agreement.project_lead'|trans, 'projectLead.name') }} - {{ knp_pagination_sortable(service_agreements, 'service_agreement.valid_from'|trans, 'service_agreement.validFrom') }} - {{ knp_pagination_sortable(service_agreements, 'service_agreement.valid_to'|trans, 'service_agreement.validTo') }} - {{ knp_pagination_sortable(service_agreements, 'service_agreement.is_active'|trans, 'service_agreement.isActive') }} {{ 'service_agreement.actions'|trans }} @@ -48,32 +50,52 @@ {{ service_agreement.id }} {{ service_agreement.project }} + + {% if service_agreement.leantimeUrl %} + + + + + + {% endif %} + {{ service_agreement.client }} + {{ service_agreement.hostingProvider.value }} + {{ service_agreement.serverSize ? service_agreement.serverSize.value : '-' }} + {{ service_agreement.price }},- {% if service_agreement.cybersecurityAgreement %} - {{ 'service_agreement.has_cybersecurity_agreement_true'|trans }} + {{ 'service_agreement.has_cybersecurity_agreement_true'|trans }} {% else %} - {{ 'service_agreement.has_cybersecurity_agreement_false'|trans }} + {{ 'service_agreement.has_cybersecurity_agreement_false'|trans }} {% endif %} - {{ service_agreement.hostingProvider.value }} - - - - - - {{ service_agreement.price }},- + {{ service_agreement.cybersecurityAgreement and cyber_security_agreements[service_agreement.cybersecurityAgreement.id].price ? cyber_security_agreements[service_agreement.cybersecurityAgreement.id].price ~ ',-' : '-' }} {{ service_agreement.cybersecurityAgreement ? cyber_security_agreements[service_agreement.cybersecurityAgreement.id].quarterlyHours : '-' }} + + {% if service_agreement.cybersecurityAgreement and cyber_security_agreements[service_agreement.cybersecurityAgreement.id].note %} + + + + + + {% endif %} + + + {% if service_agreement.clientContactName %} + {{ service_agreement.clientContactName }} + {% if service_agreement.clientContactEmail %} + + + + + + {% endif %} + {% else %} + - + {% endif %} + {{ service_agreement.projectLead ? (service_agreement.projectLead.name ?? service_agreement.projectLead.email) : service_agreement.projectLead.email }} - {{ service_agreement.validFrom ? service_agreement.validFrom|date('d/m Y') : '' }} - {{ service_agreement.validTo ? service_agreement.validTo|date('d/m Y') : '' }} - {{ service_agreement.isActive ? 'service_agreement.yes'|trans : 'service_agreement.no'|trans }} {{ 'service_agreement.action_edit'|trans }} @@ -82,7 +104,7 @@ {% else %} - {{ 'service_agreement.no_records_found'|trans }} + {{ 'service_agreement.no_records_found'|trans }} {% endfor %} diff --git a/translations/messages.da.yaml b/translations/messages.da.yaml index 24d863f0..f5f6eb53 100644 --- a/translations/messages.da.yaml +++ b/translations/messages.da.yaml @@ -898,7 +898,7 @@ service_agreement: valid_from: "Gyldig fra" valid_to: "Gyldig til" is_active: "Aktiv" - has_cybersecurity_agreement: "Har CSA" + has_cybersecurity_agreement: "Har cybersikkerhedsaftale" quarterly_hours: "Timer pr. kvartal" note: "Note" actions: "Handlinger" @@ -917,11 +917,14 @@ service_agreement: no_records_found: "Ingen driftsaftaler" system_owner_notices: "Systemejer notifikation" operations_price: "Driftsaftale Pris" - is_eol: "EoL Aftale" + operations_price_short: "DA Pris" + cybersecurity_price_short: "CS Pris" + is_eol: "End-of-life aftale (EoL)" leantime_url: "Leantime Projekt" client_contact_name: "Kontakt hos kunden (Navn)" + client_contact_name_short: "Kontakt" client_contact_email: "Kontakt hos kunden (Email)" - dedicated_server: "Dedikeret Server" + dedicated_server: "Dedikeret server" server_size: "Serverstørrelse" cybersecurity_price: "Cybersikkerhedsaftale Pris" git_repos: "GIT Repos" @@ -932,7 +935,7 @@ service_agreement: search: "Søg" cyber_security_agreement: quarterly_hours: "Timer/kvartal" - filter_label: "Har CSA" + filter_label: "Har cybersikkerhedsaftale" include_all: "Alle" include_false: "Nej" include_true: "Ja"