ResearchOS/Wiki

Purchases & Funding

Track every dollar your lab spends and check grant burn in one view. The Purchases page pairs a flat reverse-chronological list of every purchase order with a live spending dashboard, so you can scan recent activity and check budget burn without leaving the page.

Header, filter chips, and the unified order list. The amber '+ New Purchase' button and the segmented filter chips were added in the 2026-05-22 Purchases redesign.

Cost transparency, not a filing cabinet

A purchase order in ResearchOS is a task with its task_type set to purchase. Each row on the page is one of those tasks, and inside each row is a small spreadsheet of line items (the actual things you bought). The page is built around lookup, not data entry. Lab purchase lifecycles are short, often only a few days from order to arrival, so the usual question is “did we already buy that primer?”

The header at the top reads Purchases · N orders · $X.XX total, where the total comes from every line item across every order visible to you. To the right is the Manage Funding Accounts button, which opens a popup where you can add or edit grant codes without leaving the page.

Creating a purchase

The amber + New Purchase button in the top-right of the page header opens NewPurchaseModal. The modal collapses the two-step data model (parent purchase task + first line item) into a single form, so the common case of logging one item takes one interaction instead of two.

Reordering lives right here in this flow, not in a separate floating button anywhere else in the app. Above the Item Name field is a recently ordered row, a few one-tap chips for the items you bought most recently, newest first. Tap one and the modal pre-fills the name, vendor, and price per unit from that item's most-recent record so you can restock a primer or a reagent without typing. Quantity stays at 1 and the funding string is left untouched, since a re-buy often re-bills against a different grant. If you would rather type, the Item Name field's autocomplete (described below) recalls the same history.

The modal has six fields.

  • Item Name (required): a native <datalist>autocomplete surfaces every distinct item name from your purchase history, de-duped case-insensitively with the most-recent record winning. When the typed value matches a prior item name exactly, Vendor and Price per unit fill in automatically. Quantity stays at 1, and the funding string stays unchanged (recurring purchases often re-bill against a different grant).
  • Vendor: free text, optional. Not auto-filled unless an exact Item Name match triggers the recall logic above.
  • Category: a <select> listing your non-archived, non-shared owned projects plus a synthetic Miscellaneous option at the bottom. Defaults to the first owned project alphabetically, falling back to Miscellaneous if you have no owned projects yet. Choosing Miscellaneous routes the purchase to the hidden _misc_purchases bucket (see below).
  • Price per unit and Quantity: numeric free-text inputs. Price defaults blank, and quantity defaults to 1.
  • Funding string: a <datalist>autocomplete pulls from your existing funding accounts (for example, NIH-R01-12345). Typing a new string and saving creates a budget-zero funding account automatically so the string appears in future dropdowns without a separate setup step. The field is optional, so leave it blank to record the purchase without a funding association.

On save, the modal creates a task_type: "purchase" parent task dated today with a one-day duration, then immediately creates the first line item under it. After saving you can expand the row in the list and use the inline PurchaseEditor to add more line items to the same order.

The unified scroll

Above the order list are two rows of chips. The first is a category segmented control with four chips that scope what you see. All shows every purchase task and is always visible. Project purchases filters to orders attached to a real project, hiding the Miscellaneous bucket. Miscellaneous shows only the ad-hoc purchases in the hidden _misc_purchases bucket. This chip is hidden entirely when miscTaskCount === 0 so a freshly-onboarded account does not see a confusing empty bucket. The fourth chip is Awaiting approval for lab members (relabeled Pending approval for lab heads, who own that queue), scoping the list to purchases still waiting on a PI decision. Each chip displays a live count badge that reflects the full purchase list, not just the filtered view, so the badge numbers stay stable as you switch tabs.

Below the category chips is a separate Ordering chip row that filters by where each order sits in its lifecycle (Any stage, Needs ordering, Ordered, Received). It composes with the category chips above, and the lifecycle itself is described in the next section.

The list itself is a single column sorted by start date, newest first. There is no active vs earlier split. A completed order looks almost identical to an in-flight one, with the same row, the same colors, a small green dot, and the text · Complete appended to the metadata line. The row itself is not tinted, so completed orders don't dominate the page once most of your purchases have landed.

Click any row to expand it into a line-item table. Each row is one thing you are buying with columns for item name, quantity, vendor link, price per unit, shipping, computed total, the funding account paying for it, plus the Vendor and Category columns. A line item carries two distinct identifiers, the CAS (the chemical identity of a reagent) and the catalog number (the vendor's ordering number you type back into their site to reorder), so a chemical and the part number you buy it under stay separate. The blue-tinted row at the bottom is empty and waiting for a new line. The round green checkmark at the bottom-right of the expanded view marks the whole order complete, and the red trash icon next to it deletes the entire order plus every line item under it.

An expanded order. Vendor and category are first-class columns, and both inputs draw from a datalist of values used elsewhere in your purchase history.

The Miscellaneous bucket

The existing data model requires every purchase task to have a project_id. Purchases that do not belong to any real project, like conference flights, lab snacks, or one-off equipment, still need somewhere to land. Rather than making project_id nullable and updating every reader (Gantt, Workbench, search, activity log), ResearchOS creates a per-user hidden project named _misc_purchases and routes those purchases there.

The hidden project never appears on Home, Workbench, Gantt, the project picker, or anywhere in the app that calls fetchAllProjectsIncludingShared without explicitly opting in. The /purchases route is the only surface that passes { includeHidden: true }, so the bucket is visible (and filterable) there but invisible everywhere else.

Vendor and category as first-class fields

Lab purchasing has heavy vendor reuse. The same supplier shows up across dozens of orders, often spelled three different ways. To make that data useful for analytics, every line item now carries a vendor and a category, both stored as nullable free-text strings on PurchaseItem. They are free text, not enums, since lab conventions vary too much for a fixed taxonomy.

Each input is wired to a <datalist> sourced from purchasesApi.listAllIncludingShared, deduplicated, non-null values only. So the first time you type Sigma into a vendor cell, the dropdown surfaces every distinct vendor you (or anyone sharing with you) has typed before. Suggestions update as new values are added elsewhere, including in shared purchase tasks. There is no central catalog to maintain. The data you enter is the catalog.

Order lifecycle

A purchase is not done the moment you log it. Every line item carries an ordering status that moves through three stages, Needs ordering, then Ordered, then Received. A fresh item starts at Needs ordering, and older records with no status read as Needs ordering too, so the field is always present.

Each line item shows a small colored status chip (gray for Needs ordering, blue for Ordered, green for Received) with forward and back arrows next to it. Click the forward arrow to advance a stage or the back arrow to revert one. The control is available to any lab member, so whoever actually places the order can mark it without waiting on a PI edit session. You can also assign a line item to a labmate to place the order for you, which fires a bell to that person so the request does not get lost in conversation. The Ordering chip row above the list (Any stage, Needs ordering, Ordered, Received) filters the whole page to orders containing an item in the chosen stage.

Document attachments

Every line item in an expanded order has a thin Documents sub-row beneath it. In edit mode, an Attach PDF control lets you upload a document for that item, and each attached file gets a kind label you can change from a dropdown. The four kinds are Order form, Invoice, Receipt, and Quote. Attached files are shown as clickable chips that open in a new tab. Remove an attachment with the close button in edit mode. The Documents row is hidden entirely when an item has no attachments and is not being edited, so it adds no visual noise to clean rows.

Once you have ordered or received a line item, the page checks whether any of those items still have no document attached. When there are some, a gentle amber nudge appears above the list reading something like N purchases have no document attached. Attach receipts to keep the grant record complete. It is informational only and never blocks any action.

Send to department

For PI accounts that have department routing configured in their settings, each approved purchase line item gains a Send to department control in its Documents row. Clicking it opens your OS mail client with a pre-drafted email addressed to the configured department contact and pre-filled with the item name, vendor, grant string, and total. No email is sent from within ResearchOS, and no credentials are stored. The mailto opens the PI's own mail client so the PI reviews and sends the message directly. When more than one department contact is configured, a short dropdown lets you pick which recipient before clicking.

Buy again

Reagents run out and get reordered on a loop. On any Received line item, a one-click Buy again control creates a brand-new purchase order that copies the item's name, vendor, CAS or accession, link, price, and quantity, and starts it back at Needs ordering. No form, no retyping. The reorder routes to the same project as the original when known, otherwise to the Miscellaneous bucket, and it re-enters the normal needs-ordering and approval pipeline from the top.

The loose model and the soft warning

ResearchOS does not block you from attaching a purchase item to a task whose type is not purchase. That is on purpose. Reagents often tie to a specific experiment task, and you don't want to lose that experiment-to-spend link.

Opening a purchase editor against a non-purchase task surfaces a soft amber note above the line-item table. The system does not block you. The items will show up under Items on non-purchase tasks in the dashboard below so they stay visible.

The soft amber note in PurchaseEditor when the parent task is not a purchase. The Lab Overview surface does not show this warning, since the lab-wide review wants a quieter dashboard.

The spending dashboard

Scroll past the order list and the page closes out with a live spending dashboard. Every number on it is recomputed from the same set of items you can see above. Funding accounts store only a budget cap, no spend counter, so each account's spent total is summed live from the line items charged to it. Live computation avoids drift bugs when an item is edited or moved.

Time range and project scope

The top of the dashboard has two controls. The time range dropdown chooses the window, Last 30 days, Last 90 days, Last 12 months (the default), All time, or a Custom date range with from and to inputs. The All projects checkbox lets the dashboard ignore the global project filter when you want to see the full picture. It is off by default, meaning the dashboard respects whichever projects you have filtered to in the top bar. Toggle it on to see all projects in the dashboard without losing your top-bar filter elsewhere.

Funding-account cards

Below the controls is a row of cards, one per funding account. Each card shows the account name, dollars spent, total budget, dollars remaining, and a progress bar. The numbers are computed live from items in the current window. Over-budget cards turn red. Items with a funding string that does not match any account roll up under an Uncategorized card. Click any card to filter the rest of the dashboard to just that funding string.

Each card is live-computed from items in the current window. Click a card to scope the dashboard to that funding string.

Spend over time

Next is a vertical bar chart of monthly spend across the selected window. Empty months still render as zero-height bars so the time span on the x-axis matches your range selection. Hover any bar to see the dollar total and item count for that month.

The default window is the last 12 months. Empty months render as zero-bars rather than collapsing the axis, so you can see gaps in spending.

Breakdown by project, vendor, or category

Below the time chart is a horizontal bar chart with a segmented control in the section header to switch lenses. The same set of items in the current window is regrouped by project, by vendor, or by category. Items with no value for the active lens (no vendor set, no category set, or a task with no project) render under Uncategorized in gray.

Project lens. Bars are sorted by total spend, descending.
Vendor lens. The autocomplete keeps spellings consistent enough for the groups to be meaningful.
Category lens. Items with no category roll into an Uncategorized bar.

Items on non-purchase tasks

Near the bottom of the dashboard is a thin amber line that reads Items on non-purchase tasks: followed by a count and a dollar total. Click it to expand an inline table of those items with their host task name and amount. This panel exists because the loose model lets you legitimately attach a reagent purchase to a specific experiment task. The panel keeps that visible so spend does not silently leak into other task types.

Expanded view of the amber panel. From here, open the host task in /workbench to reclassify it as a purchase or move the item to a proper purchase order.

CSV export

The top-right of the dashboard has an Export CSV button that downloads the currently filtered scope (time range plus project filter, plus any funding-card filter you have clicked) as a flat file named purchases-export-YYYY-MM-DD.csv. Columns are item id, item name, vendor, category, funding string, project name, task name, start date, total price, and owner.

Export CSV downloads only what is currently in scope (time range, project filter, and any funding-card selection).

The dashboard export covers what you are looking at right now. For a grant audit, the page header has a separate Export audit CSV button that ignores the dashboard filters and writes every purchase grouped by grant, with references to each attached document, to a file named researchos-purchases-audit.csv. That is the index an auditor needs (the document files themselves stay in your data folder).

The PI experience

PIs use the same /purchases page as every other lab member, with two differences. First, the fourth filter chip is relabeled Pending approval (members see it as Awaiting approval) because PIs own that queue. Clicking it narrows the list to every purchase across the lab that is still waiting on a PI decision, and the chip carries a live count badge of that queue. Second, a sticky banner appears at the top of the page when the pending count is above zero, showing the number of items awaiting approval across the lab and linking the PI straight to the filtered view.

Each line item in the pending view has inline Approve and Decline buttons that a PI can act on directly, in the same expanded-row view every member uses. There is no separate popup or Tools launcher.

The decline state

A declined purchase carries a declined_at timestamp and renders with a red Declined badge wherever it appears, including in the member's own list and in the Lab Activity stream. The Pending approval filter also shows a Recently declined section at the bottom so a PI can Re-approve a previously-declined purchase without making the member resubmit.

Managing funding accounts

Click Manage Funding Accounts at the top right and a popup opens over the page, so you can edit grants without losing your place in the list. Each account has a name (for example NIH-R01-12345), a total budget, and an optional one-line description.

Structured grant metadata

Each funding account can carry the official grant details beneath its everyday label. Expand Grant details (for data sharing / DOI) on an account and you can record the structured fields a repository deposit needs.

  • Award number: the official grant identifier (for example 5R01GM123456-03). A soft hint nudges you toward the NIH shape, but any value is accepted.
  • Funder name: the funding body, with a datalist seeded with NIH and NSF. Choosing NIH auto-fills its funder ID and ID type.
  • Funder ID and funder ID type: the funder's identifier and its scheme (Crossref Funder ID, ROR, GRID, ISNI, or Other).
  • Award title: the grant's full title, optional.

These fields are deliberately separate from the account name. The name is your own short label that purchases match on, while the award number is the official identifier and may differ. All of them map one-to-one to the DataCite fundingReference fields, so when you later deposit an experiment to a repository the funder attribution is a direct copy rather than something you re-enter.

Linking a project to a grant

A funding account can be linked to a project as its primary grant. That project-to-grant link is what the deposit flow reads when it prefills the funder fields, so a single experiment under that project carries the right award number and funder into its DataCite metadata automatically. For v1 a project links to a single grant.

Future considerations

A few things in this area are not yet shipped, listed here so you do not spend time looking for them.

  • Per-item dates (the time axis uses the parent task's start date)
  • A Task.completed_at timestamp
  • Multi-currency support
  • Recurring or subscription purchases
  • OCR receipt import
  • Anomaly detection and spending alerts
  • Budget-threshold notifications
  • A catalog-prefilled vendor and category list (datalist autocomplete is the only source today)