From 0a7ad12fbdda821e4375a4f4c7e65909581d1f6b Mon Sep 17 00:00:00 2001 From: s j <sj@1729.be> Date: Sun, 31 Jan 2021 20:22:43 +0100 Subject: [PATCH 1/2] i5528 add vote_weight property to share_type, export at date of total vote_weight per shareholder --- modules/sr_general_meeting/composer.json | 13 ++ .../sr_general_meeting.info.yml | 5 + .../sr_general_meeting.links.menu.yml | 6 + .../sr_general_meeting.module | 62 ++++++ .../sr_general_meeting.routing.yml | 7 + .../sr_general_meeting.services.yml | 7 + .../src/Form/ExportGeneralMeetingVoteForm.php | 77 ++++++++ .../src/GeneralMeetingService.php | 187 ++++++++++++++++++ .../src/GeneralMeetingServiceInterface.php | 11 ++ .../templates/sr-general-meeting.html.twig | 1 + .../tests/src/Functional/LoadTest.php | 46 +++++ 11 files changed, 422 insertions(+) create mode 100644 modules/sr_general_meeting/composer.json create mode 100644 modules/sr_general_meeting/sr_general_meeting.info.yml create mode 100644 modules/sr_general_meeting/sr_general_meeting.links.menu.yml create mode 100644 modules/sr_general_meeting/sr_general_meeting.module create mode 100644 modules/sr_general_meeting/sr_general_meeting.routing.yml create mode 100644 modules/sr_general_meeting/sr_general_meeting.services.yml create mode 100644 modules/sr_general_meeting/src/Form/ExportGeneralMeetingVoteForm.php create mode 100644 modules/sr_general_meeting/src/GeneralMeetingService.php create mode 100644 modules/sr_general_meeting/src/GeneralMeetingServiceInterface.php create mode 100644 modules/sr_general_meeting/templates/sr-general-meeting.html.twig create mode 100644 modules/sr_general_meeting/tests/src/Functional/LoadTest.php diff --git a/modules/sr_general_meeting/composer.json b/modules/sr_general_meeting/composer.json new file mode 100644 index 00000000..45685618 --- /dev/null +++ b/modules/sr_general_meeting/composer.json @@ -0,0 +1,13 @@ +{ + "name": "sr_general_meeting", + "type": "drupal-module", + "description": "Shareholder Register General Meeting", + "keywords": [ + ], + "homepage": "https://www.drupal.org/project/sr_general_meeting", + "minimum-stability": "dev", + "support": { + "issues": "https://www.drupal.org/project/issues/sr_general_meeting", + "source": "http://cgit.drupalcode.org/sr_general_meeting" + } +} diff --git a/modules/sr_general_meeting/sr_general_meeting.info.yml b/modules/sr_general_meeting/sr_general_meeting.info.yml new file mode 100644 index 00000000..57b94023 --- /dev/null +++ b/modules/sr_general_meeting/sr_general_meeting.info.yml @@ -0,0 +1,5 @@ +name: 'Shareholder Register General Meeting' +type: module +description: 'Shareholder Register General Meeting' +core: 8.x +package: 'startx' diff --git a/modules/sr_general_meeting/sr_general_meeting.links.menu.yml b/modules/sr_general_meeting/sr_general_meeting.links.menu.yml new file mode 100644 index 00000000..2ee489ca --- /dev/null +++ b/modules/sr_general_meeting/sr_general_meeting.links.menu.yml @@ -0,0 +1,6 @@ + +sr_general_meeting_votes: + title: 'General Meeting votes' + description: 'Export voting weights for general meeting' + route_name: sr_general_meeting.export_general_meeting_vote_form + parent: system.admin.shareholder_register.tools diff --git a/modules/sr_general_meeting/sr_general_meeting.module b/modules/sr_general_meeting/sr_general_meeting.module new file mode 100644 index 00000000..64dbd8ff --- /dev/null +++ b/modules/sr_general_meeting/sr_general_meeting.module @@ -0,0 +1,62 @@ +<?php + +/** + * @file + * Contains sr_general_meeting.module. + */ + +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Entity\EntityTypeInterface; + +/** + * Implements hook_help(). + */ +function sr_general_meeting_help($route_name, RouteMatchInterface $route_match) { + switch ($route_name) { + // Main module help for the sr_general_meeting module. + case 'help.page.sr_general_meeting': + $output = ''; + $output .= '<h3>' . t('About') . '</h3>'; + $output .= '<p>' . t('Shareholder Register General Meeting') . '</p>'; + return $output; + + default: + } +} + +/** + * Implements hook_theme(). + */ +function sr_general_meeting_theme() { + return [ + 'sr_general_meeting' => [ + 'render element' => 'children', + ], + ]; +} + +/** + * Implements hook_entity_base_field_info(). + */ +function sr_general_meeting_entity_base_field_info(EntityTypeInterface $entity_type) { + if ($entity_type->id() == 'share_type') { + $fields = array(); + $fields['vote_weight'] = BaseFieldDefinition::create('integer') + ->setLabel(t('Voting weight')) + ->setDisplayOptions('view', [ + 'label' => 'above', + 'type' => 'number', + 'weight' => 50, + ]) + ->setDisplayOptions('form', [ + 'label' => 'above', + 'type' => 'number', + 'weight' => 50, + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + return $fields; + } +} diff --git a/modules/sr_general_meeting/sr_general_meeting.routing.yml b/modules/sr_general_meeting/sr_general_meeting.routing.yml new file mode 100644 index 00000000..2caf2334 --- /dev/null +++ b/modules/sr_general_meeting/sr_general_meeting.routing.yml @@ -0,0 +1,7 @@ +sr_general_meeting.export_general_meeting_vote_form: + path: '/admin/shareholder_register/tools/export_general_meeting_vote' + defaults: + _form: '\Drupal\sr_general_meeting\Form\ExportGeneralMeetingVoteForm' + _title: 'Export General Meeting Voting Weigth' + requirements: + _access: 'TRUE' diff --git a/modules/sr_general_meeting/sr_general_meeting.services.yml b/modules/sr_general_meeting/sr_general_meeting.services.yml new file mode 100644 index 00000000..7da359b3 --- /dev/null +++ b/modules/sr_general_meeting/sr_general_meeting.services.yml @@ -0,0 +1,7 @@ +services: + logger.channel.sr_general_meeting: + parent: logger.channel_base + arguments: ['sr_general_meeting'] + sr_general_meeting.default: + class: Drupal\sr_general_meeting\GeneralMeetingService + arguments: [] diff --git a/modules/sr_general_meeting/src/Form/ExportGeneralMeetingVoteForm.php b/modules/sr_general_meeting/src/Form/ExportGeneralMeetingVoteForm.php new file mode 100644 index 00000000..8502f321 --- /dev/null +++ b/modules/sr_general_meeting/src/Form/ExportGeneralMeetingVoteForm.php @@ -0,0 +1,77 @@ +<?php + +namespace Drupal\sr_general_meeting\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class ExportGeneralMeetingVoteForm. + */ +class ExportGeneralMeetingVoteForm extends FormBase { + + /** + * Drupal\sr_general_meeting\GeneralMeetingServiceInterface definition. + * + * @var \Drupal\sr_general_meeting\GeneralMeetingServiceInterface + */ + protected $srGeneralMeetingDefault; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + $instance = parent::create($container); + $instance->srGeneralMeetingDefault = $container->get('sr_general_meeting.default'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'export_general_meeting_vote_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['date'] = [ + '#type' => 'date', + '#title' => $this->t('Date'), + '#weight' => '0', + '#required' => TRUE, + '#default_value' => date('Y-m-d'), + ]; + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Submit'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + foreach ($form_state->getValues() as $key => $value) { + // @TODO: Validate fields. + } + parent::validateForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = [ + 'date' => $form_state->getValue('date') + ]; + $batch = $this->srGeneralMeetingDefault->getGeneralMeetingBatch($config); + batch_set($batch); + } + +} diff --git a/modules/sr_general_meeting/src/GeneralMeetingService.php b/modules/sr_general_meeting/src/GeneralMeetingService.php new file mode 100644 index 00000000..94c32e6e --- /dev/null +++ b/modules/sr_general_meeting/src/GeneralMeetingService.php @@ -0,0 +1,187 @@ +<?php + +namespace Drupal\sr_general_meeting; + +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; + +use Drupal\Core\Url; +use Drupal\Core\Link; + +use Drupal\shareholder_register\Entity\Shareholder; + +/** + * Class GeneralMeetingService. + */ +class GeneralMeetingService implements GeneralMeetingServiceInterface { + + + /** + * Constructs a new GeneralMeetingService object. + */ + public function __construct() { + } + + /** + * {@inheritdoc} + */ + public function getGeneralMeetingBatch($config) { + $batch = array( + 'title' => t('Computing general meeting voting weight'), + 'operations' => [], + ); + $batch['operations'][] = [ + '\Drupal\sr_general_meeting\GeneralMeetingService::initGeneralMeetingBatch', + [$config], + ]; + $batch['operations'][] = [ + '\Drupal\sr_general_meeting\GeneralMeetingService::computeGeneralMeetingBatch', + [$config], + ]; + $batch['operations'][] = [ + '\Drupal\sr_general_meeting\GeneralMeetingService::writeGeneralMeetingBatch', + [$config], + ]; + $batch['finished'] = '\Drupal\sr_general_meeting\GeneralMeetingService::finishGeneralMeetingBatch'; + + return $batch; + } + + /** + * {@inheritdoc} + */ + public static function initGeneralMeetingBatch($general_meeting_config, &$context) { + $context['results']['details'] = []; + $context['results']['totals'] = []; + $context['results']['config'] = $general_meeting_config; + } + + /** + * {@inheritdoc} + */ + public static function computeGeneralMeetingBatch($general_meeting_config, &$context) { + $formatter = \Drupal::service('shareholder_register.formatter'); + + if (!isset($context['sandbox']['shareholder_ids'])) { + $shareholder_ids = \Drupal::entityQuery('shareholder') + ->condition('state', 'valid') + ->execute(); + + $context['sandbox']['shareholder_ids'] = $shareholder_ids; + $context['sandbox']['shareholder_ids_count'] = count($context['sandbox']['shareholder_ids']); + $context['results']['totals'] = []; + } + + for ($i = 0; $i < 100; $i++) { + if ($shareholder_id = array_pop($context['sandbox']['shareholder_ids'])) { + $shareholder = Shareholder::load($shareholder_id); + + $shares = $shareholder->getSharesAtDate($general_meeting_config['date']); + + if (!count($shares)) { + continue; + } + $weight = 0; + foreach ($shares as $share) { + $weight += $share->getShareType()->get('vote_weight')->value; + } + + $context['results']['totals'][$shareholder->id()] = [ + 'shareholder_id' => $shareholder->id(), + 'shares' => $formatter->sharesToRanges($shares), + 'weight' => $weight, + ]; + + } + } + + if ($context['sandbox']['shareholder_ids_count'] > 0) { + $context['finished'] = ($context['sandbox']['shareholder_ids_count'] - count($context['sandbox']['shareholder_ids'])) / $context['sandbox']['shareholder_ids_count']; + } + else { + $context['finished'] = 1; + } + } + + /** + * {@inheritdoc} + */ + public static function writeGeneralMeetingBatch($general_meeting_config, &$context) { + if (!isset($context['results']['output'])) { + $output = file_save_data('', 'private://general_meeting.xlsx'); + $output->setTemporary(); + $output->save(); + + $context['results']['output'] = $output; + + $spreadsheet = new Spreadsheet(); + + // Set document properties. + $spreadsheet->getProperties()->setCreator('DSR') + ->setLastModifiedBy(t('DSR')) + ->setTitle(t('DSR General Meeting Voting Weight')) + ->setSubject(t('DSR General Meeting Voting Weight')) + ->setDescription(t('DSR General Meeting Voting Weight')) + ->setCategory(t('DSR General Meeting')); + } + else { + $reader = new Xlsx(); + $reader->setReadDataOnly(TRUE); + $spreadsheet = $reader->load(drupal_realpath($context['results']['output']->getFileUri())); + } + + if (!isset($context['sandbox']['to_write'])) { + $config = $context['results']['config']; + $sheet = $spreadsheet->setActiveSheetIndex(0); + + $sheet->setCellValue("A1", t('General Meeting Voting Weight for date @date', + [ + '@date' => $general_meeting_config['date'], + ])); + $sheet->setCellValue("A3", t('Shareholder Number')); + $sheet->setCellValue("B3", t('Shareholder Name')); + $sheet->setCellValue("C3", t('Shares')); + $sheet->setCellValue("D3", t('Voting weight')); + + $context['sandbox']['row'] = 5; + $context['sandbox']['to_write'] = $context['results']['totals']; + } + else { + $sheet = $spreadsheet->getActiveSheet(); + } + + for ($i = 0; $i < 5000; $i++) { + if ($group = array_pop($context['sandbox']['to_write'])) { + $row = $context['sandbox']['row']; + $shareholder = Shareholder::load($group['shareholder_id']); + + $sheet->setCellValue("A{$row}", $shareholder->getNumber()); + $sheet->setCellValue("B{$row}", $shareholder->getName()); + $sheet->setCellValue("C{$row}", $group['shares']); + $sheet->setCellValue("D{$row}", $group['weight']); + + $context['sandbox']['row']++; + } + } + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->setPreCalculateFormulas(FALSE); + $writer->save(drupal_realpath($context['results']['output']->getFileUri())); + $context['results']['output']->save(); + $context['finished'] = (count($context['results']['totals']) - count($context['sandbox']['to_write'])) / count($context['results']['totals']); + } + + /** + * {@inheritdoc} + */ + public static function finishGeneralMeetingBatch($success, $results, $operations) { + drupal_set_message(t( + 'Exported General Meeting Voting Weight download: @url', + [ + '@url' => Link::fromTextAndUrl('general_meeting.xlsx', Url::fromUri(file_create_url($results['output']->getFileUri())))->toString(), + ] + )); + } + +} diff --git a/modules/sr_general_meeting/src/GeneralMeetingServiceInterface.php b/modules/sr_general_meeting/src/GeneralMeetingServiceInterface.php new file mode 100644 index 00000000..d8aedefb --- /dev/null +++ b/modules/sr_general_meeting/src/GeneralMeetingServiceInterface.php @@ -0,0 +1,11 @@ +<?php + +namespace Drupal\sr_general_meeting; + +/** + * Interface GeneralMeetingServiceInterface. + */ +interface GeneralMeetingServiceInterface { + + +} diff --git a/modules/sr_general_meeting/templates/sr-general-meeting.html.twig b/modules/sr_general_meeting/templates/sr-general-meeting.html.twig new file mode 100644 index 00000000..95722bd3 --- /dev/null +++ b/modules/sr_general_meeting/templates/sr-general-meeting.html.twig @@ -0,0 +1 @@ +<!-- Add you custom twig html here --> diff --git a/modules/sr_general_meeting/tests/src/Functional/LoadTest.php b/modules/sr_general_meeting/tests/src/Functional/LoadTest.php new file mode 100644 index 00000000..7a007819 --- /dev/null +++ b/modules/sr_general_meeting/tests/src/Functional/LoadTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\Tests\sr_general_meeting\Functional; + +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; + +/** + * Simple test to ensure that main page loads with module enabled. + * + * @group sr_general_meeting + */ +class LoadTest extends BrowserTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['sr_general_meeting']; + + /** + * A user with permission to administer site configuration. + * + * @var \Drupal\user\UserInterface + */ + protected $user; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->user = $this->drupalCreateUser(['administer site configuration']); + $this->drupalLogin($this->user); + } + + /** + * Tests that the home page loads with a 200 response. + */ + public function testLoad() { + $this->drupalGet(Url::fromRoute('<front>')); + $this->assertSession()->statusCodeEquals(200); + } + +} -- GitLab From 26c0adacae539142cdfccabeba45f220017e665c Mon Sep 17 00:00:00 2001 From: s j <sj@1729.be> Date: Mon, 1 Feb 2021 10:12:45 +0100 Subject: [PATCH 2/2] i5528 add details to export of voting weights --- .../src/GeneralMeetingService.php | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/modules/sr_general_meeting/src/GeneralMeetingService.php b/modules/sr_general_meeting/src/GeneralMeetingService.php index 94c32e6e..8cb18db6 100644 --- a/modules/sr_general_meeting/src/GeneralMeetingService.php +++ b/modules/sr_general_meeting/src/GeneralMeetingService.php @@ -43,6 +43,10 @@ class GeneralMeetingService implements GeneralMeetingServiceInterface { '\Drupal\sr_general_meeting\GeneralMeetingService::writeGeneralMeetingBatch', [$config], ]; + $batch['operations'][] = [ + '\Drupal\sr_general_meeting\GeneralMeetingService::writeGeneralMeetingDetailsBatch', + [$config], + ]; $batch['finished'] = '\Drupal\sr_general_meeting\GeneralMeetingService::finishGeneralMeetingBatch'; return $batch; @@ -57,6 +61,16 @@ class GeneralMeetingService implements GeneralMeetingServiceInterface { $context['results']['config'] = $general_meeting_config; } + /** + * {@inheritdoc} + */ + public static function getShareHash($share) { + return [ + 'hash' => $share->getShareType()->id(), + 'label' => $share->getShareType()->label(), + ]; + } + /** * {@inheritdoc} */ @@ -71,6 +85,7 @@ class GeneralMeetingService implements GeneralMeetingServiceInterface { $context['sandbox']['shareholder_ids'] = $shareholder_ids; $context['sandbox']['shareholder_ids_count'] = count($context['sandbox']['shareholder_ids']); $context['results']['totals'] = []; + $context['results']['details'] = []; } for ($i = 0; $i < 100; $i++) { @@ -83,8 +98,29 @@ class GeneralMeetingService implements GeneralMeetingServiceInterface { continue; } $weight = 0; + $shares_by_hash = []; foreach ($shares as $share) { + $hash_detail = static::getShareHash($share); $weight += $share->getShareType()->get('vote_weight')->value; + + if (!isset($context['results']['details'][$shareholder->id()])) { + $context['results']['details'][$shareholder->id()] = []; + } + if (!isset($context['results']['details'][$shareholder->id()][$hash_detail['hash']])) { + $context['results']['details'][$shareholder->id()][$hash_detail['hash']] = [ + 'shareholder_id' => $shareholder->id(), + 'share_numbers' => [], + 'label' => $hash_detail['label'], + 'weight' => 0, + ]; + $shares_by_hash[$hash_detail['hash']] = []; + } + $context['results']['details'][$shareholder->id()][$hash_detail['hash']]['weight'] += $share->getShareType()->get('vote_weight')->value; + $shares_by_hash[$hash_detail['hash']][] = $share; + } + + foreach ($shares_by_hash as $hash => $shares_for_hash) { + $context['results']['details'][$shareholder->id()][$hash]['shares'] = $formatter->sharesToRanges($shares_for_hash); } $context['results']['totals'][$shareholder->id()] = [ @@ -172,6 +208,79 @@ class GeneralMeetingService implements GeneralMeetingServiceInterface { $context['finished'] = (count($context['results']['totals']) - count($context['sandbox']['to_write'])) / count($context['results']['totals']); } + /** + * {@inheritdoc} + */ + public static function writeGeneralMeetingDetailsBatch($general_meeting_config, &$context) { + if (!isset($context['results']['output'])) { + $output = file_save_data('', 'private://general_meeting.xlsx'); + $output->setTemporary(); + $output->save(); + + $context['results']['output'] = $output; + + $spreadsheet = new Spreadsheet(); + + // Set document properties. + $spreadsheet->getProperties()->setCreator('DSR') + ->setLastModifiedBy(t('DSR')) + ->setTitle(t('DSR General Meeting Voting Weight')) + ->setSubject(t('DSR General Meeting Voting Weight')) + ->setDescription(t('DSR General Meeting Voting Weight')) + ->setCategory(t('DSR General Meeting')); + } + else { + $reader = new Xlsx(); + $reader->setReadDataOnly(TRUE); + $spreadsheet = $reader->load(drupal_realpath($context['results']['output']->getFileUri())); + } + + if (!isset($context['sandbox']['to_write'])) { + $config = $context['results']['config']; + $spreadsheet->createSheet(); + $sheet = $spreadsheet->setActiveSheetIndex(1); + + $sheet->setCellValue("A1", t('General Meeting Voting Weight for date @date', + [ + '@date' => $general_meeting_config['date'], + ])); + $sheet->setCellValue("A3", t('Shareholder Number')); + $sheet->setCellValue("B3", t('Shareholder Name')); + $sheet->setCellValue("C3", t('Type')); + $sheet->setCellValue("D3", t('Shares')); + $sheet->setCellValue("E3", t('Voting weight')); + + $context['sandbox']['row'] = 5; + $context['sandbox']['to_write'] = $context['results']['details']; + } + else { + $sheet = $spreadsheet->getActiveSheet(); + } + + for ($i = 0; $i < 5000; $i++) { + if ($shareholder_group = array_pop($context['sandbox']['to_write'])) { + foreach ($shareholder_group as $group) { + $row = $context['sandbox']['row']; + $shareholder = Shareholder::load($group['shareholder_id']); + + $sheet->setCellValue("A{$row}", $shareholder->getNumber()); + $sheet->setCellValue("B{$row}", $shareholder->getName()); + $sheet->setCellValue("C{$row}", $group['label']); + $sheet->setCellValue("D{$row}", $group['shares']); + $sheet->setCellValue("E{$row}", $group['weight']); + + $context['sandbox']['row']++; + } + } + } + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->setPreCalculateFormulas(FALSE); + $writer->save(drupal_realpath($context['results']['output']->getFileUri())); + $context['results']['output']->save(); + $context['finished'] = (count($context['results']['totals']) - count($context['sandbox']['to_write'])) / count($context['results']['totals']); + } + /** * {@inheritdoc} */ -- GitLab