Compare commits
37 Commits
4abd72737f
...
a09ef42add
Author | SHA1 | Date |
---|---|---|
Michael Stenta | a09ef42add | |
Michael Stenta | 6879ff591f | |
Michael Stenta | ce8173f152 | |
Michael Stenta | f5c7139a63 | |
Michael Stenta | a4544a7ecb | |
Michael Stenta | 1cb57c2178 | |
Michael Stenta | 0467595a0a | |
Michael Stenta | ca5298245a | |
Michael Stenta | b074a635c9 | |
Michael Stenta | 5cbd753905 | |
Michael Stenta | 74f3ee4b5d | |
Michael Stenta | a37820e514 | |
Michael Stenta | 31cd1aa177 | |
Michael Stenta | 220c99c510 | |
Paul Weidner | 5a49dce4f5 | |
Michael Stenta | 84421be197 | |
Michael Stenta | 74ffac59ec | |
Michael Stenta | 1c404b5152 | |
Michael Stenta | 755e07bfdf | |
Michael Stenta | 6c03557b3c | |
Michael Stenta | 7329866866 | |
Michael Stenta | 11b256b33a | |
Michael Stenta | 004ecd8e9e | |
Michael Stenta | 2a992f5a7c | |
Michael Stenta | f1c6a47846 | |
Michael Stenta | 31c5280662 | |
Michael Stenta | 8091b51561 | |
Michael Stenta | ca3bc10c7d | |
Michael Stenta | 83b15287ba | |
Michael Stenta | 8f73c593a1 | |
Paul Weidner | 17b792aca7 | |
Michael Stenta | 68ae993c41 | |
Michael Stenta | 5f09c940c1 | |
wotnak | 8c1fd8fe11 | |
wotnak | 8ce641adc5 | |
Michael Stenta | df0c25f047 | |
Michael Stenta | 1d6804d7b4 |
|
@ -92,10 +92,13 @@ jobs:
|
|||
include:
|
||||
- dbms: pgsql
|
||||
DB_URL: pgsql://farm:farm@db/farm
|
||||
processes: auto
|
||||
- dbms: mariadb
|
||||
DB_URL: mysql://farm:farm@db/farm
|
||||
processes: auto
|
||||
- dbms: sqlite
|
||||
DB_URL: sqlite://localhost/sites/default/files/db.sqlite
|
||||
processes: 1
|
||||
steps:
|
||||
- name: Print test matrix variables
|
||||
run: echo "matrix.dbms=${{ matrix.dbms }}, matrix.DB_URL=${{ matrix.DB_URL }}"
|
||||
|
@ -122,8 +125,12 @@ jobs:
|
|||
# The www-container-fs-ready file is only created once we expect the containers to be online
|
||||
# so waiting for that lets us know it is safe to start the tests
|
||||
run: until [ -f ./www/www-container-fs-ready ]; do sleep 0.1; done
|
||||
- name: Install pg_trgm PostgreSQL extension
|
||||
# This avoids race conditions when trying to automatically install it in concurrently run tests.
|
||||
if: matrix.dbms == 'pgsql'
|
||||
run: docker compose exec -T db psql -U farm -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'
|
||||
- name: Run PHPUnit tests
|
||||
run: docker compose exec -u www-data -T www phpunit --verbose /opt/drupal/web/profiles/farm
|
||||
run: docker compose exec -u www-data -T www paratest -vv --processes=${{ matrix.processes }} /opt/drupal/web/profiles/farm
|
||||
- name: Test Drush site install with all modules
|
||||
run: docker compose exec -u www-data -T www drush site-install --db-url=${{ matrix.DB_URL }} farm farm.modules='all'
|
||||
release:
|
||||
|
@ -215,8 +222,8 @@ jobs:
|
|||
fi
|
||||
outputs:
|
||||
announce: ${{ env.ANNOUNCE_RELEASE }}
|
||||
announce:
|
||||
name: Announce new release
|
||||
announce-microblog:
|
||||
name: Announce new release on farmOS-microblog
|
||||
if: needs.publish.outputs.announce
|
||||
needs:
|
||||
- build
|
||||
|
@ -227,3 +234,25 @@ jobs:
|
|||
message: '#farmOS ${{ needs.build.outputs.farmos_version }} has been released! https://github.com/farmOS/farmOS/releases/${{ needs.build.outputs.farmos_version }}'
|
||||
secrets:
|
||||
MICROBLOG_DEPLOY_KEY: ${{ secrets.MICROBLOG_DEPLOY_KEY }}
|
||||
announce-discourse:
|
||||
name: Announce new release on farmOS.discourse.group
|
||||
if: needs.publish.outputs.announce
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- release
|
||||
- publish
|
||||
steps:
|
||||
- name: Discourse API request
|
||||
env:
|
||||
DISCOURSE_API_KEY: ${{ secrets.DISCOURSE_API_KEY }}
|
||||
run: |
|
||||
curl --fail-with-body -X POST "https://farmos.discourse.group/posts/" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Api-Key: ${DISCOURSE_API_KEY}" \
|
||||
-H "Api-Username: mstenta" \
|
||||
-d '{
|
||||
"title": "farmOS ${{ needs.build.outputs.farmos_version }} has been released",
|
||||
"raw": "farmOS [${{ needs.build.outputs.farmos_version }}](https://github.com/farmOS/farmOS/releases/${{ needs.build.outputs.farmos_version }}) has been released.\n\nFor the full release notes, see [CHANGELOG.md](https://github.com/farmOS/farmOS/blob/${{ needs.build.outputs.farmos_version }}/CHANGELOG.md).",
|
||||
"category": 7
|
||||
}'
|
||||
|
|
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- [Announce new releases on farmOS.discourse.group #780](https://github.com/farmOS/farmOS/pull/780)
|
||||
- [Add a Product asset type and Product type taxonomy #787](https://github.com/farmOS/farmOS/pull/787)
|
||||
- [Inventory quick form #766](https://github.com/farmOS/farmOS/pull/766)
|
||||
|
||||
### Changed
|
||||
|
||||
- [Recommend running composer update twice #653](https://github.com/farmOS/farmOS/pull/786)
|
||||
|
||||
## [3.0.1] 2024-01-18
|
||||
|
||||
### Added
|
||||
|
||||
- [Add min/max options to integer fields in farm_field.factory #768](https://github.com/farmOS/farmOS/pull/768)
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow users with asset view access to see /asset/%id/locations.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Patch drupal/core to fix Issue #3414883: datetime_timestamp widget does not use default field value #771](https://github.com/farmOS/farmOS/pull/771)
|
||||
- [Fix duplicated revision tab on entities #773](https://github.com/farmOS/farmOS/pull/773)
|
||||
- Improve access checking on location hierarchy forms.
|
||||
|
||||
## [3.0.0] 2024-01-05
|
||||
|
||||
This is the first "stable" release of farmOS v3. See the release notes for
|
||||
|
@ -662,7 +688,8 @@ moving forward.
|
|||
Drupal 7, which required a complete refactor of the codebase. By comparison,
|
||||
updating from Drupal 9 to 10 will simply involve updating deprecated code.
|
||||
|
||||
[Unreleased]: https://github.com/farmOS/farmOS/compare/3.0.0...HEAD
|
||||
[Unreleased]: https://github.com/farmOS/farmOS/compare/3.0.1...HEAD
|
||||
[3.0.1]: https://github.com/farmOS/farmOS/releases/tag/3.0.1
|
||||
[3.0.0]: https://github.com/farmOS/farmOS/releases/tag/3.0.0
|
||||
[3.0.0-beta3]: https://github.com/farmOS/farmOS/releases/tag/3.0.0-beta3
|
||||
[3.0.0-beta2]: https://github.com/farmOS/farmOS/releases/tag/3.0.0-beta2
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"require": {
|
||||
"cweagans/composer-patches": "^1.6",
|
||||
"drupal/admin_toolbar": "^3.3",
|
||||
"drupal/core": "10.2.1",
|
||||
"drupal/core": "10.2.2",
|
||||
"drupal/config_update": "^2.0@alpha",
|
||||
"drupal/consumers": "^1.15",
|
||||
"drupal/csv_serialization": "^4.0",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"drupal/entity": "1.4",
|
||||
"drupal/entity_browser": "^2.10",
|
||||
"drupal/entity_reference_integrity": "^1.1",
|
||||
"drupal/entity_reference_revisions": "1.10",
|
||||
"drupal/entity_reference_revisions": "1.11",
|
||||
"drupal/entity_reference_validators": "^1.0@beta",
|
||||
"drupal/exif_orientation": "^1.2",
|
||||
"drupal/fraction": "^2.3.1",
|
||||
|
@ -61,7 +61,8 @@
|
|||
"Issue #2429699: Add Views EntityReference filter to be available for all entity reference fields.": "https://www.drupal.org/files/issues/2021-12-02/2429699-453-9.3.x.patch",
|
||||
"Issue #2339235: Remove taxonomy hard dependency on node module": "https://www.drupal.org/files/issues/2024-01-03/2339235-10.2.x-82.patch",
|
||||
"Issue #1874838: Allow exposed blocks to use 'Link display' settings": "https://www.drupal.org/files/issues/2021-11-11/1874838-26.patch",
|
||||
"Issue #2909128: Autocomplete not working on Chrome Android": "https://www.drupal.org/files/issues/2023-07-11/2909128-92.patch"
|
||||
"Issue #2909128: Autocomplete not working on Chrome Android": "https://www.drupal.org/files/issues/2023-07-11/2909128-92.patch",
|
||||
"Issue #3414883: Datetime_timestamp widget does not use default field value": "https://www.drupal.org/files/issues/2024-01-15/3414883-11.patch"
|
||||
},
|
||||
"drupal/entity": {
|
||||
"Issue #3206703: Provide reverse relationships for bundle plugin entity_reference fields.": "https://www.drupal.org/files/issues/2022-05-11/3206703-10.patch"
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"require": {
|
||||
"cweagans/composer-patches": "^1.7",
|
||||
"drupal/core-composer-scaffold": "10.2.1"
|
||||
"drupal/core-composer-scaffold": "10.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"behat/mink": "^1.10",
|
||||
"behat/mink-browserkit-driver": "^2.1",
|
||||
"behat/mink-selenium2-driver": "^1.6",
|
||||
"brianium/paratest": "^6",
|
||||
"drupal/coder": "^8.3",
|
||||
"mglaman/phpstan-drupal": "^1.1",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
Follow these instructions to set up a local farmOS development environment.
|
||||
|
||||
The only requirements are [Docker](https://www.docker.com) and
|
||||
[Docker Compose](https://docs.docker.com/compose).
|
||||
The only requirement is [Docker](https://www.docker.com).
|
||||
|
||||
## 1. Set up Docker containers
|
||||
|
||||
|
|
|
@ -244,9 +244,11 @@ To make an existing quick form configurable:
|
|||
assigns configuration to `$this->configuration`.
|
||||
6. Add a `config/schema/[mymodule].schema.yml` file that describes the
|
||||
[configuration schema/metatdata](https://www.drupal.org/docs/drupal-apis/configuration-api/configuration-schemametadata).
|
||||
7. Add `'#default_value' => $this->configuration['...']` lines to the form
|
||||
elements that are configurable in the `buildForm()` method.
|
||||
|
||||
The following is the same "Harvest" example as above, with the new interface
|
||||
and methods, followed by the schema file that describes the settings.
|
||||
The following is the same "Harvest" example as above, with the changes described
|
||||
above, followed by the schema file that describes the settings.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
|
|
@ -142,6 +142,8 @@ Both methods expect an array of field definition options. These include:
|
|||
- `integer` - Integer number. Additional options:
|
||||
- `size` (optional) - The integer database column size (`tiny`,
|
||||
`small`, `medium`, `normal`, or `big`). Defaults to `normal`.
|
||||
- `min` (optional) - The minimum value.
|
||||
- `max` (optional) - The maximum value.
|
||||
- `list_string` - Select list with allowed values. Additional options:
|
||||
- `allowed_values` - An associative array of allowed values.
|
||||
- `allowed_values_function` - The name of a function that returns an
|
||||
|
|
|
@ -8,7 +8,7 @@ Roles can be "managed" or "unmanaged." The permissions of managed roles are
|
|||
controlled by modules and cannot be modified through the UI. Unmanaged roles
|
||||
can be added/edited through the UI.
|
||||
|
||||
Three managed roles are provided with farmOS:
|
||||
Three default managed roles are provided with farmOS:
|
||||
|
||||
- **Manager** - Has access to everything in farmOS. They can create, edit, and
|
||||
delete records, and they can change configuration settings.
|
||||
|
@ -22,6 +22,12 @@ Three managed roles are provided with farmOS:
|
|||
|
||||
These roles can be disabled by uninstalling the "Default Roles" module.
|
||||
|
||||
The "farmOS Account Admin Role" module provides another optional managed role
|
||||
called **Account Admin**, which has permission to add/edit/remove other users.
|
||||
This is useful in situations where an instance administrator wants to give
|
||||
someone the ability to set up other accounts, without giving them full admin
|
||||
access.
|
||||
|
||||
Permissions for managed roles cannot be modified through the UI. This is not
|
||||
generally an issue since the provided roles have been carefully tailored to
|
||||
work for most applications. In some cases, you may want to further customize
|
||||
|
|
|
@ -171,6 +171,11 @@ farmOS instance via the web UI or by running `drush en mymodule`.
|
|||
Composer provides a simple way to update project dependencies:
|
||||
|
||||
composer update --no-dev
|
||||
composer update --no-dev
|
||||
|
||||
**Note: It is necessary to run this command twice to ensure all dependencies
|
||||
are properly updated.** We have an issue open to figure out a better solution:
|
||||
[Composer merge plugin dependencies are not correctly updated #653](https://github.com/farmOS/farmOS/issues/653).
|
||||
|
||||
This will check for newer versions of all your project's dependencies (based
|
||||
on the version constraints in your `composer.json` file), install them, and
|
||||
|
|
|
@ -66,16 +66,14 @@ These resources may be helpful:
|
|||
Nginx reverse proxy with self-signed certificates for local farmOS
|
||||
development with HTTPS.
|
||||
|
||||
### API Keys
|
||||
### Satellite map layers
|
||||
|
||||
Optional modules are available for adding satellite imagery layers to maps (eg:
|
||||
Mapbox, Google Maps, etc). However, because these layers are hosted by
|
||||
third-party providers, API keys are required to use them. Instructions for
|
||||
obtaining API keys are available via the links below. API keys can be entered
|
||||
into farmOS by going to Settings > Map.
|
||||
|
||||
- [Mapbox](https://docs.mapbox.com/help/how-mapbox-works/access-tokens)
|
||||
- [Google Maps](https://developers.google.com/maps/documentation/javascript/get-api-key)
|
||||
farmOS includes an optional [Mapbox](https://www.mapbox.com) module that can be
|
||||
enabled to add satellite imagery layers to the map. A Mapbox API key is
|
||||
required. For more information, see Mapbox's official documentation:
|
||||
[Access tokens](https://docs.mapbox.com/help/how-mapbox-works/access-tokens).
|
||||
Enable the Mapbox module at Setup > Modules, and then add the API key at
|
||||
Setup > Settings > Map > Mapbox.
|
||||
|
||||
## farmOS Codebase
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@ farmOS 2.x includes a **farmOS Migrate** module that leverage's Drupal core's
|
|||
migrations for each asset type, log type, etc. These migrations are defined in
|
||||
YML configuration files included with the farmOS Migrate module.
|
||||
|
||||
... migrate to 2.x then upgrade to 3.x
|
||||
|
||||
## Important considerations
|
||||
|
||||
* Do not migrate into a farmOS 2.x instance that already has records. This is
|
||||
|
|
|
@ -25,6 +25,7 @@ included with farmOS define the following Asset types:
|
|||
- Sensor
|
||||
- Water
|
||||
- Material
|
||||
- Product
|
||||
- Group*
|
||||
|
||||
*Group Assets are unique in that they can "contain" other Assets as "group
|
||||
|
@ -272,6 +273,12 @@ Plant Assets have the following additional relationships:
|
|||
- Plant type (references a Term in the "Plant type" vocabulary)
|
||||
- Season (references a Term in the "Season" vocabulary)
|
||||
|
||||
#### Product Assets
|
||||
|
||||
Product Assets have the following additional relationships:
|
||||
|
||||
- Product type (references a Term in the "Product type" vocabulary)
|
||||
|
||||
#### Sensor Assets
|
||||
|
||||
Sensor Assets have an additional "Data streams" relationship, which is used to
|
||||
|
|
|
@ -20,6 +20,7 @@ is enabled. The modules included with farmOS define the following vocabularies:
|
|||
- Log category
|
||||
- Material type
|
||||
- Plant type
|
||||
- Product type
|
||||
- Season
|
||||
- Unit
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ function farm_modules() {
|
|||
'farm_inventory' => t('Inventory management'),
|
||||
'farm_material' => t('Material assets'),
|
||||
'farm_seed' => t('Seed assets'),
|
||||
'farm_product' => t('Product assets'),
|
||||
'farm_sensor' => t('Sensor assets'),
|
||||
'farm_compost' => t('Compost assets'),
|
||||
'farm_group' => t('Group assets'),
|
||||
|
@ -58,6 +59,7 @@ function farm_modules() {
|
|||
'farm_import_csv' => t('CSV importer'),
|
||||
'farm_kml' => t('KML export features'),
|
||||
'farm_import_kml' => t('KML asset importer'),
|
||||
'farm_map_mapbox' => t('Mapbox map layers: Satellite, Outdoors'),
|
||||
'farm_api_default_consumer' => t('Default API Consumer'),
|
||||
'farm_fieldkit' => t('Field Kit integration'),
|
||||
'farm_l10n' => t('Translation/localization features'),
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
enforced:
|
||||
module:
|
||||
- farm_product
|
||||
id: product
|
||||
label: Product
|
||||
description: ''
|
||||
workflow: asset_default
|
||||
new_revision: true
|
|
@ -0,0 +1,11 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
enforced:
|
||||
module:
|
||||
- farm_product
|
||||
id: asset_product
|
||||
color: blue
|
||||
conditions:
|
||||
asset_type:
|
||||
- product
|
|
@ -0,0 +1,9 @@
|
|||
name: Product asset
|
||||
description: Adds an Product asset type.
|
||||
type: module
|
||||
package: farmOS Assets
|
||||
core_version_requirement: ^10
|
||||
dependencies:
|
||||
- farm:asset
|
||||
- farm:farm_entity
|
||||
- farm:farm_product_type
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\farm_product\Plugin\Asset\AssetType;
|
||||
|
||||
use Drupal\farm_entity\Plugin\Asset\AssetType\FarmAssetType;
|
||||
|
||||
/**
|
||||
* Provides the product asset type.
|
||||
*
|
||||
* @AssetType(
|
||||
* id = "product",
|
||||
* label = @Translation("Product"),
|
||||
* )
|
||||
*/
|
||||
class Product extends FarmAssetType {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildFieldDefinitions() {
|
||||
$fields = parent::buildFieldDefinitions();
|
||||
$field_info = [
|
||||
'product_type' => [
|
||||
'type' => 'entity_reference',
|
||||
'label' => $this->t('Product type'),
|
||||
'description' => $this->t("Enter the type of product."),
|
||||
'target_type' => 'taxonomy_term',
|
||||
'target_bundle' => 'product_type',
|
||||
'auto_create' => TRUE,
|
||||
'required' => TRUE,
|
||||
'weight' => [
|
||||
'form' => -90,
|
||||
'view' => -50,
|
||||
],
|
||||
],
|
||||
];
|
||||
foreach ($field_info as $name => $info) {
|
||||
$fields[$name] = $this->farmFieldFactory->bundleFieldDefinition($info);
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
|
@ -781,6 +781,14 @@ class FarmFieldFactory implements FarmFieldFactoryInterface {
|
|||
$field->setSetting('size', $options['size']);
|
||||
}
|
||||
|
||||
// Set the min/max constraints, if specified.
|
||||
if (isset($options['min'])) {
|
||||
$field->setSetting('min', $options['min']);
|
||||
}
|
||||
if (isset($options['max'])) {
|
||||
$field->setSetting('max', $options['max']);
|
||||
}
|
||||
|
||||
// Build form and view display settings.
|
||||
$field->setDisplayOptions('form', [
|
||||
'type' => 'number',
|
||||
|
|
|
@ -11,10 +11,3 @@ farm.settings:
|
|||
parent: farm.setup
|
||||
route_name: farm_settings.settings_page
|
||||
weight: 100
|
||||
|
||||
farm.settings.farm_info:
|
||||
title: Farm Info
|
||||
description: Test
|
||||
parent: farm.settings
|
||||
route_name: farm_settings.settings_page
|
||||
weight: -10
|
||||
|
|
|
@ -12,7 +12,6 @@ farm.asset.locations:
|
|||
_title_callback: '\Drupal\farm_ui_location\Form\LocationHierarchyForm::getTitle'
|
||||
_form: '\Drupal\farm_ui_location\Form\LocationHierarchyForm'
|
||||
requirements:
|
||||
_entity_access: 'asset.edit'
|
||||
_custom_access: '\Drupal\farm_ui_location\Form\LocationHierarchyForm::access'
|
||||
_module_dependencies: 'asset'
|
||||
asset: \d+
|
||||
|
|
|
@ -82,8 +82,13 @@ class LocationHierarchyForm extends FormBase {
|
|||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
// If the asset does not have child locations, forbid access.
|
||||
if (empty($this->getLocations($asset))) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
// Allow access if the asset has child locations.
|
||||
return AccessResult::allowedIf(!empty($this->getLocations($asset)));
|
||||
return AccessResult::allowedIf($asset->access('view', $account));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,6 +243,11 @@ class LocationHierarchyForm extends FormBase {
|
|||
/** @var \Drupal\asset\Entity\AssetInterface[] $assets */
|
||||
$assets = $storage->loadMultiple($asset_ids);
|
||||
|
||||
// Filter out assets that the user cannot view.
|
||||
$assets = array_filter($assets, function ($asset) {
|
||||
return $asset->access('view');
|
||||
});
|
||||
|
||||
// Sort assets by name, using natural sort algorithm.
|
||||
usort($assets, function ($a, $b) {
|
||||
return strnatcmp($a->label(), $b->label());
|
||||
|
@ -269,12 +279,22 @@ class LocationHierarchyForm extends FormBase {
|
|||
// Maintain a list of assets that need to be saved.
|
||||
$save_assets = [];
|
||||
|
||||
// Maintain a list of assets that were not editable by the user.
|
||||
$restricted_assets = [];
|
||||
|
||||
// Iterate through the changes.
|
||||
foreach ($changes as $change) {
|
||||
|
||||
// Load the asset.
|
||||
$asset = $storage->load($change['asset_id']);
|
||||
|
||||
// If the user does not have permission to edit the asset, count it so
|
||||
// that we can add a warning message later, and skip it.
|
||||
if (!$asset->access('edit')) {
|
||||
$restricted_assets[] = $asset;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the original parent.
|
||||
if (!empty($asset->get('parent'))) {
|
||||
/** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $parent */
|
||||
|
@ -317,6 +337,12 @@ class LocationHierarchyForm extends FormBase {
|
|||
// Show a summary of the results.
|
||||
$message = $this->formatPlural(count($save_assets), 'Updated the parent hierarchy of %count asset.', 'Updated the parent hierarchy of %count assets.', ['%count' => count($save_assets)]);
|
||||
$this->messenger()->addStatus($message);
|
||||
|
||||
// If any edits were restricted, show a warning.
|
||||
if ($restricted_assets) {
|
||||
$message = $this->formatPlural(count($restricted_assets), '%count asset could not be changed because you do not have permission.', '%count assets could not be changed because you do not have permission.', ['%count' => count($restricted_assets)]);
|
||||
$this->messenger()->addWarning($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
enforced:
|
||||
module:
|
||||
- farm_ui_map
|
||||
module:
|
||||
- asset
|
||||
id: asset.map_popup
|
||||
|
|
|
@ -79,6 +79,12 @@ function farm_ui_menu_entity_type_build(array &$entity_types) {
|
|||
*/
|
||||
function farm_ui_menu_local_tasks_alter(&$local_tasks) {
|
||||
|
||||
// Disable Drupal core revisions local tasks.
|
||||
$target_entity_types = ['asset', 'data_stream', 'log', 'plan', 'taxonomy_term'];
|
||||
foreach ($target_entity_types as $entity_type) {
|
||||
unset($local_tasks['entity.version_history:' . $entity_type . '.version_history']);
|
||||
}
|
||||
|
||||
// Remove local tasks provided by core taxonomy module.
|
||||
$taxonomy_term_tasks = [
|
||||
'entity.taxonomy_term.canonical',
|
||||
|
|
|
@ -14,10 +14,3 @@
|
|||
.toolbar-id--toolbar-icon-user {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
/* Decrease top margin from elements below page title. */
|
||||
.layout-container .page-content > .help,
|
||||
.layout-container .page-content > .region-highlighted,
|
||||
.layout-container .page-content > .region-content {
|
||||
margin-top: var(--gin-spacing-s);
|
||||
}
|
||||
|
|
|
@ -116,7 +116,9 @@ function farm_ui_views_views_pre_render(ViewExecutable $view) {
|
|||
if ($view->id() == 'farm_asset' && $view->current_display == 'page_children') {
|
||||
$asset_id = $view->args[0];
|
||||
$asset = \Drupal::entityTypeManager()->getStorage('asset')->load($asset_id);
|
||||
$title = t('Children of %asset', ['%asset' => $asset->label()]);
|
||||
if (!empty($asset)) {
|
||||
$title = t('Children of %asset', ['%asset' => $asset->label()]);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the farm_asset View and page_location display, include the
|
||||
|
@ -124,7 +126,9 @@ function farm_ui_views_views_pre_render(ViewExecutable $view) {
|
|||
if ($view->id() == 'farm_asset' && $view->current_display == 'page_location') {
|
||||
$asset_id = $view->args[0];
|
||||
$asset = \Drupal::entityTypeManager()->getStorage('asset')->load($asset_id);
|
||||
$title = t('Assets in %location', ['%location' => $asset->label()]);
|
||||
if (!empty($asset)) {
|
||||
$title = t('Assets in %location', ['%location' => $asset->label()]);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the farm_log View and page_asset display, include the asset's
|
||||
|
@ -132,7 +136,9 @@ function farm_ui_views_views_pre_render(ViewExecutable $view) {
|
|||
if ($view->id() == 'farm_log' && $view->current_display == 'page_asset') {
|
||||
$asset_id = $view->args[0];
|
||||
$asset = \Drupal::entityTypeManager()->getStorage('asset')->load($asset_id);
|
||||
$title = $asset->label() . ' ' . $view->getBaseEntityType()->getPluralLabel();
|
||||
if (!empty($asset)) {
|
||||
$title = $asset->label() . ' ' . $view->getBaseEntityType()->getPluralLabel();
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a "By type" display and a bundle argument is specified, load
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
farm_quick.settings.inventory:
|
||||
type: quick_form_settings
|
||||
label: 'Inventory quick form settings'
|
||||
mapping:
|
||||
asset:
|
||||
type: integer
|
||||
label: 'Default asset ID'
|
||||
units:
|
||||
type: string
|
||||
label: 'Default quantity units'
|
||||
measure:
|
||||
type: string
|
||||
label: 'Default quantity measure'
|
||||
inventory_adjustment:
|
||||
type: string
|
||||
label: 'Default inventory adjustment type'
|
||||
log_type:
|
||||
type: string
|
||||
label: 'Default log type'
|
|
@ -0,0 +1,9 @@
|
|||
name: Inventory Quick Form
|
||||
description: Provides a quick form for recording asset inventory adjustments.
|
||||
type: module
|
||||
package: farmOS Quick Forms
|
||||
core_version_requirement: ^10
|
||||
dependencies:
|
||||
- farm:farm_inventory
|
||||
- farm:farm_observation
|
||||
- farm:farm_quick
|
|
@ -0,0 +1,508 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\farm_quick_inventory\Plugin\QuickForm;
|
||||
|
||||
use Drupal\asset\Entity\AssetInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\farm_inventory\AssetInventoryInterface;
|
||||
use Drupal\farm_quick\Plugin\QuickForm\ConfigurableQuickFormInterface;
|
||||
use Drupal\farm_quick\Plugin\QuickForm\QuickFormBase;
|
||||
use Drupal\farm_quick\Traits\ConfigurableQuickFormTrait;
|
||||
use Drupal\farm_quick\Traits\QuickFormElementsTrait;
|
||||
use Drupal\farm_quick\Traits\QuickLogTrait;
|
||||
use Drupal\farm_quick\Traits\QuickTermTrait;
|
||||
use Drupal\log\Entity\Log;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Inventory quick form.
|
||||
*
|
||||
* @QuickForm(
|
||||
* id = "inventory",
|
||||
* label = @Translation("Inventory"),
|
||||
* description = @Translation("Record asset inventory adjustments."),
|
||||
* helpText = @Translation("Use this form to increment, decrement, or reset the inventory of an asset. A new log will be created to record the adjustment."),
|
||||
* permissions = {}
|
||||
* )
|
||||
*/
|
||||
class Inventory extends QuickFormBase implements ConfigurableQuickFormInterface {
|
||||
|
||||
use ConfigurableQuickFormTrait;
|
||||
use QuickLogTrait;
|
||||
use QuickFormElementsTrait;
|
||||
use QuickTermTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Asset inventory service.
|
||||
*
|
||||
* @var \Drupal\farm_inventory\AssetInventoryInterface
|
||||
*/
|
||||
protected $assetInventory;
|
||||
|
||||
/**
|
||||
* Current user object.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a QuickFormBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\farm_inventory\AssetInventoryInterface $asset_inventory
|
||||
* Asset inventory service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* Current user object.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MessengerInterface $messenger, EntityTypeManagerInterface $entity_type_manager, AssetInventoryInterface $asset_inventory, AccountInterface $current_user) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $messenger);
|
||||
$this->messenger = $messenger;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->assetInventory = $asset_inventory;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('messenger'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('asset.inventory'),
|
||||
$container->get('current_user'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(AccountInterface $account) {
|
||||
|
||||
// Check to ensure the user has permission to create the configured log type
|
||||
// and view the configured asset.
|
||||
$result = AccessResult::allowedIf($this->entityTypeManager->getAccessControlHandler('log')->createAccess($this->configuration['log_type'], $account));
|
||||
if (!empty($this->configuration['asset'])) {
|
||||
$asset = $this->entityTypeManager->getStorage('asset')->load($this->configuration['asset']);
|
||||
$result = $result->andIf(AccessResult::allowedIf($asset->access('view')));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'asset' => NULL,
|
||||
'measure' => NULL,
|
||||
'units' => NULL,
|
||||
'inventory_adjustment' => 'reset',
|
||||
'log_type' => 'observation',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, string $id = NULL) {
|
||||
|
||||
// Date.
|
||||
$form['date'] = [
|
||||
'#type' => 'datetime',
|
||||
'#title' => $this->t('Date'),
|
||||
'#default_value' => new DrupalDateTime('midnight', $this->currentUser->getTimeZone()),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
// Asset.
|
||||
$form['asset'] = [
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#title' => $this->t('Asset'),
|
||||
'#description' => $this->t("Which asset's inventory is being adjusted?"),
|
||||
'#target_type' => 'asset',
|
||||
'#selection_settings' => [
|
||||
'sort' => [
|
||||
'field' => 'status',
|
||||
'direction' => 'ASC',
|
||||
],
|
||||
],
|
||||
'#maxlength' => 1024,
|
||||
'#required' => TRUE,
|
||||
];
|
||||
if (!empty($this->configuration['asset'])) {
|
||||
$form['asset']['#default_value'] = $this->entityTypeManager->getStorage('asset')->load($this->configuration['asset']);
|
||||
}
|
||||
|
||||
// Quantity.
|
||||
$form['quantity'] = $this->buildInlineContainer();
|
||||
$form['quantity']['#tree'] = TRUE;
|
||||
$form['quantity']['value'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Quantity'),
|
||||
'#size' => 16,
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['quantity']['units'] = [
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#title' => $this->t('Units'),
|
||||
'#target_type' => 'taxonomy_term',
|
||||
'#selection_settings' => [
|
||||
'target_bundles' => ['unit'],
|
||||
],
|
||||
'#autocreate' => [
|
||||
'bundle' => 'unit',
|
||||
],
|
||||
'#size' => 16,
|
||||
];
|
||||
if (!empty($this->configuration['units'])) {
|
||||
$form['quantity']['units']['#default_value'] = $this->createOrLoadTerm($this->configuration['units'], 'unit');
|
||||
}
|
||||
$form['quantity']['measure'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Measure'),
|
||||
'#options' => array_merge(['' => ''], quantity_measure_options()),
|
||||
'#default_value' => $this->configuration['measure'],
|
||||
];
|
||||
|
||||
// Inventory adjustment.
|
||||
$form['inventory_adjustment'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Adjustment type'),
|
||||
'#description' => $this->t('What type of inventory adjustment is this?'),
|
||||
'#options' => [
|
||||
'increment' => $this->t('Increment'),
|
||||
'decrement' => $this->t('Decrement'),
|
||||
'reset' => $this->t('Reset'),
|
||||
],
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $this->configuration['inventory_adjustment'],
|
||||
];
|
||||
|
||||
// Notes.
|
||||
$form['notes'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Notes'),
|
||||
];
|
||||
$form['notes']['notes'] = [
|
||||
'#type' => 'text_format',
|
||||
'#title' => $this->t('Notes'),
|
||||
'#title_display' => 'invisible',
|
||||
'#format' => 'default',
|
||||
];
|
||||
|
||||
// Advanced.
|
||||
$form['advanced'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Advanced'),
|
||||
];
|
||||
|
||||
// Log type.
|
||||
$form['advanced']['log_type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Log type'),
|
||||
'#description' => $this->t('Select the type of log to create.'),
|
||||
'#options' => $this->logTypeOptions(),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $this->configuration['log_type'],
|
||||
];
|
||||
|
||||
// Log name.
|
||||
// Provide a checkbox to allow customizing this. Otherwise, it will be
|
||||
// automatically generated on submission.
|
||||
$form['advanced']['custom_name'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Customize log name'),
|
||||
'#description' => $this->t('This allows the log name to be customized. Otherwise, a default name will be generated.'),
|
||||
'#default_value' => FALSE,
|
||||
'#ajax' => [
|
||||
'callback' => [$this, 'logNameCallback'],
|
||||
'wrapper' => 'log-name',
|
||||
],
|
||||
];
|
||||
$form['advanced']['name_wrapper'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'log-name'],
|
||||
];
|
||||
if ($form_state->getValue('custom_name', FALSE)) {
|
||||
$form['advanced']['name_wrapper']['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Log name'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $this->generateLogName($form_state),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
// Done.
|
||||
$form['done'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Completed'),
|
||||
'#default_value' => TRUE,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of log type options.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array of log type labels, keyed by machine name.
|
||||
* Only log types that the user has access to create will be included.
|
||||
*/
|
||||
protected function logTypeOptions() {
|
||||
$log_access_control_handler = $this->entityTypeManager->getAccessControlHandler('log');
|
||||
$log_types = array_filter($this->entityTypeManager->getStorage('log_type')->loadMultiple(), function ($log_type) use ($log_access_control_handler) {
|
||||
return $log_access_control_handler->createAccess($log_type->id(), $this->currentUser);
|
||||
});
|
||||
return array_map(function ($log_type) {
|
||||
return $log_type->label();
|
||||
}, $log_types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate log name.
|
||||
*
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*
|
||||
* @return string
|
||||
* Returns a log name string.
|
||||
*/
|
||||
protected function generateLogName(FormStateInterface $form_state) {
|
||||
$log_name = '';
|
||||
|
||||
// Get the asset name. If an asset has not been selected, bail.
|
||||
$asset = $form_state->getValue('asset');
|
||||
if (is_numeric($asset)) {
|
||||
$asset = $this->entityTypeManager->getStorage('asset')->load($asset);
|
||||
}
|
||||
if (!($asset instanceof AssetInterface)) {
|
||||
return $log_name;
|
||||
}
|
||||
|
||||
// Create a summary of the quantity.
|
||||
$quantity_summary = $form_state->getValue(['quantity', 'value']);
|
||||
$units = $form_state->getValue(['quantity', 'units']);
|
||||
$measure = $form_state->getValue(['quantity', 'measure']);
|
||||
if (!empty($units)) {
|
||||
if (is_numeric($units)) {
|
||||
$units = $this->entityTypeManager->getStorage('taxonomy_term')->load($units);
|
||||
}
|
||||
elseif (is_array($units) && !empty($units['entity'])) {
|
||||
$units = $units['entity'];
|
||||
}
|
||||
if ($units instanceof TermInterface) {
|
||||
$quantity_summary .= ' ' . $units->label();
|
||||
}
|
||||
}
|
||||
if (!empty($measure)) {
|
||||
$quantity_summary .= ' (' . $measure . ')';
|
||||
}
|
||||
|
||||
// Generate the log name based on the inventory adjustment type.
|
||||
switch ($form_state->getValue('inventory_adjustment')) {
|
||||
case 'increment':
|
||||
$log_name = $this->t('Increment inventory of @asset by @quantity', ['@asset' => Markup::create($asset->label()), '@quantity' => $quantity_summary]);
|
||||
break;
|
||||
|
||||
case 'decrement':
|
||||
$log_name = $this->t('Decrement inventory of @asset by @quantity', ['@asset' => Markup::create($asset->label()), '@quantity' => $quantity_summary]);
|
||||
break;
|
||||
|
||||
case 'reset':
|
||||
$log_name = $this->t('Reset inventory of @asset to @quantity', ['@asset' => Markup::create($asset->label()), '@quantity' => $quantity_summary]);
|
||||
break;
|
||||
}
|
||||
|
||||
return $log_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
// Mock a minimal log of the selected type to ensure that it validates. This
|
||||
// protects against creating log types that have required fields that this
|
||||
// form is not able to populate.
|
||||
$log = Log::create([
|
||||
'type' => $form_state->getValue('log_type'),
|
||||
]);
|
||||
$violations = $log->validate();
|
||||
if ($violations->count()) {
|
||||
$form_state->setError($form['log_type'], $this->t('The selected log type cannot be created. It may have required fields that this form is unable to populate.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
// Load asset.
|
||||
$asset = $this->entityTypeManager->getStorage('asset')->load($form_state->getValue('asset'));
|
||||
|
||||
// Load units term (if specified).
|
||||
$units = $form_state->getValue(['quantity', 'units']);
|
||||
if (is_numeric($units)) {
|
||||
$units = $this->entityTypeManager->getStorage('taxonomy_term')->load($form_state->getValue(['quantity', 'units']));
|
||||
}
|
||||
elseif (is_array($units) && !empty($units['entity'])) {
|
||||
$units = $units['entity'];
|
||||
}
|
||||
|
||||
// Create a quantity for the inventory adjustment.
|
||||
$quantity = [
|
||||
'measure' => $form_state->getValue(['quantity', 'measure']),
|
||||
'value' => $form_state->getValue(['quantity', 'value']),
|
||||
'units' => $units,
|
||||
'inventory_adjustment' => $form_state->getValue('inventory_adjustment'),
|
||||
'inventory_asset' => $asset,
|
||||
];
|
||||
|
||||
// Draft an inventory adjustment log from the user-submitted data.
|
||||
$timestamp = $form_state->getValue('date')->getTimestamp();
|
||||
$status = $form_state->getValue('done') ? 'done' : 'pending';
|
||||
$log = [
|
||||
'type' => $form_state->getValue('log_type'),
|
||||
'timestamp' => $timestamp,
|
||||
'quantity' => [$quantity],
|
||||
'notes' => $form_state->getValue('notes'),
|
||||
'status' => $status,
|
||||
];
|
||||
|
||||
// Generate a name for the log.
|
||||
// If a custom plant name was provided, use that. Otherwise, generate one.
|
||||
$log['name'] = $this->generateLogName($form_state);
|
||||
if (!empty($form_state->getValue('custom_name', FALSE)) && $form_state->hasValue('name')) {
|
||||
$log['name'] = $form_state->getValue('name');
|
||||
}
|
||||
|
||||
// Create the log.
|
||||
$this->createLog($log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for log name field.
|
||||
*/
|
||||
public function logNameCallback(array $form, FormStateInterface $form_state) {
|
||||
return $form['advanced']['name_wrapper'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
// Asset.
|
||||
$form['asset'] = [
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#title' => $this->t('Asset'),
|
||||
'#description' => $this->t("Which asset's inventory is being adjusted?"),
|
||||
'#target_type' => 'asset',
|
||||
'#selection_settings' => [
|
||||
'sort' => [
|
||||
'field' => 'status',
|
||||
'direction' => 'ASC',
|
||||
],
|
||||
],
|
||||
'#maxlength' => 1024,
|
||||
];
|
||||
if (!empty($this->configuration['asset'])) {
|
||||
$form['asset']['#default_value'] = $this->entityTypeManager->getStorage('asset')->load($this->configuration['asset']);
|
||||
}
|
||||
|
||||
// Units.
|
||||
$form['units'] = [
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#title' => $this->t('Units'),
|
||||
'#target_type' => 'taxonomy_term',
|
||||
'#selection_settings' => [
|
||||
'target_bundles' => ['unit'],
|
||||
],
|
||||
'#autocreate' => [
|
||||
'bundle' => 'unit',
|
||||
],
|
||||
'#size' => 16,
|
||||
];
|
||||
if (!empty($this->configuration['units'])) {
|
||||
$form['units']['#default_value'] = $this->createOrLoadTerm($this->configuration['units'], 'unit');
|
||||
}
|
||||
|
||||
// Measure.
|
||||
$form['measure'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Measure'),
|
||||
'#options' => array_merge(['' => ''], quantity_measure_options()),
|
||||
'#default_value' => $this->configuration['measure'],
|
||||
];
|
||||
|
||||
// Inventory adjustment.
|
||||
$form['inventory_adjustment'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Adjustment type'),
|
||||
'#description' => $this->t('What type of inventory adjustment is this?'),
|
||||
'#options' => [
|
||||
'increment' => $this->t('Increment'),
|
||||
'decrement' => $this->t('Decrement'),
|
||||
'reset' => $this->t('Reset'),
|
||||
],
|
||||
'#default_value' => $this->configuration['inventory_adjustment'],
|
||||
];
|
||||
|
||||
// Log type.
|
||||
$form['log_type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Log type'),
|
||||
'#description' => $this->t('Select the type of log to create.'),
|
||||
'#options' => $this->logTypeOptions(),
|
||||
'#default_value' => $this->configuration['log_type'],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['asset'] = $form_state->getValue('asset');
|
||||
$this->configuration['units'] = NULL;
|
||||
if (!empty($form_state->getValue('units'))) {
|
||||
$this->configuration['units'] = $form_state->getValue('units')['entity']->label();
|
||||
}
|
||||
$this->configuration['measure'] = $form_state->getValue('measure');
|
||||
$this->configuration['inventory_adjustment'] = $form_state->getValue('inventory_adjustment');
|
||||
$this->configuration['log_type'] = $form_state->getValue('log_type');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\farm_quick_inventory\Kernel;
|
||||
|
||||
use Drupal\asset\Entity\Asset;
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\Tests\farm_quick\Kernel\QuickFormTestBase;
|
||||
|
||||
/**
|
||||
* Tests for farmOS inventory quick form.
|
||||
*
|
||||
* @group farm
|
||||
*/
|
||||
class QuickInventoryTest extends QuickFormTestBase {
|
||||
|
||||
/**
|
||||
* Quick form ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $quickFormId = 'inventory';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'farm_activity',
|
||||
'farm_equipment',
|
||||
'farm_inventory',
|
||||
'farm_observation',
|
||||
'farm_quantity_standard',
|
||||
'farm_quick_inventory',
|
||||
'farm_unit',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig([
|
||||
'farm_activity',
|
||||
'farm_equipment',
|
||||
'farm_observation',
|
||||
'farm_quantity_standard',
|
||||
'farm_unit',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inventory quick form submission.
|
||||
*/
|
||||
public function testQuickInventory() {
|
||||
|
||||
// Get today's date.
|
||||
$today = new DrupalDateTime('midnight');
|
||||
|
||||
// Create an equipment asset.
|
||||
$equipment = Asset::create([
|
||||
'name' => 'Tractor',
|
||||
'type' => 'equipment',
|
||||
'status' => 'active',
|
||||
]);
|
||||
$equipment->save();
|
||||
|
||||
// Programmatically submit the inventory quick form (reset to 1).
|
||||
$form_values = [
|
||||
'date' => [
|
||||
'date' => $today->format('Y-m-d'),
|
||||
'time' => $today->format('H:i:s'),
|
||||
],
|
||||
'asset' => [
|
||||
['target_id' => $equipment->id()],
|
||||
],
|
||||
'quantity' => [
|
||||
'value' => '1',
|
||||
'units' => '',
|
||||
'measure' => '',
|
||||
],
|
||||
'inventory_adjustment' => 'reset',
|
||||
'notes' => [
|
||||
'value' => 'Lorem ipsum',
|
||||
'format' => 'default',
|
||||
],
|
||||
'log_type' => 'observation',
|
||||
'done' => TRUE,
|
||||
];
|
||||
$this->submitQuickForm($form_values);
|
||||
|
||||
// Load logs.
|
||||
$logs = $this->logStorage->loadMultiple();
|
||||
|
||||
// Confirm that one log exists.
|
||||
$this->assertCount(1, $logs);
|
||||
|
||||
// Check that the log's fields were populated correctly.
|
||||
$log = $logs[1];
|
||||
$this->assertEquals('observation', $log->bundle());
|
||||
$this->assertEquals($today->getTimestamp(), $log->get('timestamp')->value);
|
||||
$this->assertEquals('Reset inventory of Tractor to 1', $log->label());
|
||||
$this->assertEquals('1', $log->get('quantity')->referencedEntities()[0]->get('value')[0]->get('decimal')->getValue());
|
||||
$this->assertCount(0, $log->get('quantity')->referencedEntities()[0]->get('units')->referencedEntities());
|
||||
$this->assertEquals('', $log->get('quantity')->referencedEntities()[0]->get('measure')->value);
|
||||
$this->assertEquals('reset', $log->get('quantity')->referencedEntities()[0]->get('inventory_adjustment')->value);
|
||||
$this->assertEquals($equipment->id(), $log->get('quantity')->referencedEntities()[0]->get('inventory_asset')->referencedEntities()[0]->id());
|
||||
$this->assertEquals('Lorem ipsum', $log->get('notes')->value);
|
||||
$this->assertEquals('done', $log->get('status')->value);
|
||||
|
||||
// Check that the asset has a single inventory of 1.
|
||||
$inventory = \Drupal::service('asset.inventory')->getInventory($equipment);
|
||||
$this->assertCount(1, $inventory);
|
||||
$this->assertEquals('1', $inventory[0]['value']);
|
||||
$this->assertEquals('', $inventory[0]['units']);
|
||||
$this->assertEquals('', $inventory[0]['measure']);
|
||||
|
||||
// Programmatically submit the inventory quick form (increment by 1 with an
|
||||
// activity log).
|
||||
$form_values = [
|
||||
'date' => [
|
||||
'date' => $today->format('Y-m-d'),
|
||||
'time' => $today->format('H:i:s'),
|
||||
],
|
||||
'asset' => [
|
||||
['target_id' => $equipment->id()],
|
||||
],
|
||||
'quantity' => [
|
||||
'value' => '1',
|
||||
'units' => '',
|
||||
'measure' => '',
|
||||
],
|
||||
'inventory_adjustment' => 'increment',
|
||||
'log_type' => 'activity',
|
||||
'done' => TRUE,
|
||||
];
|
||||
$this->submitQuickForm($form_values);
|
||||
|
||||
// Confirm that two logs exists.
|
||||
$logs = $this->logStorage->loadMultiple();
|
||||
$this->assertCount(2, $logs);
|
||||
|
||||
// Check that the log is an activity and that the name was populated
|
||||
// correctly.
|
||||
$log = $logs[2];
|
||||
$this->assertEquals('activity', $log->bundle());
|
||||
$this->assertEquals('Increment inventory of Tractor by 1', $log->label());
|
||||
|
||||
// Check that the asset has a single inventory of 2.
|
||||
$inventory = \Drupal::service('asset.inventory')->getInventory($equipment);
|
||||
$this->assertCount(1, $inventory);
|
||||
$this->assertEquals('2', $inventory[0]['value']);
|
||||
$this->assertEquals('', $inventory[0]['units']);
|
||||
$this->assertEquals('', $inventory[0]['measure']);
|
||||
|
||||
// Programmatically submit the inventory quick form (decrement by 1).
|
||||
$form_values = [
|
||||
'date' => [
|
||||
'date' => $today->format('Y-m-d'),
|
||||
'time' => $today->format('H:i:s'),
|
||||
],
|
||||
'asset' => [
|
||||
['target_id' => $equipment->id()],
|
||||
],
|
||||
'quantity' => [
|
||||
'value' => '1',
|
||||
'units' => '',
|
||||
'measure' => '',
|
||||
],
|
||||
'inventory_adjustment' => 'decrement',
|
||||
'log_type' => 'observation',
|
||||
'done' => TRUE,
|
||||
];
|
||||
$this->submitQuickForm($form_values);
|
||||
|
||||
// Confirm that three logs exists.
|
||||
$logs = $this->logStorage->loadMultiple();
|
||||
$this->assertCount(3, $logs);
|
||||
|
||||
// Check that the log name was populated correctly.
|
||||
$log = $logs[3];
|
||||
$this->assertEquals('Decrement inventory of Tractor by 1', $log->label());
|
||||
|
||||
// Check that the asset has a single inventory of 1.
|
||||
$inventory = \Drupal::service('asset.inventory')->getInventory($equipment);
|
||||
$this->assertCount(1, $inventory);
|
||||
$this->assertEquals('1', $inventory[0]['value']);
|
||||
$this->assertEquals('', $inventory[0]['units']);
|
||||
$this->assertEquals('', $inventory[0]['measure']);
|
||||
|
||||
// Create a unit term.
|
||||
$unit = Term::create([
|
||||
'name' => 'liters',
|
||||
'vid' => 'unit',
|
||||
]);
|
||||
$unit->save();
|
||||
|
||||
// Programmatically submit the inventory quick form with units and measure.
|
||||
$form_values = [
|
||||
'date' => [
|
||||
'date' => $today->format('Y-m-d'),
|
||||
'time' => $today->format('H:i:s'),
|
||||
],
|
||||
'asset' => [
|
||||
['target_id' => $equipment->id()],
|
||||
],
|
||||
'quantity' => [
|
||||
'value' => '10',
|
||||
'units' => [
|
||||
['target_id' => $unit->id()],
|
||||
],
|
||||
'measure' => 'volume',
|
||||
],
|
||||
'inventory_adjustment' => 'reset',
|
||||
'log_type' => 'observation',
|
||||
'done' => TRUE,
|
||||
];
|
||||
$this->submitQuickForm($form_values);
|
||||
|
||||
// Confirm that four logs exists.
|
||||
$logs = $this->logStorage->loadMultiple();
|
||||
$this->assertCount(4, $logs);
|
||||
|
||||
// Check that the log's name and quantity measure and units were populated.
|
||||
$log = $logs[4];
|
||||
$this->assertEquals('Reset inventory of Tractor to 10 liters (volume)', $log->label());
|
||||
$this->assertEquals('liters', $log->get('quantity')->referencedEntities()[0]->get('units')->referencedEntities()[0]->get('name')->value);
|
||||
$this->assertEquals('volume', $log->get('quantity')->referencedEntities()[0]->get('measure')->value);
|
||||
|
||||
// Check that the asset has two inventories.
|
||||
$inventory = \Drupal::service('asset.inventory')->getInventory($equipment);
|
||||
$this->assertCount(2, $inventory);
|
||||
|
||||
// Load the volume (liters) inventory and confirm that it is 10.
|
||||
$inventory = \Drupal::service('asset.inventory')->getInventory($equipment, 'volume', $unit->id());
|
||||
$this->assertEquals('volume', $inventory[0]['measure']);
|
||||
$this->assertEquals('liters', $inventory[0]['units']);
|
||||
$this->assertEquals('10', $inventory[0]['value']);
|
||||
|
||||
// Test customizing the log name.
|
||||
$form_values = [
|
||||
'date' => [
|
||||
'date' => $today->format('Y-m-d'),
|
||||
'time' => $today->format('H:i:s'),
|
||||
],
|
||||
'asset' => [
|
||||
['target_id' => $equipment->id()],
|
||||
],
|
||||
'quantity' => [
|
||||
'value' => '1',
|
||||
'units' => '',
|
||||
'measure' => '',
|
||||
],
|
||||
'inventory_adjustment' => 'reset',
|
||||
'log_type' => 'observation',
|
||||
'done' => TRUE,
|
||||
'custom_name' => TRUE,
|
||||
'name' => 'Test custom log name',
|
||||
];
|
||||
$this->submitQuickForm($form_values);
|
||||
|
||||
// Confirm that five logs exists.
|
||||
$logs = $this->logStorage->loadMultiple();
|
||||
$this->assertCount(5, $logs);
|
||||
|
||||
// Check that the log name was populated correctly.
|
||||
$log = $logs[5];
|
||||
$this->assertEquals('Test custom log name', $log->label());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
enforced:
|
||||
module:
|
||||
- farm_product_type
|
||||
name: Product type
|
||||
vid: product_type
|
||||
description: 'A list of product types.'
|
||||
weight: 0
|
|
@ -0,0 +1,7 @@
|
|||
name: Product type vocabulary
|
||||
description: Provides an Product type vocabulary.
|
||||
type: module
|
||||
package: farmOS Taxonomies
|
||||
core_version_requirement: ^10
|
||||
dependencies:
|
||||
- drupal:taxonomy
|
Loading…
Reference in New Issue