A finance request names three invoices. A worker opens billing history, checks three boxes, and starts a download. The count looks right, but one checkbox was left over from a previous search. Two requested invoices and one unrelated document were selected.
Checking the count would not catch the mistake. The application needs to compare identities.
I work on BrowserAct. Our Adobe invoice guide distinguishes individual-account retrieval from directly billed team contracts. Its November 9 documentation review describes a native Admin Console flow for selecting one or more invoices, with contract-owner access. That native feature is the starting point here: add a selection check before using it, rather than assume a new collector must reproduce the entire workflow.
The Python example below checks synthetic selection snapshots. It does not sign in to Adobe, operate Admin Console, download PDFs, or test a BrowserAct Bot. The fields are an application contract, not an Adobe API response.
Keep the purchase route outside the selection algorithm
An individual Adobe purchase, a directly billed team contract, and a reseller purchase have different retrieval paths. Product access does not prove billing access. A missing team Billing history tab should prompt a contract-ownership check, not an automatic switch to another identity.
This gate applies after an authorized operator has established the correct native route. It does not authorize access or make the personal-account interface support team batch controls. Reseller documents remain a separate request to the reseller.
The caller supplies an expected account and contract reference. These may be internal normalized references. The upstream adapter must explain how it establishes the mapping to the current page; the function cannot discover or verify that mapping itself.
Freeze the requested IDs before touching checkboxes
Use the invoice references from the finance request as the expected set. Amounts and dates can help locate rows, but repeated subscription amounts make poor document identities. A matching amount is not a substitute for the requested invoice number.
Keep that expected set separate from the observed selection. Building both from the same checked rows would only prove that a list equals itself. If the request changes, create a new request revision and obtain a new selection observation rather than silently editing the earlier evidence.
The observer must enumerate all selected rows, including selections retained across filters or pages. Reading only the visible table while assuming hidden selections are clear defeats the check. If complete enumeration is unavailable, the application should require manual review instead of setting a completeness flag optimistically.
Compare scope, document type, and exact membership
The gate rejects missing identifiers and duplicate IDs instead of silently normalizing them away. It then requires a complete, independently verified selection snapshot in the intended scope. Finally, each selected row must represent an invoice in that scope, and its ID must match the request exactly.
def checked_ids(values):
if not isinstance(values, list) or not values:
raise ValueError("Expected a nonempty list of invoice IDs")
if any(not isinstance(v, str) or not v.strip() for v in values):
raise ValueError("Resolve every invoice ID")
if len(values) != len(set(values)):
raise ValueError("Duplicate invoice IDs need review")
return set(values)
def selection_gate(expected_scope, requested_ids, snapshot):
if (not isinstance(expected_scope, tuple) or len(expected_scope) != 2
or any(not isinstance(v, str) or not v.strip()
for v in expected_scope)):
raise ValueError("Expected an account and contract scope")
expected = checked_ids(requested_ids)
if snapshot.get("scope_verified") is not True:
return "scope_review_required"
if snapshot.get("selection_complete") is not True:
return "selection_review_required"
observed_scope = (snapshot.get("account"), snapshot.get("contract"))
if observed_scope != expected_scope:
return "scope_mismatch"
rows = snapshot.get("selected_rows")
if not isinstance(rows, list) or not rows:
return "selection_empty"
if any(not isinstance(row, dict) for row in rows):
raise ValueError("Expected selected-row records")
for row in rows:
if (row.get("account"), row.get("contract")) != expected_scope:
return "row_scope_mismatch"
if row.get("document_type") != "invoice":
return "document_type_mismatch"
observed = checked_ids([row.get("invoice_id") for row in rows])
if observed != expected:
return "selection_membership_mismatch"
return "ready_for_native_download"
The strict booleans prevent a string such as "false" from qualifying as verified evidence. The ID comparison is intentionally exact. If a portal formats identifiers differently, implement and test an explicit mapping before this boundary; do not strip punctuation until two distinct invoices collapse into one.
Test substitutions as well as missing rows
| Synthetic selection | Expected outcome |
|---|---|
| Requested IDs selected in another order | Ready for the native download step |
| Same count, one unrelated ID substituted | Membership mismatch |
| One requested ID missing or one extra ID retained | Membership mismatch |
| Same ID appears twice | Validation error |
| A row belongs to another contract | Row scope mismatch |
| A payment receipt fills an invoice slot | Document type mismatch |
| Selection completeness is unconfirmed | Selection review required |
The local checks exercise these cases and malformed inputs. They do not establish that Adobe exposes a particular selector, retains checkbox state in a particular way, or returns a specific file package. The stale-selection opening example is a design scenario, not a reported Adobe defect.
Make readiness expire when the page changes
ready_for_native_download is a decision about one supplied snapshot. It is not proof of a saved file. Bind the decision to the request revision and the observation that produced it. If the account, contract, filter, selection, or page state changes before the native action, discard the readiness decision and obtain fresh evidence.
A production adapter must control that interval. This small pure function cannot prevent another operator from changing the page or ensure the snapshot is truthful. Avoid storing a reusable “approved” flag that survives navigation to another contract.
After the native download, verify the actual artifacts against the requested invoices. A returned file count alone repeats the same mistake at a later stage. Keep invoice identity, document type, and archive evidence, and retain missing items without substituting nearby periods.
Adobe invoices and payment receipts serve different purposes in the source guide. Retrieving a record does not call for changing a payment method or contract owner. Keep those actions outside a document-selection workflow.
Native batch controls can save repeated work. A small identity check helps ensure that the work they save is still the work that was requested.
Source and native retrieval context: Download Adobe invoices: personal and team accounts.
Top comments (0)