mirror of
https://github.com/farmOS/farmOS.git
synced 2024-02-23 11:37:38 +01:00
600 lines
18 KiB
Text
600 lines
18 KiB
Text
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Farm inventory module.
|
|
*/
|
|
|
|
include_once 'farm_inventory.features.inc';
|
|
|
|
/**
|
|
* Implements hook_restws_field_collection_info().
|
|
*/
|
|
function farm_inventory_restws_field_collection_info() {
|
|
return array(
|
|
'field_farm_inventory' => array(
|
|
'alias' => 'inventory',
|
|
'label' => t('Inventory'),
|
|
'multiple' => TRUE,
|
|
'fields' => array(
|
|
'asset' => array(
|
|
'field_name' => 'field_farm_inventory_asset',
|
|
'field_label' => t('Asset'),
|
|
'field_type' => 'farm_asset',
|
|
'field_value' => 'target_id',
|
|
),
|
|
'value' => array(
|
|
'field_name' => 'field_farm_inventory_value',
|
|
'field_label' => t('Value'),
|
|
'field_type' => 'decimal',
|
|
'field_value' => 'decimal',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_farm_ui_entity_views().
|
|
*/
|
|
function farm_inventory_farm_ui_entity_views($entity_type, $bundle, $entity) {
|
|
$views = array();
|
|
|
|
// Add inventory adjustments View at the bottom of assets.
|
|
if ($entity_type == 'farm_asset') {
|
|
$views[] = array(
|
|
'name' => 'farm_inventory_log',
|
|
'group' => 'logs_special',
|
|
'weight' => 100,
|
|
);
|
|
}
|
|
|
|
return $views;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_view_alter().
|
|
*/
|
|
function farm_inventory_entity_view_alter(&$build, $type) {
|
|
|
|
// If it's not a farm_asset, or if the entity object is not available, bail.
|
|
if ($type != 'farm_asset' || empty($build['#entity'])) {
|
|
return;
|
|
}
|
|
|
|
// Alias the asset variable.
|
|
$asset = $build['#entity'];
|
|
|
|
// If inventory management is not enabled for this asset, bail.
|
|
if (!farm_inventory_enabled($asset)) {
|
|
return;
|
|
}
|
|
|
|
// Get the asset's inventory.
|
|
$inventory = farm_inventory($asset);
|
|
|
|
// If the inventory is an empty string, and the asset is treated as an
|
|
// individual, then set the inventory to 1.
|
|
if ($inventory == '' && farm_inventory_individual($asset)) {
|
|
$inventory = '1';
|
|
}
|
|
|
|
// Build the inventory display.
|
|
$output = '<strong>' . t('Inventory') . ':</strong> ' . $inventory;
|
|
|
|
// Add it to the build array.
|
|
$build['inventory'] = array(
|
|
'#markup' => $output,
|
|
'#prefix' => '<div class="inventory">',
|
|
'#suffix' => '</div>',
|
|
'#weight' => -120,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_load().
|
|
*/
|
|
function farm_inventory_entity_load($entities, $type) {
|
|
|
|
// Only act on farm_asset_type entities.
|
|
if ($type != 'farm_asset_type') {
|
|
return;
|
|
}
|
|
|
|
// Load asset type inventory settings.
|
|
$settings = array();
|
|
$result = db_query('SELECT * FROM {farm_inventory_asset_type}');
|
|
foreach ($result as $row) {
|
|
if (!empty($row->type)) {
|
|
$settings[$row->type] = array(
|
|
'enabled' => $row->enabled,
|
|
'individual' => $row->individual,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Iterate through the entities and add inventory settings.
|
|
foreach ($entities as $entity) {
|
|
|
|
// Get the asset type machine name.
|
|
$asset_type = $entity->type;
|
|
|
|
// If settings are available for the entity's bundle, add them.
|
|
if (!empty($settings[$asset_type])) {
|
|
$entity->inventory = $settings[$asset_type];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_insert().
|
|
*/
|
|
function farm_inventory_entity_insert($entity, $type) {
|
|
|
|
// Only act on farm_asset_type entities.
|
|
if ($type != 'farm_asset_type') {
|
|
return;
|
|
}
|
|
|
|
// Save asset type inventory settings.
|
|
_farm_inventory_asset_type_settings_save($entity);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_update().
|
|
*/
|
|
function farm_inventory_entity_update($entity, $type) {
|
|
|
|
// Only act on farm_asset_type entities.
|
|
if ($type != 'farm_asset_type') {
|
|
return;
|
|
}
|
|
|
|
// Save asset type inventory settings.
|
|
_farm_inventory_asset_type_settings_save($entity);
|
|
}
|
|
|
|
/**
|
|
* Helper function for saving asset type inventory settings when an asset type
|
|
* is inserted or updated.
|
|
*
|
|
* @param FarmAssetType $asset_type
|
|
* A farm asset type entity.
|
|
*/
|
|
function _farm_inventory_asset_type_settings_save($asset_type) {
|
|
|
|
// If the machine name is not set, bail.
|
|
if (empty($asset_type->type)) {
|
|
return;
|
|
}
|
|
|
|
// If inventory settings are provided, save them.
|
|
if (!empty($asset_type->inventory)) {
|
|
|
|
// First, delete existing settings.
|
|
_farm_inventory_asset_type_settings_delete($asset_type->type);
|
|
|
|
// Then, save new settings.
|
|
$record = array('type' => $asset_type->type);
|
|
$record = array_merge($record, $asset_type->inventory);
|
|
drupal_write_record('farm_inventory_asset_type', $record);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_delete().
|
|
*/
|
|
function farm_inventory_entity_delete($entity, $type) {
|
|
|
|
// Only act on farm_asset_type entities.
|
|
if ($type != 'farm_asset_type') {
|
|
return;
|
|
}
|
|
|
|
// If the asset machine name is not set, bail.
|
|
if (empty($entity->type)) {
|
|
return;
|
|
}
|
|
|
|
// Delete settings for this asset type.
|
|
_farm_inventory_asset_type_settings_delete($entity->type);
|
|
}
|
|
|
|
/**
|
|
* Helper function for deleting asset type inventory settings.
|
|
*
|
|
* @param string $type
|
|
* The asset type machine name.
|
|
*/
|
|
function _farm_inventory_asset_type_settings_delete($type) {
|
|
db_query('DELETE FROM {farm_inventory_asset_type} WHERE type = :type', array(':type' => $type));
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter().
|
|
*/
|
|
function farm_inventory_form_farm_asset_type_form_alter(&$form, &$form_state, $form_id) {
|
|
|
|
// Get the asset type machine name, if available.
|
|
$type = '';
|
|
if (!empty($form['type']['#default_value'])) {
|
|
$type = $form['type']['#default_value'];
|
|
}
|
|
|
|
// Load existing settings for this asset type.
|
|
$settings = array();
|
|
if (!empty($type)) {
|
|
$settings = db_query('SELECT * FROM {farm_inventory_asset_type} WHERE type = :type', array(':type' => $type))->fetchAssoc();
|
|
}
|
|
|
|
// Add inventory configuration fieldset for the asset type.
|
|
$form['inventory'] = array(
|
|
'#type' => 'fieldset',
|
|
'#title' => t('Inventory'),
|
|
'#description' => t('Configure inventory management options for this asset type.'),
|
|
'#tree' => TRUE,
|
|
);
|
|
|
|
// Enable inventory.
|
|
$form['inventory']['enabled'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('Enable inventory tracking'),
|
|
'#default_value' => !empty($settings['enabled']) ? TRUE : FALSE,
|
|
);
|
|
|
|
// Define the default inventory value.
|
|
$form['inventory']['individual'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('Assets are individuals'),
|
|
'#description' => t('If this is enabled, assets that have no inventory adjustments will be assumed to be individuals, and will have a default inventory of 1. If this is disabled, the default inventory for assets of this type will be zero.'),
|
|
'#default_value' => !empty($settings['individual']) ? TRUE : FALSE,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter().
|
|
*/
|
|
function farm_inventory_form_farm_asset_form_alter(&$form, &$form_state, $form_id) {
|
|
|
|
// Get the farm asset entity from the form.
|
|
$asset = $form['farm_asset']['#value'];
|
|
|
|
// If inventory is not enabled for this asset, bail.
|
|
if (!farm_inventory_enabled($asset)) {
|
|
return;
|
|
}
|
|
|
|
// Get the asset's current inventory.
|
|
$inventory = farm_inventory($asset);
|
|
|
|
// Add a field for setting the asset's current inventory.
|
|
$form['inventory'] = array(
|
|
'#type' => 'fieldset',
|
|
'#title' => t('Inventory'),
|
|
'#description' => t('Set the current inventory level for this asset. An observation log will be created automatically that adjusts the inventory level to match this value.'),
|
|
'#collapsible' => TRUE,
|
|
'#collapsed' => TRUE,
|
|
'#weight' => 100,
|
|
);
|
|
$form['inventory']['inventory'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('Current inventory'),
|
|
'#default_value' => $inventory,
|
|
);
|
|
$form['actions']['submit']['#submit'][] = 'farm_inventory_asset_form_submit';
|
|
$form['#group_children']['inventory'] = 'group_farm_general';
|
|
}
|
|
|
|
/**
|
|
* Submit handler for processing the asset inventory field.
|
|
*
|
|
* @param array $form
|
|
* The form array.
|
|
* @param array $form_state
|
|
* The form state array.
|
|
*/
|
|
function farm_inventory_asset_form_submit(array $form, array &$form_state) {
|
|
|
|
// Only proceed if inventory has a value.
|
|
if (empty($form_state['values']['inventory'])) {
|
|
return;
|
|
}
|
|
|
|
// Only proceed if the value is not the default value.
|
|
if ($form_state['values']['inventory'] == $form['inventory']['inventory']['#default_value']) {
|
|
return;
|
|
}
|
|
|
|
// If an asset doesn't exist, bail.
|
|
if (empty($form_state['values']['farm_asset'])) {
|
|
return;
|
|
}
|
|
|
|
// Grab the asset.
|
|
$asset = $form_state['values']['farm_asset'];
|
|
|
|
// Create an observation log to set the inventory.
|
|
farm_inventory_set($asset, $form_state['values']['inventory']);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of asset types that have inventory enabled.
|
|
*
|
|
* @return array
|
|
* Returns an array of asset type machine names.
|
|
*/
|
|
function farm_inventory_asset_types() {
|
|
$asset_types = array();
|
|
$result = db_query('SELECT type FROM {farm_inventory_asset_type} WHERE enabled = 1');
|
|
foreach ($result as $row) {
|
|
if (!empty($row->type)) {
|
|
$asset_types[] = $row->type;
|
|
}
|
|
}
|
|
return $asset_types;
|
|
}
|
|
|
|
/**
|
|
* Check whether or not inventory management is enabled on an asset.
|
|
*
|
|
* @param FarmAsset $asset
|
|
* The asset to check.
|
|
*
|
|
* @return bool
|
|
* Returns TRUE or FALSE.
|
|
*/
|
|
function farm_inventory_enabled(FarmAsset $asset) {
|
|
|
|
// If the asset type is not set, bail.
|
|
if (empty($asset->type)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Check the database to see if inventory management is enabled.
|
|
$result = db_query('SELECT enabled FROM {farm_inventory_asset_type} WHERE type = :type', array(':type' => $asset->type))->fetchField();
|
|
|
|
// Return TRUE or FALSE.
|
|
if (!empty($result)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Check whether or not an asset is treated as an individual by default.
|
|
*
|
|
* @param FarmAsset $asset
|
|
* The asset to check.
|
|
*
|
|
* @return bool
|
|
* Returns TRUE or FALSE.
|
|
*/
|
|
function farm_inventory_individual(FarmAsset $asset) {
|
|
|
|
// If the asset type is not set, bail.
|
|
if (empty($asset->type)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Check the database to see if the asset is treated as an individual.
|
|
$result = db_query('SELECT individual FROM {farm_inventory_asset_type} WHERE type = :type', array(':type' => $asset->type))->fetchField();
|
|
|
|
// Return TRUE or FALSE.
|
|
if (!empty($result)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Calculate an asset's inventory level.
|
|
*
|
|
* @param FarmAsset $asset
|
|
* The farm_asset object to calculate inventory for.
|
|
* @param int $time
|
|
* Unix timestamp limiter. Only logs before this time will be included.
|
|
* Defaults to the current time. Set to 0 to load the absolute last.
|
|
* @param $done
|
|
* Whether or not to only show logs that are marked as "done". TRUE will limit
|
|
* to logs that are done, and FALSE will limit to logs that are not done. If
|
|
* any other value is used, no filtering will be applied. Defaults to TRUE.
|
|
*
|
|
* @return string
|
|
* Returns the asset's inventory as a string. If no inventory adjustments
|
|
* exist for the asset, an empty string will be returned.
|
|
*/
|
|
function farm_inventory(FarmAsset $asset, $time = REQUEST_TIME, $done = TRUE) {
|
|
|
|
// If the asset doesn't have an ID (for instance if it is new and hasn't been
|
|
// saved yet), bail.
|
|
if (empty($asset->id)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Make a query for calculating the inventory.
|
|
$query = farm_inventory_query($asset->id, $time, $done);
|
|
|
|
// Execute the query and gather the inventory result.
|
|
$result = $query->execute();
|
|
$inventory = $result->fetchField();
|
|
|
|
// If there are no inventory adjustments, return an empty string. Note that
|
|
// we use is_null($inventory) instead of empty($inventory), because empty()
|
|
// would be TRUE if the inventory is set to 0.
|
|
if (is_null($inventory)) {
|
|
return '';
|
|
}
|
|
|
|
// Return the formatted inventory.
|
|
return farm_inventory_format($inventory);
|
|
}
|
|
|
|
/**
|
|
* Format an inventory value.
|
|
*
|
|
* @param string $inventory
|
|
* The inventory value to format.
|
|
*
|
|
* @return string
|
|
* The formatted inventory string.
|
|
*/
|
|
function farm_inventory_format($inventory) {
|
|
|
|
// Add zero (to remove trailing zeroes).
|
|
// See https://stackoverflow.com/questions/14531679/remove-useless-zero-digits-from-decimals-in-php
|
|
$inventory += 0;
|
|
|
|
// Convert to a string.
|
|
$inventory = (string) $inventory;
|
|
|
|
// Return the inventory.
|
|
return $inventory;
|
|
}
|
|
|
|
/**
|
|
* Build a query to calculate an asset's inventory level.
|
|
*
|
|
* @param int|string $asset_id
|
|
* The asset id to search for. This can either be a specific id, or a field
|
|
* alias string from another query (ie: 'mytable.assetid'). For an example
|
|
* of field alias string usage, see the Views field handler code in
|
|
* farm_inventory_handler_field_asset_inventory_value::query().
|
|
* @param int $time
|
|
* Unix timestamp limiter. Only logs before this time will be included.
|
|
* Defaults to the current time. Set to 0 to load the absolute last.
|
|
* @param $done
|
|
* Whether or not to only show logs that are marked as "done". TRUE will limit
|
|
* to logs that are done, and FALSE will limit to logs that are not done. If
|
|
* any other value is used, no filtering will be applied. Defaults to TRUE.
|
|
*
|
|
* @return \SelectQuery
|
|
* Returns a SelectQuery object.
|
|
*/
|
|
function farm_inventory_query($asset_id, $time = REQUEST_TIME, $done = TRUE) {
|
|
|
|
/**
|
|
* Please read the comments in farm_log_query() to understand how this works,
|
|
* and to be aware of the limitations and responsibilities we have in this
|
|
* function with regard to sanitizing query inputs.
|
|
*/
|
|
|
|
// Ensure $asset_id is valid, because it will be used directly in the query
|
|
// string. This is defensive code. See note about farm_log_query() above.
|
|
if (!is_numeric($asset_id) || $asset_id < 0) {
|
|
$asset_id = db_escape_field($asset_id);
|
|
}
|
|
|
|
// Use the farm_log_asset_query() helper function to start a query object.
|
|
$query = farm_log_query($time, $done);
|
|
|
|
// Add a query tag to identify where this came from.
|
|
$query->addTag('farm_inventory_query');
|
|
|
|
// Join in the Inventory field collection. Use an inner join to exclude logs
|
|
// that do not have an inventory field collection attached.
|
|
$query->innerJoin('field_data_field_farm_inventory', 'ss_fdffi', "ss_fdffi.entity_type = 'log' AND ss_fdffi.entity_id = ss_log.id AND ss_fdffi.deleted = 0");
|
|
|
|
// Join in the inventory adjustment asset and filter to only include
|
|
// inventory adjustments that reference the specified asset. Use an inner
|
|
// join to exclude logs that do not have an inventory asset reference.
|
|
$query->innerJoin('field_data_field_farm_inventory_asset', 'ss_fdffia', "ss_fdffia.entity_id = ss_fdffi.field_farm_inventory_value AND ss_fdffia.deleted = 0");
|
|
$query->where('ss_fdffia.field_farm_inventory_asset_target_id = ' . $asset_id);
|
|
|
|
// Join in the inventory adjustment value. Use an inner join to exclude logs
|
|
// that do not have an inventory adjustment value.
|
|
$query->innerJoin('field_data_field_farm_inventory_value', 'ss_fdffiv', "ss_fdffiv.entity_id = ss_fdffi.field_farm_inventory_value AND ss_fdffiv.deleted = 0");
|
|
|
|
// Add an expression that calculates the SUM of all values.
|
|
$query->addExpression('SUM(ss_fdffiv.field_farm_inventory_value_numerator / ss_fdffiv.field_farm_inventory_value_denominator)', 'inventory');
|
|
|
|
// Remove the order by fields that were added by default in farm_log_query().
|
|
// Since we are only adding an expression, and no other fields, we can't have
|
|
// any order by fields, otherwise proper databases (like PostgreSQL) require
|
|
// the order by fields to also be included in an aggregate function or in a
|
|
// group by clause.
|
|
$order_by_fields = &$query->getOrderBy();
|
|
$order_by_fields = array();
|
|
|
|
// Return the query object.
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Create a log for adjusting asset inventory.
|
|
*
|
|
* @param FarmAsset $asset
|
|
* The asset to adjust.
|
|
* @param string $inventory
|
|
* The new asset inventory value.
|
|
* @param int $timestamp
|
|
* The timestamp of the inventory adjustment. Defaults to the current time.
|
|
* @param string $log_type
|
|
* The type of log to create. Defaults to "farm_observation".
|
|
* @param bool $done
|
|
* Boolean indicating whether or not the log should be marked "done".
|
|
* Defaults to TRUE.
|
|
*
|
|
* @return \Log
|
|
* Returns the log that was created.
|
|
*/
|
|
function farm_inventory_set($asset, $inventory, $timestamp = REQUEST_TIME, $log_type = 'farm_observation', $done = TRUE) {
|
|
|
|
// If the asset type does not have inventory enabled, bail with an error.
|
|
if (!farm_inventory_enabled($asset)) {
|
|
drupal_set_message(t('An inventory log was not created for @asset because inventory is not enabled for that asset type.', array('@asset' => entity_label('farm_asset', $asset))), 'error');
|
|
return;
|
|
}
|
|
|
|
// Load the asset's current inventory level (as a fraction).
|
|
$current_inventory = farm_inventory($asset);
|
|
|
|
// Convert the current inventory to a fraction.
|
|
$current_inventory_fraction = fraction_from_decimal($current_inventory);
|
|
|
|
// Convert the new inventory to a fraction.
|
|
$inventory_fraction = fraction_from_decimal($inventory);
|
|
|
|
// Subtract the current inventory from the new inventory to figure out the
|
|
// necessary adjustment value.
|
|
$value_fraction = $inventory_fraction->subtract($current_inventory_fraction);
|
|
|
|
// Get the numerator and denominator.
|
|
$numerator = $value_fraction->getNumerator();
|
|
$denominator = $value_fraction->getDenominator();
|
|
|
|
// If there is no value difference, bail.
|
|
if (empty($numerator) || empty($denominator)) {
|
|
return;
|
|
}
|
|
|
|
// If the log is an observation, set the name to:
|
|
// "Inventory of [assets] is [inventory]".
|
|
$log_name = '';
|
|
if ($log_type == 'farm_observation') {
|
|
$assets_summary = farm_log_entity_label_summary('farm_asset', $asset);
|
|
$log_name = t('Inventory of !assets is !inventory', array('!assets' => $assets_summary, '!inventory' => $inventory));
|
|
}
|
|
|
|
// Create a new farm log entity.
|
|
$log = farm_log_create($log_type, $log_name, $timestamp, $done);
|
|
|
|
// Create a new inventory field_collection entity attached to the log.
|
|
$adjustment = entity_create('field_collection_item', array('field_name' => 'field_farm_inventory'));
|
|
$adjustment->setHostEntity('log', $log);
|
|
|
|
// Create an entity wrapper for the adjustment.
|
|
$adjustment_wrapper = entity_metadata_wrapper('field_collection_item', $adjustment);
|
|
|
|
// Set the adjustment asset.
|
|
$adjustment_wrapper->field_farm_inventory_asset = $asset;
|
|
|
|
// Set the adjustment value (fraction numerator and denominator).
|
|
$adjustment_wrapper->field_farm_inventory_value->numerator->set($numerator);
|
|
$adjustment_wrapper->field_farm_inventory_value->denominator->set($denominator);
|
|
|
|
// Save the adjustment.
|
|
$adjustment_wrapper->save();
|
|
|
|
// Return the log.
|
|
return $log;
|
|
}
|