Compare commits

...

37 Commits

Author SHA1 Message Date
Michael Stenta a09ef42add Add CHANGELOG.md line for #766. 2024-02-02 16:34:26 -05:00
Michael Stenta 6879ff591f Save unit term label to configuration and automatically generate term on form load. 2024-02-02 16:34:26 -05:00
Michael Stenta ce8173f152 Add a dependency on farm_quick. 2024-02-02 16:34:26 -05:00
Michael Stenta f5c7139a63 Check inventory quick form access based on configured log type and asset. 2024-02-02 16:34:26 -05:00
Michael Stenta a4544a7ecb Move log type and custom name fields to Advanced section (hidden by default). 2024-02-02 16:34:26 -05:00
Michael Stenta 1cb57c2178 Make inventory quick form configurable. 2024-02-02 16:34:26 -05:00
Michael Stenta 0467595a0a Fix error when creating new unit terms.
The entity_autocomplete widget's values are different depending
on whether the selected term already exists. If it exists, the
value is the term ID. If it is being created, the value is an
array with an 'entity' key containing the new TermInterface
object.
2024-02-02 16:34:26 -05:00
Michael Stenta ca5298245a Allow the log name to be customized. 2024-02-02 16:34:26 -05:00
Michael Stenta b074a635c9 Validate that the selected log type can be created. 2024-02-02 16:34:26 -05:00
Michael Stenta 5cbd753905 Allow log type to be selected. 2024-02-02 16:34:26 -05:00
Michael Stenta 74f3ee4b5d Initial inventory quick form with tests. 2024-02-02 16:34:26 -05:00
Michael Stenta a37820e514 Add a Product asset type and Product type taxonomy #787 2024-02-02 16:33:49 -05:00
Michael Stenta 31cd1aa177 Recommend running composer update twice #653 2024-02-02 13:22:31 -05:00
Michael Stenta 220c99c510 Whitespace fix. 2024-01-31 06:38:30 -05:00
Paul Weidner 5a49dce4f5 Remove decreased page top margin #788 2024-01-31 06:22:22 -05:00
Michael Stenta 84421be197 Add enforced module dependency to core.entity_view_mode.asset.map_popup.yml. 2024-01-31 05:12:46 -05:00
Michael Stenta 74ffac59ec Document Account Admin role in docs/guide/people. 2024-01-26 14:56:47 -05:00
Michael Stenta 1c404b5152 Announce new releases on farmOS.discourse.group #780 2024-01-22 10:42:42 -05:00
Michael Stenta 755e07bfdf
Only Docker is necessary in dev environment docs.
Docker Compose is included with Docker now.
2024-01-19 21:42:50 -05:00
Michael Stenta 6c03557b3c farmOS 3.0.1 2024-01-18 16:23:31 -05:00
Michael Stenta 7329866866 Update drupal/entity_reference_revisions to 1.11. 2024-01-18 16:23:31 -05:00
Michael Stenta 11b256b33a Remove farm.settings.farm_info menu link.
This is currently the only sub-menu link provided for
settings pages, which is weird. This is a quick fix which
now forces users to click "Settings" to see all the sub-tabs,
instead of thinking there is only one by looking at the
toolbar menu. In a follow-up we may consider providing a
deriver that automatically generates menu links for each
sub-tab so they all show in the toolbar menu. It would be
burdensome to require every module that provides a settings
tab to also add a menu link.
2024-01-18 16:23:31 -05:00
Michael Stenta 004ecd8e9e Allow users with asset view access to see asset/[id]/locations. 2024-01-18 15:09:17 -05:00
Michael Stenta 2a992f5a7c Filter out assets that the user can't view. 2024-01-18 15:08:35 -05:00
Michael Stenta f1c6a47846 Prevent users without asset edit access from moving them in location hierarchy. 2024-01-18 14:41:21 -05:00
Michael Stenta 31c5280662 Fix duplicated revision tab on entities #773 2024-01-18 06:25:14 -05:00
Michael Stenta 8091b51561 Update satellite map layer instructions in installation guide.
We no longer include Google Maps layers in farmOS core.
Simplify the docs by only describing how to enable Mapbox
layers.
2024-01-18 06:22:31 -05:00
Michael Stenta ca3bc10c7d Add Mapbox module to /setup/modules. 2024-01-18 06:22:31 -05:00
Michael Stenta 83b15287ba Update Drupal core to 10.2.2. 2024-01-17 15:41:04 -05:00
Michael Stenta 8f73c593a1 Fix Error: Call to a member function label() on null in farm_ui_views_views_pre_render().
These errors generally only happen for admins editing Views
through the UI. This makes the code more defensive.
2024-01-16 17:28:59 -05:00
Paul Weidner 17b792aca7 Patch drupal/core to fix Issue #3414883: datetime_timestamp widget does not use default field value #771
The datetime_timestamp widget does not use default field value.
This means that the timestamp field in the log form does not have a
default value and requires the user to input a value before submitting the form
2024-01-16 14:24:14 -05:00
Michael Stenta 68ae993c41 Add min/max options to integer fields in farm_field.factory #768 2024-01-12 05:51:45 -05:00
Michael Stenta 5f09c940c1 Use paratest to speedup tests in Github actions workflow #762 2024-01-11 13:02:52 -05:00
wotnak 8c1fd8fe11 Install pg_trgm PostgreSQL extension before starting tests to avoid race conditions when trying to automatically install it in concurrently run tests 2024-01-11 13:02:27 -05:00
wotnak 8ce641adc5 Revert "Use phpunit instead of paratest."
This reverts commit 6a9c872e9b.
2024-01-11 13:00:57 -05:00
Michael Stenta df0c25f047 Add step to configurable quick form documentation that describes setting #default_value. 2024-01-10 08:18:02 -05:00
Michael Stenta 1d6804d7b4 Remove accidentally merged placeholder text from docs/hosting/migration.md. 2024-01-10 08:17:25 -05:00
32 changed files with 1052 additions and 43 deletions

View File

@ -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
}'

View File

@ -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

View File

@ -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"

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -25,6 +25,7 @@ included with farmOS define the following Asset types:
- Sensor
- Water
- Material
- Product
- Group&ast;
&ast;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

View File

@ -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

View File

@ -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'),

View File

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_product
id: product
label: Product
description: ''
workflow: asset_default
new_revision: true

View File

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_product
id: asset_product
color: blue
conditions:
asset_type:
- product

View File

@ -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

View File

@ -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;
}
}

View File

@ -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',

View File

@ -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

View File

@ -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+

View File

@ -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);
}
}
}

View File

@ -1,6 +1,9 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_ui_map
module:
- asset
id: asset.map_popup

View File

@ -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',

View File

@ -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);
}

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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');
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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