Wire manual buy/sell to the mammon acquire/consume endpoints
Summary
The mammon backend exposes manual write endpoints (mammon #2): POST /activity/acquire and POST /activity/consume. The acquisition store still has stubbed no-ops from when no write endpoint existed — this issue wires them up, binds every acquisition/consumption to a ledger, adds an Eve-time field to the modals, and removes the now-redundant Scan buy button.
src/market/acquisition/acquisition.ts — "the backend exposes no write endpoint yet, so buy/sell are no-ops."
addAcquiredType(type, quantity, price, source?) → currently {}
removeAcquiredType(id, quantity) → currently {}
The buy/sell UI already exists (BuyModal.vue, SellModal.vue, opened from TypeInfo.vue and AcquisitionsPanel.vue) and already calls these two store actions — so this is wiring + a ledger binding + a date input, not re-adding buttons.
Prerequisites
Regenerate the spec + client — done.docs/mammon-api.yml and src/generated/mammon already contain acquire/consume and the ManualActivityRequest/ActivitySourceRequest schemas, so activityApi.acquire(...) / activityApi.consume(...) exist. src/generated/mammon is generated — do not hand-edit.
Source model — always LEDGER
The old AcquiredTypeSource ('bo' | 'so' | 'prod' | 'misc') was vestigial — defined and only referenced as an unused param. Removed.
Every manual acquisition/consumption is bound to a ledger: the request source is always ActivitySourceRequest = { type: 'LEDGER', characterId: null, ledgerId }. NONE/CHARACTER are not used here; CORPORATION is rejected by the backend anyway.
How the ledger is determined depends on context:
Per-ledger tab (ViewLedgerAcquisitions.vue) — the ledger is fixed to that tab (ledgerId from useLedgerParam()). No select is shown; the modal binds to the tab's ledger directly (passed as the AcquisitionsPanelledgerId prop → modal open()).
TypeInfo.vue and the account-wideAcquisitions.vue panel — no context ledger, so the modal shows a LedgerSelect (@/ledger, v-model-bound to a Ledger, backed by useLedgersStore().ledgers) for the user to pick. The submit button (Add/Remove) is disabled until a ledger is selected.
AcquisitionsPanel gains a ledgerId? prop it forwards to the modals: when set (per-ledger tab) the modal hides the select and uses it; when unset (account-wide) the modal shows the select.
Remove the Scan buy button
The Scan-page buy path is no longer required — removed:
src/market/scan/ScanResultTable.vue — dropped the buttons column (the ShoppingCartIcon cell), the buy emit, the Emits interface, the ShoppingCartIcon import, and the now-dead infoOnly→buttons handling in columnsToIgnore. (TypeInfo.vue keeps its own buy button.)
src/pages/market/Scan.vue — removed the BuyModal import/ref/element and the @buy handler.
Eve-time date field
Both modals gain a datetime-local input labelled "Eve Time" so the user can set when the acquisition/consumption happened, sent as the request's datetime.
EVE time is UTC/GMT. datetime-local is timezone-naive, so it is seeded with UTC-now and its value is read back as UTC — what is shown and stored is Eve time regardless of the browser's zone. Helpers live in AcquiredType.ts:
removeAcquiredType(id, quantity, ledgerId, datetime: Date) → look up the acquisition by id locally to get its marketTypeId, then activityApi.consume({ source: { type: 'LEDGER', characterId: null, ledgerId }, marketTypeId, quantity, unitPrice: 0, datetime: datetime.toISOString(), taxes: null, description: null }). The backend consume records a consumption of a market type (FIFO pools handle the draw-down); it takes no acquisition id. unitPrice is 0 because the sell captures no price and the generated ManualActivityRequest requires the field.
Adds processNewActivities() delegating to activityApi.processNewActivities().
Modals
BuyModal / SellModal own the Eve-time input, and the LedgerSelectonly when no context ledger is supplied; open(...) receives the (optional) context ledgerId and forwards the resolved ledgerId + Date to the store action.
TypeInfo.vue opens BuyModal with no context ledger (select shown); AcquisitionsPanel opens both modals with its ledgerId prop — set → hidden/bound, unset → select shown.
Sell loop:SellModal iterates the acquisitions of the (single) market type, capping each consume at min(remaining-to-sell, pool.remaining). Since consume is by marketTypeId, the per-iteration quantities sum to the requested count (equivalent to a single consume of the total). One processNewActivities() after the batch, not per iteration.
A created activity only appears in GET /acquisitions once processed. After the create call(s) the modal calls activityApi.processNewActivities() (via the store) — one call for the batched sell loop. If mammon #35 (immediate processing on acquire/consume) ships, this explicit call can be dropped.
Frontend auto-refresh was descoped. The originally-planned changed-event / both-context live re-fetch is not implemented — the account-wide page and per-ledger tab already have manual/auto Refresh, so live re-render after a write was left out of scope. AcquisitionsPanel does not emit changed; parents do not re-fetch on write.
AcquiredTypeSource removed; no remaining references.
addAcquiredType records an acquisition via POST /activity/acquire bound to the resolved ledger.
removeAcquiredType records a consumption via POST /activity/consume bound to the resolved ledger (consume by marketTypeId, unitPrice: 0).
Per-ledger tab: no ledger select — bound to the tab's ledger. TypeInfo / account-wide: LedgerSelect shown, submit disabled until a ledger is chosen. Source is always LEDGER(ledgerId).
Both modals expose an "Eve Time"datetime-local input (default Eve/UTC now) whose value is sent as datetime; store actions take a Date.
The Scan buy button is removed (ScanResultTable buttons column + buy emit; Scan.vue modal wiring).
Processing triggered via processNewActivities() (one process for the batched sell loop) so entries surface in GET /acquisitions.
The no write endpoint yet comment is removed.
Refresh updates both the account-wide page and the per-ledger tab — descoped; relies on the existing manual/auto Refresh controls.
Notes / follow-ups
The generated src/generated/mammon/api.ts uses recursive extends interfaces (LedgerResponse, IskTransferResponse, ItemTransferResponse, MainLedgerResponse, …) that vue-tsc rejects — npm run build fails at baseline with ~32 pre-existing errors unrelated to this change. Worth fixing in the mammon-side codegen.
# Wire manual buy/sell to the mammon acquire/consume endpoints
## Summary
The mammon backend exposes manual write endpoints (mammon #2): `POST /activity/acquire` and `POST /activity/consume`. The acquisition store still has stubbed no-ops from when no write endpoint existed — this issue wires them up, binds every acquisition/consumption to a ledger, adds an Eve-time field to the modals, and removes the now-redundant Scan buy button.
- `src/market/acquisition/acquisition.ts` — *"the backend exposes no write endpoint yet, so buy/sell are no-ops."*
- `addAcquiredType(type, quantity, price, source?)` → currently `{}`
- `removeAcquiredType(id, quantity)` → currently `{}`
The buy/sell UI **already exists** (`BuyModal.vue`, `SellModal.vue`, opened from `TypeInfo.vue` and `AcquisitionsPanel.vue`) and already calls these two store actions — so this is *wiring + a ledger binding + a date input*, not re-adding buttons.
## Prerequisites
1. **Regenerate the spec + client** — **done.** `docs/mammon-api.yml` and `src/generated/mammon` already contain `acquire`/`consume` and the `ManualActivityRequest`/`ActivitySourceRequest` schemas, so `activityApi.acquire(...)` / `activityApi.consume(...)` exist. `src/generated/mammon` is generated — do not hand-edit.
## Source model — always LEDGER
The old `AcquiredTypeSource` (`'bo' | 'so' | 'prod' | 'misc'`) was **vestigial** — defined and only referenced as an unused param. **Removed.**
Every manual acquisition/consumption is **bound to a ledger**: the request source is always `ActivitySourceRequest = { type: 'LEDGER', characterId: null, ledgerId }`. `NONE`/`CHARACTER` are not used here; `CORPORATION` is rejected by the backend anyway.
How the ledger is determined depends on context:
- **Per-ledger tab** (`ViewLedgerAcquisitions.vue`) — the ledger is fixed to that tab (`ledgerId` from `useLedgerParam()`). **No select is shown**; the modal binds to the tab's ledger directly (passed as the `AcquisitionsPanel` `ledgerId` prop → modal `open()`).
- **`TypeInfo.vue`** and the **account-wide** `Acquisitions.vue` panel — no context ledger, so the modal shows a **`LedgerSelect`** (`@/ledger`, `v-model`-bound to a `Ledger`, backed by `useLedgersStore().ledgers`) for the user to pick. The submit button (Add/Remove) is **disabled until a ledger is selected**.
`AcquisitionsPanel` gains a `ledgerId?` prop it forwards to the modals: when set (per-ledger tab) the modal hides the select and uses it; when unset (account-wide) the modal shows the select.
## Remove the Scan buy button
The Scan-page buy path is no longer required — **removed**:
- `src/market/scan/ScanResultTable.vue` — dropped the `buttons` column (the `ShoppingCartIcon` cell), the `buy` emit, the `Emits` interface, the `ShoppingCartIcon` import, and the now-dead `infoOnly`→`buttons` handling in `columnsToIgnore`. (`TypeInfo.vue` keeps its own buy button.)
- `src/pages/market/Scan.vue` — removed the `BuyModal` import/ref/element and the `@buy` handler.
## Eve-time date field
Both modals gain a `datetime-local` input labelled **"Eve Time"** so the user can set when the acquisition/consumption happened, sent as the request's `datetime`.
- EVE time is UTC/GMT. `datetime-local` is timezone-naive, so it is **seeded with UTC-now** and its value is **read back as UTC** — what is shown and stored is Eve time regardless of the browser's zone. Helpers live in `AcquiredType.ts`:
- `toEveDatetimeInput(date)` → `date.toISOString().slice(0, 16)` (seed value)
- `fromEveDatetimeInput(value)` → `new Date(\`${value}Z\`)` — returns a **`Date`**
- Both modals default the field to now on `open()`.
- The store actions take a **`Date`** (not a pre-formatted string) and call `.toISOString()` at the request boundary.
## Change
- **`acquisition.ts`**
- Remove `AcquiredTypeSource`.
- `addAcquiredType(type, quantity, price, ledgerId, datetime: Date)` → `activityApi.acquire({ source: { type: 'LEDGER', characterId: null, ledgerId }, marketTypeId: type, quantity, unitPrice: price, datetime: datetime.toISOString(), taxes: null, description: null })`.
- `removeAcquiredType(id, quantity, ledgerId, datetime: Date)` → look up the acquisition by `id` locally to get its `marketTypeId`, then `activityApi.consume({ source: { type: 'LEDGER', characterId: null, ledgerId }, marketTypeId, quantity, unitPrice: 0, datetime: datetime.toISOString(), taxes: null, description: null })`. The backend consume records a consumption of a *market type* (FIFO pools handle the draw-down); it takes no acquisition id. `unitPrice` is `0` because the sell captures no price and the generated `ManualActivityRequest` requires the field.
- Adds `processNewActivities()` delegating to `activityApi.processNewActivities()`.
- **Modals**
- `BuyModal` / `SellModal` own the Eve-time input, and the `LedgerSelect` **only when no context ledger is supplied**; `open(...)` receives the (optional) context `ledgerId` and forwards the resolved `ledgerId` + `Date` to the store action.
- `TypeInfo.vue` opens `BuyModal` with no context ledger (select shown); `AcquisitionsPanel` opens both modals with its `ledgerId` prop — set → hidden/bound, unset → select shown.
- **Sell loop:** `SellModal` iterates the acquisitions of the (single) market type, capping each `consume` at `min(remaining-to-sell, pool.remaining)`. Since consume is by `marketTypeId`, the per-iteration quantities sum to the requested count (equivalent to a single consume of the total). One `processNewActivities()` after the batch, not per iteration.
### Request body (`ManualActivityRequest`)
`source` (always `{ type: 'LEDGER', characterId: null, ledgerId }`), `marketTypeId`, `quantity`, `unitPrice`, `datetime` (user-set, Eve/UTC), `taxes: null`, `description: null`.
### Processing (make it show up in the list)
A created activity only appears in `GET /acquisitions` once processed. After the create call(s) the modal calls `activityApi.processNewActivities()` (via the store) — one call for the batched sell loop. If mammon #35 (immediate processing on acquire/consume) ships, this explicit call can be dropped.
**Frontend auto-refresh was descoped.** The originally-planned `changed`-event / both-context live re-fetch is **not** implemented — the account-wide page and per-ledger tab already have manual/auto Refresh, so live re-render after a write was left out of scope. `AcquisitionsPanel` does **not** emit `changed`; parents do not re-fetch on write.
## Acceptance criteria
- [x] `docs/mammon-api.yml` + `src/generated/mammon` contain `acquire`/`consume` (already regenerated externally); `activityApi.acquire` / `activityApi.consume` available.
- [x] `AcquiredTypeSource` removed; no remaining references.
- [x] `addAcquiredType` records an acquisition via `POST /activity/acquire` bound to the resolved ledger.
- [x] `removeAcquiredType` records a consumption via `POST /activity/consume` bound to the resolved ledger (consume by `marketTypeId`, `unitPrice: 0`).
- [x] Per-ledger tab: **no ledger select** — bound to the tab's ledger. TypeInfo / account-wide: `LedgerSelect` shown, submit disabled until a ledger is chosen. Source is always `LEDGER(ledgerId)`.
- [x] Both modals expose an **"Eve Time"** `datetime-local` input (default Eve/UTC now) whose value is sent as `datetime`; store actions take a `Date`.
- [x] The Scan buy button is removed (`ScanResultTable` buttons column + `buy` emit; `Scan.vue` modal wiring).
- [x] Processing triggered via `processNewActivities()` (one process for the batched sell loop) so entries surface in `GET /acquisitions`.
- [x] The `no write endpoint yet` comment is removed.
- [x] ~~Refresh updates both the account-wide page and the per-ledger tab~~ — **descoped**; relies on the existing manual/auto Refresh controls.
## Notes / follow-ups
- The generated `src/generated/mammon/api.ts` uses recursive `extends` interfaces (`LedgerResponse`, `IskTransferResponse`, `ItemTransferResponse`, `MainLedgerResponse`, …) that `vue-tsc` rejects — `npm run build` fails at baseline with ~32 pre-existing errors unrelated to this change. Worth fixing in the mammon-side codegen.
## References
- mammon #2 — manual acquire/consume endpoints (shipped, create-only).
- mammon #35 — process activities immediately on manual acquire/consume (would remove the explicit `processNewActivities` call).
- `src/market/acquisition/{acquisition.ts,AcquiredType.ts,BuyModal.vue,SellModal.vue,AcquisitionsPanel.vue}` — store, helpers, modals + panel.
- `src/ledger/LedgerSelect.vue`, `useLedgersStore` / `useLedgerParam` (`src/ledger/ledger.ts`) — ledger select + context.
- `src/market/scan/ScanResultTable.vue`, `src/pages/market/Scan.vue` — Scan buy button removed.
- `src/pages/market/TypeInfo.vue`, `src/pages/market/Acquisitions.vue`, `src/pages/ledger/ViewLedgerAcquisitions.vue` — open sites.
- `src/mammon/mammonService.ts` — `activityApi`, `acquisitionApi` instances.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Wire manual buy/sell to the mammon acquire/consume endpoints
Summary
The mammon backend exposes manual write endpoints (mammon #2):
POST /activity/acquireandPOST /activity/consume. The acquisition store still has stubbed no-ops from when no write endpoint existed — this issue wires them up, binds every acquisition/consumption to a ledger, adds an Eve-time field to the modals, and removes the now-redundant Scan buy button.src/market/acquisition/acquisition.ts— "the backend exposes no write endpoint yet, so buy/sell are no-ops."addAcquiredType(type, quantity, price, source?)→ currently{}removeAcquiredType(id, quantity)→ currently{}The buy/sell UI already exists (
BuyModal.vue,SellModal.vue, opened fromTypeInfo.vueandAcquisitionsPanel.vue) and already calls these two store actions — so this is wiring + a ledger binding + a date input, not re-adding buttons.Prerequisites
docs/mammon-api.ymlandsrc/generated/mammonalready containacquire/consumeand theManualActivityRequest/ActivitySourceRequestschemas, soactivityApi.acquire(...)/activityApi.consume(...)exist.src/generated/mammonis generated — do not hand-edit.Source model — always LEDGER
The old
AcquiredTypeSource('bo' | 'so' | 'prod' | 'misc') was vestigial — defined and only referenced as an unused param. Removed.Every manual acquisition/consumption is bound to a ledger: the request source is always
ActivitySourceRequest = { type: 'LEDGER', characterId: null, ledgerId }.NONE/CHARACTERare not used here;CORPORATIONis rejected by the backend anyway.How the ledger is determined depends on context:
ViewLedgerAcquisitions.vue) — the ledger is fixed to that tab (ledgerIdfromuseLedgerParam()). No select is shown; the modal binds to the tab's ledger directly (passed as theAcquisitionsPanelledgerIdprop → modalopen()).TypeInfo.vueand the account-wideAcquisitions.vuepanel — no context ledger, so the modal shows aLedgerSelect(@/ledger,v-model-bound to aLedger, backed byuseLedgersStore().ledgers) for the user to pick. The submit button (Add/Remove) is disabled until a ledger is selected.AcquisitionsPanelgains aledgerId?prop it forwards to the modals: when set (per-ledger tab) the modal hides the select and uses it; when unset (account-wide) the modal shows the select.Remove the Scan buy button
The Scan-page buy path is no longer required — removed:
src/market/scan/ScanResultTable.vue— dropped thebuttonscolumn (theShoppingCartIconcell), thebuyemit, theEmitsinterface, theShoppingCartIconimport, and the now-deadinfoOnly→buttonshandling incolumnsToIgnore. (TypeInfo.vuekeeps its own buy button.)src/pages/market/Scan.vue— removed theBuyModalimport/ref/element and the@buyhandler.Eve-time date field
Both modals gain a
datetime-localinput labelled "Eve Time" so the user can set when the acquisition/consumption happened, sent as the request'sdatetime.datetime-localis timezone-naive, so it is seeded with UTC-now and its value is read back as UTC — what is shown and stored is Eve time regardless of the browser's zone. Helpers live inAcquiredType.ts:toEveDatetimeInput(date)→date.toISOString().slice(0, 16)(seed value)fromEveDatetimeInput(value)→new Date(\${value}Z`)— returns a **Date`**open().Date(not a pre-formatted string) and call.toISOString()at the request boundary.Change
acquisition.tsAcquiredTypeSource.addAcquiredType(type, quantity, price, ledgerId, datetime: Date)→activityApi.acquire({ source: { type: 'LEDGER', characterId: null, ledgerId }, marketTypeId: type, quantity, unitPrice: price, datetime: datetime.toISOString(), taxes: null, description: null }).removeAcquiredType(id, quantity, ledgerId, datetime: Date)→ look up the acquisition byidlocally to get itsmarketTypeId, thenactivityApi.consume({ source: { type: 'LEDGER', characterId: null, ledgerId }, marketTypeId, quantity, unitPrice: 0, datetime: datetime.toISOString(), taxes: null, description: null }). The backend consume records a consumption of a market type (FIFO pools handle the draw-down); it takes no acquisition id.unitPriceis0because the sell captures no price and the generatedManualActivityRequestrequires the field.processNewActivities()delegating toactivityApi.processNewActivities().Modals
BuyModal/SellModalown the Eve-time input, and theLedgerSelectonly when no context ledger is supplied;open(...)receives the (optional) contextledgerIdand forwards the resolvedledgerId+Dateto the store action.TypeInfo.vueopensBuyModalwith no context ledger (select shown);AcquisitionsPanelopens both modals with itsledgerIdprop — set → hidden/bound, unset → select shown.SellModaliterates the acquisitions of the (single) market type, capping eachconsumeatmin(remaining-to-sell, pool.remaining). Since consume is bymarketTypeId, the per-iteration quantities sum to the requested count (equivalent to a single consume of the total). OneprocessNewActivities()after the batch, not per iteration.Request body (
ManualActivityRequest)source(always{ type: 'LEDGER', characterId: null, ledgerId }),marketTypeId,quantity,unitPrice,datetime(user-set, Eve/UTC),taxes: null,description: null.Processing (make it show up in the list)
A created activity only appears in
GET /acquisitionsonce processed. After the create call(s) the modal callsactivityApi.processNewActivities()(via the store) — one call for the batched sell loop. If mammon #35 (immediate processing on acquire/consume) ships, this explicit call can be dropped.Frontend auto-refresh was descoped. The originally-planned
changed-event / both-context live re-fetch is not implemented — the account-wide page and per-ledger tab already have manual/auto Refresh, so live re-render after a write was left out of scope.AcquisitionsPaneldoes not emitchanged; parents do not re-fetch on write.Acceptance criteria
docs/mammon-api.yml+src/generated/mammoncontainacquire/consume(already regenerated externally);activityApi.acquire/activityApi.consumeavailable.AcquiredTypeSourceremoved; no remaining references.addAcquiredTyperecords an acquisition viaPOST /activity/acquirebound to the resolved ledger.removeAcquiredTyperecords a consumption viaPOST /activity/consumebound to the resolved ledger (consume bymarketTypeId,unitPrice: 0).LedgerSelectshown, submit disabled until a ledger is chosen. Source is alwaysLEDGER(ledgerId).datetime-localinput (default Eve/UTC now) whose value is sent asdatetime; store actions take aDate.ScanResultTablebuttons column +buyemit;Scan.vuemodal wiring).processNewActivities()(one process for the batched sell loop) so entries surface inGET /acquisitions.no write endpoint yetcomment is removed.Refresh updates both the account-wide page and the per-ledger tab— descoped; relies on the existing manual/auto Refresh controls.Notes / follow-ups
src/generated/mammon/api.tsuses recursiveextendsinterfaces (LedgerResponse,IskTransferResponse,ItemTransferResponse,MainLedgerResponse, …) thatvue-tscrejects —npm run buildfails at baseline with ~32 pre-existing errors unrelated to this change. Worth fixing in the mammon-side codegen.References
processNewActivitiescall).src/market/acquisition/{acquisition.ts,AcquiredType.ts,BuyModal.vue,SellModal.vue,AcquisitionsPanel.vue}— store, helpers, modals + panel.src/ledger/LedgerSelect.vue,useLedgersStore/useLedgerParam(src/ledger/ledger.ts) — ledger select + context.src/market/scan/ScanResultTable.vue,src/pages/market/Scan.vue— Scan buy button removed.src/pages/market/TypeInfo.vue,src/pages/market/Acquisitions.vue,src/pages/ledger/ViewLedgerAcquisitions.vue— open sites.src/mammon/mammonService.ts—activityApi,acquisitionApiinstances.