Creating and editing a price¶
Prices are entered in a dialog. The fields come from field management (table
prices), so they are configurable per installation — custom fields appear
automatically.
Permission: price_edit
Entry points¶
- Create price — button at the top right of the price list
- Edit — row action in the list or on the detail page
- A cell in the price matrix — creates or edits with the device type and price group already filled in
Form fields¶
| Field | Required | Description |
|---|---|---|
| Device type | Yes | Device type the price applies to |
| Price group | Yes | Customer segment the price applies to |
| Price | Yes | Amount per unit |
| Complexity | — | Effort level (pick list, ISO/DAkkS calibration by default) |
| Start date / End date | — | Validity period; empty means open-ended |
| Quantity / Unit | — | Reference quantity (fractions allowed) and unit |
| VAT | Yes | Tax rate in percent |
| Discount | — | Discount in percent |
| Price min / Price max | — | Price range (0 means "not set") |
| Costs | — | Internal costs |
Which fields appear and which are mandatory is controlled by field management.
By default Price min and Discount are read-only, matching the V1 setup.
Rules on save¶
- One row per combination. Device type, price group, start date and complexity may occur together only once. Two rows in the same slot would force price resolution to pick one of them arbitrarily.
- Plausible validity period. The end date must not precede the start date — such a window would never be valid.
- Plausible price range. If both minimum and maximum are set, the minimum must not exceed the maximum.
- Amounts must not be negative.
Partial updates are checked against the stored record: changing only the end date is still validated against the start date already on file.
Validity periods¶
| Setup | Meaning |
|---|---|
| No dates | Valid indefinitely |
| Start date only | Valid from that day onwards |
| Start and end | Valid within the period only |
If more than one row is valid on a day, the more specific one wins: a dated row beats an open-ended one, and a later start date beats an earlier one. The result is identical on every supported database.
End dates from legacy data: 2099-12-31
Where a V1 form demanded an end date that does not exist in business terms, a date that never arrives was often entered instead — usually 2099-12-31. V2 reads that as open-ended: list, grid and printed document show nothing there, and the row ranks correctly, namely as open-ended.
The value stays in the database on purpose: V1 selects prices for a booking
via end_date >= today and would no longer find a cleared row. If you have
switched V1 off and want to clean up for real, use
php artisan prices:clear-open-end-sentinel — without --apply it only
reports.
Treat a price change as a new validity
For a price increase, do not overwrite the existing price. Duplicate it, set an end date on the original and a start date on the copy. That keeps it traceable which price applied when.
Duplicating¶
The Duplicate row action opens a pre-filled dialog. The copy needs at least one difference in price group, start date or complexity — otherwise it occupies the same combination as the original and is rejected.
The API can copy with overrides directly:
Effect on existing orders¶
Order positions copy the price when they are created. Later changes to the price list do not apply retroactively. Inside an order the Refresh prices button deliberately pulls the current values.
API¶
POST /api/v2/prices
{
"type_id": "…",
"category_id": "…",
"price": 85.00,
"tax": 19,
"unit": "EA",
"complexity": "ISO-Kalibrierung",
"start_date": "2026-01-01",
"end_date": null
}
PUT /api/v2/prices/{id}
Device type and price group can also be changed afterwards, to move a row that was filed under the wrong type.
Permissions¶
Creating, editing and duplicating require price_edit; deleting requires
price_delete.