Initial asset.inventory service.
This commit is contained in:
parent
d71612a2b9
commit
4064c4f84f
|
@ -39,6 +39,29 @@ a string in Well-Known Text format.
|
|||
$geometry = \Drupal::service('asset.location')->getGeometry($asset);
|
||||
```
|
||||
|
||||
## Asset inventory service
|
||||
|
||||
**Service name**: `asset.inventory`
|
||||
|
||||
The asset inventory service provides methods that encapsulate the logic for
|
||||
determining an asset's inventory.
|
||||
|
||||
**Methods**:
|
||||
|
||||
`getInventory($asset, $measure = '', $units = '')` - Get inventory summaries
|
||||
for an asset. Returns an array of arrays with the following keys: `measure`,
|
||||
`value`, `units`. This can be optionally filtered by `$measure` and `$units`.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```php
|
||||
// Get summaries of all inventories for an asset.
|
||||
$all_inventory = \Drupal::service('asset.inventory')->getInventory($asset);
|
||||
|
||||
// Get the current inventory for a given measure and units.
|
||||
$gallons_of_fertilizer = \Drupal::service('asset.inventory')->getInventory($asset, 'volume', 'gallons');
|
||||
```
|
||||
|
||||
## Group membership service
|
||||
|
||||
**Service name**: `group.membership`
|
||||
|
|
|
@ -8,3 +8,4 @@ dependencies:
|
|||
- farm:asset
|
||||
- farm:farm_field
|
||||
- farm:quantity
|
||||
- fraction:fraction
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
services:
|
||||
asset.inventory:
|
||||
class: Drupal\farm_inventory\AssetInventory
|
||||
arguments:
|
||||
[ '@datetime.time' ]
|
|
@ -0,0 +1,276 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\farm_inventory;
|
||||
|
||||
use Drupal\asset\Entity\AssetInterface;
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\fraction\Fraction;
|
||||
|
||||
/**
|
||||
* Asset inventory logic.
|
||||
*/
|
||||
class AssetInventory implements AssetInventoryInterface {
|
||||
|
||||
/**
|
||||
* The database object.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The time service.
|
||||
*
|
||||
* @var \Drupal\Component\Datetime\TimeInterface
|
||||
*/
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(TimeInterface $time) {
|
||||
$this->database = Database::getConnection();
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInventory(AssetInterface $asset, string $measure = '', int $units = 0): array {
|
||||
|
||||
// Get a list of the measure+units pairs we will calculate inventory for.
|
||||
$measure_units_pairs = $this->getMeasureUnitsPairs($asset, $measure, $units);
|
||||
|
||||
// Iterate through the measure+units pairs and build inventory summaries.
|
||||
$inventories = [];
|
||||
foreach ($measure_units_pairs as $pair) {
|
||||
$total = $this->calculateInventory($asset, $pair['measure'], $pair['units']);
|
||||
$inventories[] = [
|
||||
'measure' => $pair['measure'] ? $pair['measure'] : '',
|
||||
'value' => $total->toDecimal(0, TRUE),
|
||||
'units' => $pair['units'] ? $pair['units'] : NULL,
|
||||
];
|
||||
}
|
||||
|
||||
// Return the inventory summaries.
|
||||
return $inventories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for all measure+units inventory pairs of an asset.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The asset we are querying inventory of.
|
||||
* @param string $measure
|
||||
* The quantity measure of the inventory. See quantity_measures().
|
||||
* @param int $units
|
||||
* The quantity units of the inventory (term ID).
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays. Each array will have a 'measure' and 'units' key.
|
||||
*/
|
||||
protected function getMeasureUnitsPairs(AssetInterface $asset, string $measure = '', int $units = 0) {
|
||||
|
||||
// If both a measure and units are provided, that is the only pair.
|
||||
if (!empty($measure) && !empty($units)) {
|
||||
return [
|
||||
[
|
||||
'measure' => $measure,
|
||||
'units' => $units,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Query the database for measure+units pairs.
|
||||
$query = $this->database->select('quantity', 'q');
|
||||
$query->condition('q.inventory_asset', $asset->id());
|
||||
$query->addField('q', 'measure');
|
||||
$query->addField('q', 'units');
|
||||
$query->groupBy('q.measure');
|
||||
$query->groupBy('q.units');
|
||||
|
||||
// Filter by measure or units, if provided.
|
||||
if (!empty($measure)) {
|
||||
$query->condition('q.measure', $measure);
|
||||
}
|
||||
if (!empty($units)) {
|
||||
$query->condition('q.units', $units);
|
||||
}
|
||||
|
||||
// Execute the query and build the array of measure+units pairs.
|
||||
$result = $query->execute();
|
||||
$pairs = [];
|
||||
foreach ($result as $row) {
|
||||
$pairs[] = [
|
||||
'measure' => !empty($row->measure) ? $row->measure : '',
|
||||
'units' => !empty($row->units) ? $row->units : 0,
|
||||
];
|
||||
}
|
||||
return $pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for the latest asset "reset" adjustment timestamp.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The asset we are querying inventory of.
|
||||
* @param string $measure
|
||||
* The quantity measure of the inventory. See quantity_measures().
|
||||
* @param int $units
|
||||
* The quantity units of the inventory (term ID).
|
||||
*
|
||||
* @return int|null
|
||||
* Returns a unix timestamp, or NULL if no "reset" adjustment is available.
|
||||
*/
|
||||
protected function getLatestResetTimestamp(AssetInterface $asset, string $measure = '', int $units = 0) {
|
||||
$query = $this->baseQuery($asset, $measure, $units);
|
||||
$query->condition('q.inventory_adjustment', 'reset');
|
||||
$query->addExpression('MAX(l.timestamp)');
|
||||
return $query->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the inventory of an asset, for a given measure+units pair.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The asset we are querying inventory of.
|
||||
* @param string $measure
|
||||
* The quantity measure of the inventory. See quantity_measures().
|
||||
* @param int $units
|
||||
* The quantity units of the inventory (term ID).
|
||||
*
|
||||
* @return \Drupal\fraction\Fraction
|
||||
* Returns a Fraction object representing the total inventory.
|
||||
*/
|
||||
protected function calculateInventory(AssetInterface $asset, string $measure = '', int $units = 0) {
|
||||
|
||||
// Query the database for inventory adjustments of the given asset,
|
||||
// measure, and units.
|
||||
$adjustments = $this->getAdjustments($asset, $measure, $units);
|
||||
|
||||
// Iterate through the results and calculate the inventory.
|
||||
// This will use fraction math to maintain maximum precision.
|
||||
$total = new Fraction();
|
||||
foreach ($adjustments as $adjustment) {
|
||||
|
||||
// Create a Fraction object from the numerator and denominator.
|
||||
$value = new Fraction($adjustment->numerator, $adjustment->denominator);
|
||||
|
||||
// Reset/increment/decrement the total.
|
||||
switch ($adjustment->type) {
|
||||
|
||||
// Reset.
|
||||
case 'reset':
|
||||
$total = $value;
|
||||
break;
|
||||
|
||||
// Increment.
|
||||
case 'increment':
|
||||
$total->add($value);
|
||||
break;
|
||||
|
||||
// Decrement.
|
||||
case 'decrement':
|
||||
$total->subtract($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for all inventory adjustments of an asset.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The asset we are querying inventory of.
|
||||
* @param string $measure
|
||||
* The quantity measure of the inventory. See quantity_measures().
|
||||
* @param int $units
|
||||
* The quantity units of the inventory (term ID).
|
||||
*
|
||||
* @return array
|
||||
* An array of objects with the following properties: type (reset,
|
||||
* increment, or decrement), numerator, and denominator.
|
||||
*/
|
||||
protected function getAdjustments(AssetInterface $asset, string $measure = '', int $units = 0) {
|
||||
|
||||
// First, query the database to find the timestamp of the most recent
|
||||
// "reset" adjustment log for this asset (if available).
|
||||
$latest_reset = $this->getLatestResetTimestamp($asset, $measure, $units);
|
||||
|
||||
// Then, query the database for all inventory adjustments.
|
||||
$query = $this->baseQuery($asset, $measure, $units);
|
||||
$query->addField('q', 'inventory_adjustment', 'type');
|
||||
$query->addField('q', 'value__numerator', 'numerator');
|
||||
$query->addField('q', 'value__denominator', 'denominator');
|
||||
$query->condition('q.inventory_adjustment', NULL, 'IS NOT NULL');
|
||||
|
||||
// Sort by log timestamp and then ID, ascending.
|
||||
$query->orderBy('l.timestamp', 'ASC');
|
||||
$query->orderBy('l.id', 'ASC');
|
||||
|
||||
// Filter to logs that happened after the the latest reset, if available.
|
||||
if (!empty($latest_reset)) {
|
||||
$query->condition('l.timestamp', $latest_reset, '>=');
|
||||
}
|
||||
|
||||
// Execute the query and return the results.
|
||||
return $query->execute()->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a base query for getting asset inventory adjustments.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The asset we are querying inventory of.
|
||||
* @param string $measure
|
||||
* The quantity measure of the inventory. See quantity_measures().
|
||||
* @param int $units
|
||||
* The quantity units of the inventory (term ID).
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\SelectInterface
|
||||
* A database query object.
|
||||
*/
|
||||
protected function baseQuery(AssetInterface $asset, string $measure = '', int $units = 0) {
|
||||
|
||||
// Start with a query of the quantity base table.
|
||||
$query = $this->database->select('quantity', 'q');
|
||||
|
||||
// Only include adjustments that reference the asset.
|
||||
$query->condition('q.inventory_asset', $asset->id());
|
||||
|
||||
// Filter by measure and units. If either is empty, then explicitly filter
|
||||
// to only include rows with NULL values.
|
||||
if (!empty($measure)) {
|
||||
$query->condition('q.measure', $measure);
|
||||
}
|
||||
else {
|
||||
$query->condition('q.measure', NULL, 'IS NULL');
|
||||
}
|
||||
if (!empty($units)) {
|
||||
$query->condition('q.units', $units);
|
||||
}
|
||||
else {
|
||||
$query->condition('q.units', NULL, 'IS NULL');
|
||||
}
|
||||
|
||||
// Join the {log_field_data} table (via reverse reference through
|
||||
// the {log__quantity} table).
|
||||
$query->join('log__quantity', 'lq', 'q.id = lq.quantity_target_id');
|
||||
$query->join('log_field_data', 'l', 'lq.entity_id = l.id');
|
||||
|
||||
// Filter out logs that are not done.
|
||||
$query->condition('l.status', 'done');
|
||||
|
||||
// Filter out future logs.
|
||||
$query->condition('l.timestamp', $this->time->getRequestTime(), '<=');
|
||||
|
||||
// Return the query.
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\farm_inventory;
|
||||
|
||||
use Drupal\asset\Entity\AssetInterface;
|
||||
|
||||
/**
|
||||
* Asset inventory logic.
|
||||
*/
|
||||
interface AssetInventoryInterface {
|
||||
|
||||
/**
|
||||
* Get inventory summaries for an asset.
|
||||
*
|
||||
* @param \Drupal\asset\Entity\AssetInterface $asset
|
||||
* The Asset entity.
|
||||
* @param string $measure
|
||||
* The quantity measure of the inventory. See quantity_measures().
|
||||
* @param int $units
|
||||
* The quantity units of the inventory (term ID).
|
||||
*
|
||||
* @return array
|
||||
* Returns an array of asset inventory information.
|
||||
*/
|
||||
public function getInventory(AssetInterface $asset, string $measure = '', int $units = 0): array;
|
||||
|
||||
}
|
Loading…
Reference in New Issue