mutual_credit-5.0.x-dev/src/Entity/Transaction.php

src/Entity/Transaction.php
<?php

namespace Drupal\mcapi\Entity;

use Drupal\mcapi\Entity\Currency;
use Drupal\mcapi\Entity\Type;
use Drupal\mcapi\Entity\State;
use Drupal\mcapi\Event\TransactionAssembleEvent;
use Drupal\mcapi\Event\McapiEvents;
use Drupal\user\UserInterface;
use Drupal\user\EntityOwnerTrait;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
use Drupal\Core\Messenger\MessengerTrait;

/**
 * Defines the Transaction entity.
 *
 * Transactions are grouped and handled by serial number but the unique database
 * key is the xid.
 *
 * @ContentEntityType(
 *   id = "mcapi_transaction",
 *   label = @Translation("Transaction"),
 *   label_collection = @Translation("Transactions"),
 *   handlers = {
 *     "storage" = "Drupal\mcapi\Entity\Storage\TransactionStorage",
 *     "storage_schema" = "Drupal\mcapi\Entity\Storage\TransactionStorageSchema",
 *     "view_builder" = "Drupal\mcapi\Entity\ViewBuilder\TransactionViewBuilder",
 *     "list_builder" = "\Drupal\mcapi\Entity\ListBuilder\TransactionListBuilder",
 *     "access" = "Drupal\mcapi\Entity\Access\TransactionAccessControlHandler",
 *     "form" = {
 *       "default" = "Drupal\mcapi\Form\TransactionForm",
 *       "bill" = "Drupal\mcapi\Form\TransactionForm",
 *       "credit" = "Drupal\mcapi\Form\TransactionForm"
 *     },
 *     "views_data" = "Drupal\mcapi\Entity\Views\TransactionViewsData",
 *     "route_provider" = {
 *       "html" = "Drupal\mcapi\Entity\RouteProvider\TransactionRouteProvider",
 *     },
 *   },
 *   admin_permission = "configure mcapi",
 *   base_table = "mcapi_transaction",
 *   entity_keys = {
 *     "id" = "xid",
 *     "uuid" = "uuid",
 *     "name" = "description",
 *     "owner" = "creator"
 *   },
 *   field_ui_base_route = "entity.mcapi_transaction.collection",
 *   translatable = FALSE,
 *   links = {
 *     "canonical" = "/transaction/{mcapi_transaction}",
 *     "add-form" = "/transact/add",
 *     "collection" = "/admin/accounting/transactions"
 *   },
 *   constraints = {
 *     "DifferentWallets" = {}
 *   }
 * )
 */
class Transaction extends ContentEntityBase implements TransactionInterface, \IteratorAggregate {

  use MessengerTrait;
  use EntityChangedTrait;
  use EntityOwnerTrait;

  protected $eventDispatcher;
  public bool $assembled = FALSE;
  public bool $isChild = FALSE;

  /**
   * Constructor.
   *
   * @note There seems to be no way to inject anything here.
   *
   * @see SqlContentEntityStorage::mapFromStorageRecords
   */
  public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
    parent::__construct($values, $entity_type, $bundle, $translations);
    $this->eventDispatcher = \Drupal::service('event_dispatcher');
  }

  /**
   * {@inheritDoc}
   */
  public function label($langcode = NULL) {
    return t("Transaction #@serial", ['@serial' => $this->serial->value]);
  }

  /**
   * {@inheritDoc}
   *
   * @note you cannot load a transaction by its xid.
   */
  public static function load($serial) {
    return static::loadBySerial($serial);
  }

  /**
   * {@inheritDoc}
   */
  public static function loadBySerial(int $serial) : Transaction {
    $storage = \Drupal::entityTypeManager()->getStorage('mcapi_transaction');
    $xid = $storage->getParentXid($serial);
    $results = $storage->loadByProperties(['xid' => $xid]);
    // Should be only one result [$xid => $serial]
    return reset($results);
  }

  /**
   * {@inheritDoc}
   */
  protected function urlRouteParameters($rel) {
    return [
      'mcapi_transaction' => $this->serial->value,
    ];
  }

  /**
   * {@inheritDoc}
   */
  public function assemble() : array {
    $new_children = [];
    if (!$this->assembled and $this->isNew() and !$this->isChild) {
      $start = count($this->children);
      $this->eventDispatcher->dispatch(
        new TransactionAssembleEvent($this),
        McapiEvents::ASSEMBLE
      );
      $new_children = array_slice($this->children->referencedEntities(), $start);
    }
    $this->assembled = TRUE;
    return $new_children;
  }

  /**
   * Validate a transaction (including children).
   */
  public function validate() : EntityConstraintViolationListInterface {
    static $parent = TRUE;
    if ($parent and !$this->assembled) {
      $this->assemble();
    }
    $parent = FALSE; // prevents infinite recursion.
    /* @var EntityConstraintViolationListInterface $violationList */
    $violationList = parent::validate();

    // The child referenced entities aren't validated automatically.
    foreach ($this->children->referencedEntities() as $child) {
      foreach ($child->validate() as $violation) {
        $violationList->add($violation);
      }
    }
    return $violationList;
  }

  /**
   * {@inheritDoc}
   */
  public static function preCreate(EntityStorageInterface $storage, array &$values) {
    $type_name = isset($values['type']) ? $values['type'] : 'default';
    if ($type = Type::load($type_name)) {
      // doesn't apply to imported transactions or child transactions.
      if (empty($values['serial']) and empty($values['state'])) {
        $values['state'] = 'temp';
      }
    }
    else {
      trigger_error("Replaced bad transaction type '$type_name' with 'default'", E_USER_ERROR);
    }
    if (empty($values['worth']) and isset($values['quantity'])) {
      if ($curr = $values['currency'] ?? $values['currency_id'] ?? $values['curr_id']) {
        $values['worth'][] = [
          'curr_id' => $curr,
          'value' => $values['quantity']
        ];
        unset($values['currency'], $values['currcode'], $values['curr_id']);
      }
      else {
        throw new \Exception('Cannot derive worth field from given fields: '.print_r($values, 1));
      }
    }
    $values += [
      'type' => 'default',
      // Uid of 0 means drush must have created it.
      'creator' => static::getDefaultEntityOwner(),
      'state' => 'done'
    ];

    // There isn't a hook these can be moved into.
    if (isset($values['payer']) and is_array($values['payer'])) {
      $values['type'] = 'mass';
      $payers = $values['payer'];
      $values['payer']= array_shift($payers);
      foreach($payers as $payer) {
        $values['children'][] = static::create(['payer' => $payer] + $values);
      }
    }
    elseif (isset($values['payee']) and is_array($values['payee'])) {
      $values['type'] = 'mass';
      $payees = $values['payee'];
      $values['payee'] = array_shift($payees);
      foreach($payees as $payee) {
        $values['children'][] = static::create(['payee' => $payee] + $values);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public function getCacheTagsToInvalidate() {
    $tags = parent::getCacheTagsToInvalidate();
    // Invalidate tags of wallets in child transactions.
    foreach ($this->flatten() as $transaction) {
      foreach (['payer', 'payee'] as $actor) {
        if ($wallet = $transaction->{$actor}->entity) {
          $tags = array_merge_recursive($tags, $wallet->getCacheTagsToInvalidate());
        }
      }
    }
    // We should only be clearing the currency displays, but I haven't worked out the cache tags for that.
    foreach (Currency::loadMultiple($this->worth->currencies()) as $currency) {
      $tags = array_merge($tags, $currency->getCacheTagsToInvalidate());
    }
    return $tags;
  }

  /**
   * {@inheritDoc}
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    $fields = parent::baseFieldDefinitions($entity_type);
    $fields += static::ownerBaseFieldDefinitions($entity_type);
    $fields['description'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Description'))
      ->setDescription(t('The good or service.'))
      ->setSettings(['max_length' => 255])
      ->setRequired(FALSE)
      ->setDefaultValue('')
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayOptions('form', ['type' => 'string_textfield', 'weight' => 3])
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('view', ['type' => 'string', 'weight' => 3]);

    $fields['serial'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Serial number'))
      ->setDescription(t('Grouping of related transactions.'))
      ->setReadOnly(TRUE)
      ->setRequired(FALSE);

    $fields['worth'] = BaseFieldDefinition::create('worth')
      ->setLabel(t('Worth'))
      ->setDescription(t('Value of the transaction (one or more currencies)'))
      ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
      ->setRequired(TRUE)
      ->addPropertyConstraints('curr_id', [
        'AllowedValues' => ['callback' =>  ['Drupal\mcapi\Entity\Transaction', 'getAllCurrencyIds']],
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('view', ['weight' => 4]);

    $fields[PAYER_FIELDNAME] = BaseFieldDefinition::create('wallet_reference')
      ->setLabel(t('Payer'))
      ->setDescription(t('The giving wallet'))
      ->setReadOnly(TRUE)
      ->setRequired(TRUE)
      ->setCardinality(1)
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayOptions('form', ['type' => 'wallet_reference_autocomplete', 'weight' => 1])
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('view', ['type' => 'entity_reference_label', 'weight' => 1]);

    $fields[PAYEE_FIELDNAME] = BaseFieldDefinition::create('wallet_reference')
      ->setLabel(t('Payee'))
      ->setDescription(t('The receiving wallet'))
      ->setReadOnly(TRUE)
      ->setRequired(TRUE)
      ->setCardinality(1)
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayOptions('form', ['type' => 'wallet_reference_autocomplete', 'weight' => 2])
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('view', ['type' => 'entity_reference_label', 'weight' => 2]);

    $fields['creator'] // Alterations to ownerBaseFieldDefinition
      ->setLabel(t('Creator'))
      ->setDescription(t('The user who created the transaction'))
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions(
        'form',
        ['type' => 'hidden', 'weight' => 10]
      )
      ->setReadOnly(TRUE)
      ->setRequired(TRUE)
      ->setRevisionable(FALSE);

    $fields['created'] = BaseFieldDefinition::create('created')
      ->setLabel(t('Created on'))
      ->setDescription(t('The time the transaction was created.'))
      ->setRevisionable(FALSE)
      ->setRequired(TRUE)
      ->setDisplayOptions(
        'form',
        ['type' => 'hidden', 'weight' => 10]
      )
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(t('Changed'))
      ->setDescription(t('The time that the transaction was last saved.'))
      ->setDisplayConfigurable('view', TRUE)
      ->setInitialValue('created')
      ->setRevisionable(FALSE)
      ->setRequired(TRUE)
      ->setTranslatable(FALSE);

    $fields['type'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Type'))
      ->setDescription(t('The type/workflow path of the transaction'))
      ->setSetting('target_type', 'mcapi_type')
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayOptions('form', ['region' => 'hidden'])
      ->addPropertyConstraints('target_id', [
        'AllowedValues' => ['callback' =>  ['Drupal\mcapi\Entity\Transaction', 'getAllowedTypes']],
      ])
      ->setReadOnly(TRUE)
      ->setRequired(TRUE);

    $fields['state'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('State'))
      ->setDescription(t('Completed, pending, disputed, etc.'))
      ->setSetting('target_type', 'mcapi_state')
      ->setDefaultValue('temp')
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('form', ['region' => 'hidden'])
      ->addPropertyConstraints('target_id', [
        'AllowedValues' => ['callback' => ['Drupal\mcapi\Entity\Transaction', 'getAllowedStates']],
      ])
      ->setReadOnly(FALSE)
      ->setRequired(TRUE);

    $fields['children'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Children'))
      ->setDescription(t('Dependent transactions such as fees'))
      ->setSetting('target_type', 'mcapi_transaction')
      ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
      ->setRevisionable(FALSE)
      ->setDisplayConfigurable('view', true)
      ->setDisplayConfigurable('form', false);
    return $fields;
  }

  /**
   * {@inheritDoc}
   */
  public function getOwner() {
    return $this->creator->entity;
  }

  /**
   * {@inheritDoc}
   */
  public function getOwnerId() {
    return $this->creator->target_id;
  }

  /**
   * {@inheritDoc}
   */
  public function setOwner(UserInterface $account) {
    $this->creator = $account;
  }

  /**
   * {@inheritDoc}
   */
  public function setOwnerId($uid) {
    $this->creator->targetId = $uid;
  }

  /**
   * {@inheritDoc}
   */
  public function getCreatedTime() {
    return $this->get('created')->value;
  }

  /**
   * {@inheritDoc}
   */
  public function toArray() {
    $array = parent::toArray();
    foreach ($this->children->referencedEntities() as $key => $transaction) {
      $array['children'][$key]['entity'] = $transaction->toArray();
    }
    return $array;
  }


  /**
   * Options callback for Transaction::state field.
   */
  static function getAllowedStates() :array {
    return array_keys(State::LoadMultiple());
  }

  /**
   * Options callback for Transaction::type field.
   */
  static function getAllowedTypes() {
    return array_keys(Type::LoadMultiple());
  }

  /**
   * Options callback for Transaction::worth::curr_id field.
   */
  static function getAllCurrencyIds() {
    return array_keys(Currency::LoadMultiple());
  }
  /**
   * {@inheritDoc}
   * @deprecated but removing requires reinstalling the field definition and content as in mcapi_update_8998
   */
  static function getAllowedCurrencies() {
    return static::getAllCurrencyIds();
  }

  /**
   * {@inheritDoc}
   *
   * Workaround for when the content_translation module tries to link to the
   * original language before the entity is saved and an id is available on transaction/0/save
   * I don't know why the entity definition 'translatable = FALSE' doesn't apply
   */
  public function hasLinkTemplate($rel) {
    if (!$this->isNew()) {
      return parent::hasLinkTemplate($rel);
    }
    return FALSE;
  }

  /**
   * Make a child as a clone of the $this, and modify given fields.
   * @param array $params
   * @throws \Exception
   */
  public function createChild(array $params) : static {
    $logger = \Drupal::logger('mcapi');
    if ($params['payer'] == $params['payee']) {
      throw new \Exception('Skipped child transaction with same payer and payee: '.$params['payee']);
    }
    $params['worth']['curr_id'] = $params['curr_id'] ?? $this->worth->curr_id;
    unset($params['curr_id']);
    if (isset($params['quantity'])) {
      $params['worth']['value'] = $params['quantity'];
      unset($params['quantity']);
    }
    unset($params['state']);
    $params += ['type' => 'inherit'];
    $params += $this->toArray();
    // This prevents validation recursing through the children and will be overwritten
    $child = Transaction::Create();

    foreach ($params as $field => $val) {
      if (in_array($field, ['xid', 'uuid', 'children'])) continue;
      $child->set($field, $val);
    }
    // Check the value wasn't rounded down to nothing
    if ($child->worth->isEmpty()) {
      throw new \Exception('Empty worth value in child transaction');
    }
    $child->isChild = TRUE;
    $this->children->appendItem($child);
    return $child;
  }

  /**
   * {@inheritDoc}
   */
  function onChange($name) {
    parent::onChange($name);
    if ($name == 'state' or $name == 'serial') {
      foreach ($this->children->referencedEntities() as $child) {
        $child->{$name}->setValue($this->{$name}->getValue());
      }
    }
  }

  /**
   * Iterate through the transaction and children as if it was an array.
   */
  public function flatten() {
    yield $this;
    foreach ($this->children->referencedEntities() as $child) {
      yield $child;
    }
  }

  /**
   * Delete all the transaction children.
   */
  public function delete() {
    foreach ($this->children->referencedEntities() as $t) {
      $t->delete();
    }
    parent::delete();
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc