diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f3e756..200bbc16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- [PR-301](https://github.com/OS2Forms/os2forms/pull/301) + Add address information to Digital Post shipments to ensure "*fjernprint*" + can be sent. + ## [5.0.0] 2025-11-18 - [PR-192](https://github.com/OS2Forms/os2forms/pull/192) diff --git a/modules/os2forms_digital_post/README.md b/modules/os2forms_digital_post/README.md index 999d87c7..16e83513 100644 --- a/modules/os2forms_digital_post/README.md +++ b/modules/os2forms_digital_post/README.md @@ -80,3 +80,154 @@ of recipients: ``` shell drush os2forms-digital-post:test:send --help ``` + +## Fjernprint (physical digital post) + +To comply with the address placement in the envelope window (kuvert-rude) an +[event subscriber](src/EventSubscriber/Os2formsDigitalPostSubscriber.php) is +used to inject an address information element into generated HTML before it is +converted to a PDF. + +We are only guaranteed to have the necessary information when in a digital +post context. For that reason, the injection of address information is only +done when in a digital post context. Note also that the information is only +injected – it is not styled. This allows flexibility across installations but +also means that it is up to individual installations to style it correctly. +This should be done in OS2Forms Attachment-templates, see +[Overwriting templates](https://github.com/OS2Forms/os2forms/tree/develop/modules/os2forms_attachment#overwriting-templates). + +To see the exact requirements for address placement, see +[digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf](docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf). + +### The injected HTML + +Variations of the injected HTML include extended addresses and c/o. + +Without extended address information or c/o: + +```html + +
+
+
Jeppe
+
Test vej HouseNr
+
2100 Copenhagen
+
+
+``` + +With just an extended address: + +```html +
+
+
Jeppe
+
Test vej HouseNr Floor AppartmentNr
+
2100 Copenhagen
+
+
+``` + +With just c/o: + +```html + +
+
+
Jeppe
+
c/o Mikkel
Test vej HouseNr
+
2100 Copenhagen
+
+
+``` + +With extended address information and c/o: + +```html +
+
+
Jeppe
+
c/o Mikkel
+
Test vej HouseNr Floor AppartmentNr
+
2100 Copenhagen
+
+
+``` + + + +### Styling of the HTML + +The following SCSS can be used to style the injected HTML accordingly: + +```scss +$margin-top: 25mm; +// There is no exact measurement for margin right in the specifications +$margin-right: 10mm; +$margin-bottom: 20mm; +$margin-left: 17mm; +$page-width: 210mm; +$page-height: 297mm; +$envelope-window-height: 89mm; +$envelope-window-width: 115mm; +$recipient-window-height: 21mm; +$recipient-window-width: 59mm; + +@page { + size: A4; + margin: 0; +} + +body { + margin-top: $margin-top; + margin-right: $margin-right; + margin-bottom: $margin-bottom; + margin-left: $margin-left; +} + +header { + position: fixed; + top: 0; + height: $margin-top; + width: calc($page-width - $margin-left - $margin-right); + font-size: 12px; +} + +footer { + position: fixed; + bottom: 0; + height: $margin-bottom; + width: calc($page-width - $margin-left - $margin-right); + font-size: 12px; +} + +// Style the envelope window that may be injected by Digital Post. +// Note that top/left is made from the assumption that @page has margin 0. +// @see \Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber:onPrintRender +#envelope-window-digital-post { + position: absolute; + top: $margin-top; + left: $margin-left; + height: $envelope-window-height; + width: $envelope-window-width; + background: white +} + +// If envelope window is present, move webform content down +// @see os2forms_digital_post +#envelope-window-digital-post ~ * .webform-entity-print-body { + margin-top: $envelope-window-height; +} + +// Style the h-card div +#envelope-window-digital-post > div { + position: absolute; + top: 16mm; + left: 4mm; + font-size: 10px; + height: $recipient-window-height; + width: $recipient-window-width; +} + +// More custom styling... +``` diff --git a/modules/os2forms_digital_post/docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf b/modules/os2forms_digital_post/docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf new file mode 100644 index 00000000..9f381c7a Binary files /dev/null and b/modules/os2forms_digital_post/docs/digst_a4_farve_ej_til_kant_demo_ny_rudeplacering.pdf differ diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 745b88d2..32fdb740 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -52,6 +52,7 @@ services: - "@logger.channel.os2forms_digital_post" - "@logger.channel.os2forms_digital_post_submission" - "@Drupal\\os2forms_digital_post\\Helper\\DigitalPostHelper" + - "@Drupal\\os2forms_digital_post\\EventSubscriber\\Os2formsDigitalPostSubscriber" Drupal\os2forms_digital_post\Helper\SF1461Helper: @@ -69,3 +70,9 @@ services: - '@database' - '@Drupal\os2forms_digital_post\Helper\MeMoHelper' - '@logger.channel.os2forms_digital_post' + + Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber: + arguments: + - '@session' + tags: + - { name: 'event_subscriber' } diff --git a/modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php b/modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php new file mode 100644 index 00000000..4fefcffa --- /dev/null +++ b/modules/os2forms_digital_post/src/EventSubscriber/Os2formsDigitalPostSubscriber.php @@ -0,0 +1,123 @@ +getHtml(); + + // Only modify HTML if there is exactly one submission. + if (count($event->getEntities()) === 1) { + $submission = $event->getEntities()[0]; + if ($submission instanceof WebformSubmissionInterface) { + // Check whether generation is for digital post. + if ($lookupResult = $this->getDigitalPostContext($submission)) { + + // Combine address parts. + $streetAddress = $lookupResult->getStreet(); + + if ($lookupResult->getHouseNr()) { + $streetAddress .= ' ' . $lookupResult->getHouseNr(); + } + + $extendedAddress = ''; + + if ($lookupResult->getFloor()) { + $extendedAddress = $lookupResult->getFloor(); + } + if ($lookupResult->getApartmentNr()) { + $extendedAddress .= ' ' . $lookupResult->getApartmentNr(); + } + + // Generate address HTML. + $addressHtml = '
'; + $addressHtml .= '
' . htmlspecialchars($lookupResult->getName()) . '
'; + if ($lookupResult->getCoName()) { + $addressHtml .= '
c/o ' . htmlspecialchars($lookupResult->getCoName()) . '
'; + } + $addressHtml .= '
'; + $addressHtml .= '' . htmlspecialchars($streetAddress) . ''; + if (!empty($extendedAddress)) { + $addressHtml .= ' ' . htmlspecialchars($extendedAddress) . ''; + } + $addressHtml .= '
'; + $addressHtml .= '
'; + $addressHtml .= '' . htmlspecialchars($lookupResult->getPostalCode()) . ''; + $addressHtml .= ' ' . htmlspecialchars($lookupResult->getCity()) . ''; + $addressHtml .= '
'; + $addressHtml .= '
'; + $addressHtml .= '
'; + + // Insert address HTML immediately after body opening tag. + $html = preg_replace('@]*>@', '${0}' . $addressHtml, $html); + } + } + } + + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + PrintEvents::POST_RENDER => ['onPrintRender'], + ]; + } + + /** + * Indicate Digital Post context in the current session. + */ + public function setDigitalPostContext(WebformSubmissionInterface $submission, CompanyLookupResult|CprLookupResult $lookupResult): void { + $key = $this->createSessionKeyFromSubmission($submission); + $this->session->set($key, $lookupResult); + } + + /** + * Check for Digital Post context in the current session. + */ + public function getDigitalPostContext(WebformSubmissionInterface $submission): CompanyLookupResult|CprLookupResult|null { + $key = $this->createSessionKeyFromSubmission($submission); + + $digitalPostContext = $this->session->get($key); + + // We only need/use it once, so just remove it after fetching it. + if ($digitalPostContext) { + $this->session->remove($key); + } + + return $digitalPostContext; + } + + /** + * Create a session key from a submission that is unique to the submission. + */ + public function createSessionKeyFromSubmission(WebformSubmissionInterface $submission): string { + // Due to cloning of submission during attachment logic, we cannot use + // submission id or uuid. Webform serial, however, is copied along, so a + // combination of webform id and serial is used for uniqueness. + // @see \Drupal\os2forms_attachment\Element\AttachmentElement::overrideWebformSettings + return 'digital_post_context_' . $submission->getWebform()->id() . '_' . $submission->serial(); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php index 60fae6a2..e26e0b1b 100644 --- a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php +++ b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php @@ -2,12 +2,15 @@ namespace Drupal\os2forms_digital_post\Helper; +use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult; +use Drupal\os2web_datalookup\LookupResult\CprLookupResult; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\advancedqueue\Entity\QueueInterface; use Drupal\advancedqueue\Job; use Drupal\advancedqueue\JobResult; +use Drupal\os2forms_digital_post\EventSubscriber\Os2formsDigitalPostSubscriber; use Drupal\os2forms_digital_post\Exception\InvalidRecipientIdentifierElementException; use Drupal\os2forms_digital_post\Exception\RuntimeException; use Drupal\os2forms_digital_post\Exception\SubmissionNotFoundException; @@ -62,6 +65,7 @@ public function __construct( #[Autowire(service: 'logger.channel.os2forms_digital_post_submission')] private readonly LoggerChannelInterface $submissionLogger, private readonly DigitalPostHelper $digitalPostHelper, + private readonly Os2formsDigitalPostSubscriber $digitalPostSubscriber, ) { $this->webformSubmissionStorage = $entityTypeManager->getStorage('webform_submission'); $this->queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); @@ -152,6 +156,8 @@ public function sendDigitalPost(WebformSubmissionInterface $submission, array $h $recipientIdentifierType = 'CPR'; } + $this->digitalPostSubscriber->setDigitalPostContext($submission, $lookupResult); + $senderSettings = $this->settings->getSender(); $messageOptions = [ self::RECIPIENT_IDENTIFIER_TYPE => $recipientIdentifierType,