//native
import * as wmill from "windmill-client"
type Invoice = { id: number; "created-at": string }
/**
* New Invoices
* Trigger returning invoices created since the last run, tracked by a created-at watermark with id de-duplication.
*/
export async function main(auth: RT.Coupa) {
const base = auth.instance_url.replace(/\/+$/, "")
const state: { watermark: string; seenIds: number[] } | null =
await wmill.getState()
if (!state || !state.watermark) {
// First run: start 60s in the past (clock-skew tolerance), return no backlog
const seed = new Date(Date.now() - 60_000)
.toISOString()
.replace("Z", "+00:00")
await wmill.setState({ watermark: seed, seenIds: [] })
return []
}
// gt_or_eq + id de-dup instead of gt, so invoices sharing the watermark second aren't skipped
const returned: Invoice[] = []
let offset = 0
while (true) {
const url = new URL(`${base}/api/invoices`)
url.searchParams.append("created_at[gt_or_eq]", state.watermark)
url.searchParams.append("limit", "50")
url.searchParams.append("offset", String(offset))
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${auth.token}`,
Accept: "application/json",
},
})
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`)
}
const page = (await response.json()) as Invoice[]
returned.push(...page)
if (page.length < 50) break
offset += 50
}
const seen = new Set(state.seenIds)
const newInvoices = returned.filter((i) => !seen.has(i.id))
if (newInvoices.length > 0) {
// Advance the watermark to the newest Coupa-returned created-at; remember
// the ids at that exact instant so the next gt_or_eq query can skip them
const maxEpoch = Math.max(
...returned.map((i) => new Date(i["created-at"]).getTime())
)
const atMax = returned.filter(
(i) => new Date(i["created-at"]).getTime() === maxEpoch
)
await wmill.setState({
watermark: atMax[0]["created-at"],
seenIds: atMax.map((i) => i.id),
})
}
return newInvoices
}
Submitted by hugo989 4 hours ago