diff --git a/README.md b/README.md index fb3784c..85de2ac 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ inventory_summary() | | | |---|---| | **Tools** | `inventory_summary` · `inventory_list` · `inventory_get` · `inventory_upsert_lot` · `inventory_upsert_item` · `inventory_delete_item` · `inventory_set_price` · `inventory_mark_sold` · `inventory_listing` · `inventory_import_csv` · `inventory_export_csv` · `inventory_reprice_plan` · `inventory_stale` | -| **View** | a rail panel: the item grid (double-click to edit name/category/condition/qty; status select; click a price for the Price dialog), Price / Sold / Listing / Edit / Delete per row, an optional **game system** per item (filter, ordering, inline edit with suggestions), multi-select with **Copy as Markdown** grouped `## system` → `### lot` → `- Name — Category — $price` (also ⌘C), lots with their P&L, sales, an activity log, CSV import (file or paste, with the mapping report) and export | +| **View** | a rail panel: the item grid (double-click to edit name/category/condition/qty; status select; click a price for the Price dialog), Price / Sold / Listing / Edit / Delete per row, an optional **game system** per item (filter, ordering, inline edit with suggestions), multi-select with **Copy list** (also ⌘C) — a for-sale post per game system: the system as a header, `(all NoS)` when the group shares a condition, `Name (condition) — $price` per line, pending/listed items under their own marker, lots with their P&L, sales, an activity log, CSV import (file or paste, with the mapping report) and export | | **API** | bearer-gated JSON under `/api/plugins/inventory` — `summary`, `lots`, `items`, `items/{id}/price`, `items/{id}/sold`, `items/{id}/listings`, `listings/{id}/end`, `sales`, `stale`, `audit`, `import`, `export` | | **Automations** | `weekly_review: true` arms a plugin-owned recurring turn (`weekly_review_cron`, default Monday 09:00 in `review_timezone`) that re-prices stale evidence from eBay sold comps through the same tools, reports stale listings with a recommendation (it never changes a listing itself), and posts the per-lot P&L. Cancelled when the plugin is disabled. | | **Events** | `inventory.item.changed`, `inventory.lot.changed`, `inventory.sale.recorded`, `inventory.imported` | diff --git a/api.py b/api.py index 8400813..242fa0d 100644 --- a/api.py +++ b/api.py @@ -40,6 +40,10 @@ async def _lots() -> dict: async def _systems() -> dict: return {"systems": store.systems()} + @r.get("/conditions") + async def _conditions() -> dict: + return {"conditions": store.conditions()} + @r.put("/lots/{lot_id}") async def _put_lot(lot_id: str, body: dict) -> dict: try: diff --git a/protoagent.plugin.yaml b/protoagent.plugin.yaml index ded8f90..46903e7 100644 --- a/protoagent.plugin.yaml +++ b/protoagent.plugin.yaml @@ -1,6 +1,6 @@ id: inventory name: Inventory (resale source of truth) -version: 0.4.0 +version: 0.4.1 description: >- The resale inventory as a source of truth: lots (what you bought and for how much), items (what each lot split into, its condition and status), listings (where each item is diff --git a/pyproject.toml b/pyproject.toml index 1824a8f..7887139 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "inventory-plugin" -version = "0.4.0" +version = "0.4.1" description = "Resale inventory as a source of truth for protoAgent — lots, items, listings, sales, price evidence, audit" requires-python = ">=3.11" diff --git a/store.py b/store.py index 9a945e0..f8fe086 100644 --- a/store.py +++ b/store.py @@ -534,6 +534,12 @@ def systems(self) -> list[str]: rows = con.execute("SELECT DISTINCT system FROM items WHERE system!='' ORDER BY system").fetchall() return [r[0] for r in rows] + def conditions(self) -> list[str]: + """The distinct condition labels in use (NoS, NIB, sealed, painted …) for suggestions.""" + with self._read() as con: + rows = con.execute("SELECT DISTINCT condition FROM items WHERE condition!='' ORDER BY condition").fetchall() + return [r[0] for r in rows] + def find_item(self, *, lot_id: str, name: str) -> dict | None: """The item with this lot + name (case-insensitive) — how a sheet without an id column is matched on re-import instead of minting duplicates.""" diff --git a/tests/test_system.py b/tests/test_system.py index 5569274..0778539 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -63,6 +63,9 @@ def test_api_lists_systems_and_filters(registry): c.post("/api/plugins/inventory/items", json={"id": "B", "name": "b", "system": "Warhammer 40K"}) c.post("/api/plugins/inventory/items", json={"id": "C", "name": "c"}) assert c.get("/api/plugins/inventory/systems").json() == {"systems": ["Blood Bowl", "Warhammer 40K"]} + c.put("/api/plugins/inventory/items/A", json={"condition": "NoS"}) + c.put("/api/plugins/inventory/items/B", json={"condition": "NIB"}) + assert c.get("/api/plugins/inventory/conditions").json() == {"conditions": ["NIB", "NoS"]} assert [ i["id"] for i in c.get("/api/plugins/inventory/items", params={"system": "Blood Bowl"}).json()["items"] ] == ["A"] diff --git a/tests/test_view.py b/tests/test_view.py index cbb5ff8..e1cfab9 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -110,9 +110,10 @@ def test_the_pure_helpers_under_node(): "\nconsole.log(JSON.stringify({ neg: fmt(-12.5), pos: fmt(1234.5), nul: fmt(null), zero: fmt(0), sneg: signed(-3), spos: signed(3), snul: signed(null)," " esc: esc('&\\''), field: field('name', 'Name', '\">'), opt: field('lot_id', 'Lot', '', { type: 'select', options: [['\">', 'x