Cut 0.4.0: document the control entities and ship the dashboard
Rebuilds the README entity docs around the distinction that kept causing confusion — the produce box is chosen, its contents are read-only, and only add-ons are a list you edit — and re-exports the dashboard view now running, including the date labels that stop the to-do list and the totals reading as a contradiction.
This commit is contained in:
@@ -5,6 +5,30 @@ All notable changes to this project are documented here.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.4.0] - 2026-08-03
|
||||
|
||||
Control, not just reporting: the box can now be managed from Home Assistant.
|
||||
|
||||
### Added
|
||||
|
||||
- Skip and restore, donate, add/remove add-ons, subscribe/unsubscribe,
|
||||
vacation holds, and produce-box switching — every reversible one verified by
|
||||
a live round trip that returned the account to its prior state.
|
||||
- Entities for each: a produce-box select, a skip switch, a donate button, an
|
||||
add-ons to-do list the built-in conversation agent can drive, and sensors for
|
||||
subscriptions and vacation holds.
|
||||
- A `freshharvest_action` event after every write, carrying action/success/
|
||||
target/detail so automations can notify on the outcome.
|
||||
- A four-section dashboard view with the controls in place, exported to
|
||||
`examples/dashboard-view.yaml`.
|
||||
|
||||
### Notes
|
||||
|
||||
- Donating is the one action never executed: it cannot be undone, so it will be
|
||||
proven the first time there is a box actually worth giving away.
|
||||
- Entities are not exposed to the conversation agent by default; that is the
|
||||
operator's call.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
@@ -32,6 +32,26 @@ The next delivery and the *open* order are usually two different deliveries.
|
||||
Once an order passes its cutoff it locks for packing, and the cart you can still
|
||||
edit is the following week's — so both are exposed separately.
|
||||
|
||||
Three things arrive in a delivery and only one is a list you edit:
|
||||
|
||||
| | What it is | Entity |
|
||||
| --- | --- | --- |
|
||||
| **Produce box** | Chosen, not assembled. Ten options. | `select.fresh_harvest_produce_box` |
|
||||
| **Box contents** | Fresh Harvest fills it; read-only. | `produce` attr of `..._next_delivery_items` |
|
||||
| **Add-ons** | Yours to add and remove. | `todo.fresh_harvest_add_ons` |
|
||||
|
||||
### Controls
|
||||
|
||||
| Entity | Notes |
|
||||
| --- | --- |
|
||||
| `select.fresh_harvest_produce_box` | Switches the **next delivery only**, not the standing order |
|
||||
| `switch.fresh_harvest_skip_next_order` | Skip, and turn back off to restore |
|
||||
| `button.fresh_harvest_donate_next_order` | Donates the box. **Not reversible** |
|
||||
| `todo.fresh_harvest_add_ons` | Add/remove items; names resolve via the site's search index |
|
||||
|
||||
Home Assistant's built-in conversation agent can drive the to-do list through
|
||||
`HassListAddItem`, so no bespoke voice work is needed.
|
||||
|
||||
### The order arriving next
|
||||
|
||||
| Entity | Example |
|
||||
@@ -45,11 +65,6 @@ edit is the following week's — so both are exposed separately.
|
||||
| `sensor.fresh_harvest_next_delivery_fee` | `5.99` |
|
||||
| `sensor.fresh_harvest_next_delivery_items` | `11` |
|
||||
|
||||
`next_delivery_items` carries the contents as attributes: `produce`, `add_ons`
|
||||
(each with quantity, unit and extended price), `produce_count`, `add_ons_count`
|
||||
and `box`. The totals sensor carries `driver_tip` and `bounty_savings`, which
|
||||
are optional or promotional rather than charges.
|
||||
|
||||
### The order you can still change
|
||||
|
||||
| Entity | Example |
|
||||
@@ -61,15 +76,21 @@ are optional or promotional rather than charges.
|
||||
| `sensor.fresh_harvest_shopping_window` | `Shop tomorrow` |
|
||||
|
||||
`binary_sensor.fresh_harvest_order_open` is the one to automate on: it turns off
|
||||
when the cutoff passes, which is the last moment to add anything to the box.
|
||||
`shopping_window` reads `closed` when nothing is changeable — distinct from
|
||||
unknown.
|
||||
when the cutoff passes, the last moment to change the box.
|
||||
|
||||
### The account
|
||||
|
||||
| Entity | Example |
|
||||
| --- | --- |
|
||||
| `sensor.fresh_harvest_delivery_day` | `Tuesdays` |
|
||||
| `sensor.fresh_harvest_subscriptions` | `1`, with each standing order in attributes |
|
||||
| `sensor.fresh_harvest_vacation_holds` | `0`, with ranges in attributes |
|
||||
|
||||
## Events
|
||||
|
||||
Every write action fires `freshharvest_action` with `action`, `success`,
|
||||
`target` and `detail`, so an automation can notify on an add succeeding or a
|
||||
skip failing.
|
||||
|
||||
## Consistency guarantees
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
{
|
||||
"domain": "freshharvest",
|
||||
"name": "Fresh Harvest",
|
||||
"codeowners": ["@sudolulo"],
|
||||
"codeowners": [
|
||||
"@sudolulo"
|
||||
],
|
||||
"config_flow": true,
|
||||
"documentation": "https://github.com/sudolulo/ha-freshharvest",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/sudolulo/ha-freshharvest/issues",
|
||||
"requirements": ["beautifulsoup4>=4.12"],
|
||||
"version": "0.3.0"
|
||||
"requirements": [
|
||||
"beautifulsoup4>=4.12"
|
||||
],
|
||||
"version": "0.4.0"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
# Fresh Harvest dashboard view.
|
||||
#
|
||||
# Four sections: the next delivery, its cost broken down per line, what is in
|
||||
# the box, and the order that can still be changed.
|
||||
# Four sections: the next delivery, what is in the box, the controls, and the
|
||||
# add-ons you can still edit.
|
||||
#
|
||||
# The split matters. The produce box is CHOSEN (a select), its contents are
|
||||
# read-only, and only add-ons are add/removable (a to-do list). The to-do list
|
||||
# edits the OPEN order while the totals describe the ARRIVING one, which is why
|
||||
# each is labelled with its date -- unlabelled, they read as a contradiction.
|
||||
#
|
||||
# To use it, open your dashboard, choose "Edit dashboard" -> "Raw configuration
|
||||
# editor", and paste this under `views:`. It needs the entities this
|
||||
# integration creates and nothing else — no custom cards.
|
||||
# editor", and paste this under `views:`. It needs only the entities this
|
||||
# integration creates -- no custom cards.
|
||||
|
||||
type: sections
|
||||
max_columns: 4
|
||||
@@ -44,37 +49,6 @@ sections:
|
||||
name: Items
|
||||
icon: mdi:basket-check
|
||||
color: light-green
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Cost Breakdown
|
||||
heading_style: title
|
||||
icon: mdi:receipt-text-outline
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_next_delivery_box_price
|
||||
name: Produce box
|
||||
icon: mdi:package-variant
|
||||
color: green
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_next_delivery_add_ons
|
||||
name: Add-ons
|
||||
icon: mdi:cart-plus
|
||||
color: purple
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_next_delivery_subtotal
|
||||
name: Subtotal
|
||||
icon: mdi:calculator
|
||||
color: grey
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_next_delivery_tax
|
||||
name: Tax
|
||||
icon: mdi:bank
|
||||
color: grey
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_next_delivery_fee
|
||||
name: Delivery fee
|
||||
icon: mdi:truck-outline
|
||||
color: grey
|
||||
- type: markdown
|
||||
content: |-
|
||||
{%- set fee = states('sensor.fresh_harvest_next_delivery_fee') | float(-1) -%}
|
||||
@@ -112,24 +86,31 @@ sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Still Open
|
||||
heading: Manage
|
||||
heading_style: title
|
||||
icon: mdi:cart-arrow-right
|
||||
icon: mdi:tune-variant
|
||||
- type: tile
|
||||
entity: binary_sensor.fresh_harvest_order_open
|
||||
name: Can still change
|
||||
icon: mdi:pencil-outline
|
||||
color: amber
|
||||
entity: select.fresh_harvest_produce_box
|
||||
name: Produce box
|
||||
icon: mdi:package-variant-closed
|
||||
color: green
|
||||
features:
|
||||
- type: select-options
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_open_order_delivery
|
||||
name: Delivery
|
||||
icon: mdi:calendar-arrow-right
|
||||
entity: switch.fresh_harvest_skip_next_order
|
||||
name: Skip next order
|
||||
icon: mdi:calendar-remove
|
||||
color: amber
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_shopping_window
|
||||
name: Shopping window
|
||||
icon: mdi:clock-alert-outline
|
||||
color: orange
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_open_order_delivery
|
||||
name: Open order
|
||||
icon: mdi:calendar-arrow-right
|
||||
color: amber
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_open_order_total
|
||||
name: Running total
|
||||
@@ -145,8 +126,57 @@ sections:
|
||||
{%- elif left > 0 -%}
|
||||
The **{{ when }}** order needs **${{ '%.2f' | format(left) }} more** for free delivery.
|
||||
{%- endif -%}
|
||||
- type: tile
|
||||
entity: button.fresh_harvest_donate_next_order
|
||||
name: Donate this box
|
||||
icon: mdi:hand-heart
|
||||
color: pink
|
||||
hide_state: true
|
||||
- type: markdown
|
||||
content: |-
|
||||
Once an order passes its cutoff it locks for packing, and the following week's cart opens.
|
||||
Donating gives the whole box away and **cannot be undone**.
|
||||
|
||||
[Change this order →](https://freshharvest.com/p/dashboard/manage-orders)
|
||||
[Manage on freshharvest.com →](https://freshharvest.com/p/dashboard/manage-orders)
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
heading: Add-ons & Standing Orders
|
||||
heading_style: title
|
||||
icon: mdi:cart-plus
|
||||
- type: markdown
|
||||
content: |-
|
||||
{%- set d = states('sensor.fresh_harvest_open_order_delivery') -%}
|
||||
{%- if d not in ['unknown', 'unavailable', 'none'] -%}
|
||||
Editing the **{{ (d | as_datetime).strftime('%b %-d') }}** order — the one still open.
|
||||
{%- else -%}
|
||||
No order is open for changes right now.
|
||||
{%- endif -%}
|
||||
- type: todo-list
|
||||
entity: todo.fresh_harvest_add_ons
|
||||
display_order: alpha_asc
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_next_delivery_add_ons
|
||||
name: Arriving box add-ons
|
||||
icon: mdi:cart-plus
|
||||
color: purple
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_subscriptions
|
||||
name: Subscriptions
|
||||
icon: mdi:autorenew
|
||||
color: cyan
|
||||
- type: markdown
|
||||
content: |-
|
||||
{%- set items = state_attr('sensor.fresh_harvest_subscriptions', 'items') or [] -%}
|
||||
{%- if items -%}
|
||||
**Repeating every week**
|
||||
{% for i in items %}
|
||||
- {{ i }}
|
||||
{%- endfor %}
|
||||
{%- else -%}
|
||||
_No standing orders beyond the box._
|
||||
{%- endif -%}
|
||||
- type: tile
|
||||
entity: sensor.fresh_harvest_vacation_holds
|
||||
name: Vacation holds
|
||||
icon: mdi:airplane
|
||||
color: blue
|
||||
|
||||
Reference in New Issue
Block a user