Files
dieheldin/export-customers.ts
2026-02-24 07:04:01 +01:00

157 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { writeFileSync } from "node:fs"
import { Openmagicline } from "openmagicline"
// ---------------------------------------------------------------------------
// Config set via environment variables or edit directly below
// ---------------------------------------------------------------------------
const GYM = process.env.MAGICLINE_GYM ?? "dieheldin"
const USERNAME = process.env.MAGICLINE_USER ?? "tanja.wild"
const PASSWORD = process.env.MAGICLINE_PASS ?? "Luna22111993"
if (!GYM || !USERNAME || !PASSWORD) {
console.error(
"Missing credentials. Set MAGICLINE_GYM, MAGICLINE_USER, and MAGICLINE_PASS.",
)
process.exit(1)
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Escape a value for use in a CSV cell. */
function csvCell(value: unknown): string {
if (value === null || value === undefined) return ""
const str = String(value)
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
return `"${str.replace(/"/g, '""')}"`
}
return str
}
/** Escape a value for use in HTML table cell content. */
function htmlEscape(value: unknown): string {
if (value === null || value === undefined) return ""
return String(value)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
const mgl = new Openmagicline({ gym: GYM, username: USERNAME, password: PASSWORD })
await mgl.login()
console.log("Logged in.")
// Fetch the real facility/organization unit ID — searching with facility:0 returns nothing.
const facilityId = await mgl.util.getDefaultUnitID()
console.log(`Using facility ID: ${facilityId}`)
const SEARCH_CAP = 50
const seen = new Set<number>()
const customers: Record<string, unknown>[] = []
async function sweep(prefix: string): Promise<void> {
const results = await mgl.customer.search(prefix, {
facility: facilityId,
showAllFacilities: false,
searchInName: false,
searchInCustomerNumber: true,
})
if (results.length === SEARCH_CAP) {
for (const d of "0123456789") {
await sweep(prefix + d)
}
return
}
for (const c of results) {
if (!seen.has(c.databaseId) && c.customerStatus === 1) {
seen.add(c.databaseId)
customers.push(c as unknown as Record<string, unknown>)
}
}
}
for (const d of "0123456789") {
const prefix = `1-${d}`
process.stdout.write(`Sweeping '${prefix}'… `)
const before = customers.length
await sweep(prefix)
console.log(`+${customers.length - before} (total: ${customers.length})`)
}
if (customers.length === 0) {
console.error("No customers found.")
process.exit(1)
}
// Collect the union of all keys across all customer records
const allKeys = Array.from(
customers.reduce((keys, c) => {
for (const k of Object.keys(c)) keys.add(k)
return keys
}, new Set<string>()),
)
// ---------------------------------------------------------------------------
// CSV export
// ---------------------------------------------------------------------------
const csvLines = [
allKeys.map(csvCell).join(","),
...customers.map((c) => allKeys.map((k) => csvCell(c[k])).join(",")),
]
const csvPath = "customers.csv"
writeFileSync(csvPath, csvLines.join("\n"), "utf-8")
console.log(`CSV written to ${csvPath} (${customers.length} rows)`)
// ---------------------------------------------------------------------------
// HTML export
// ---------------------------------------------------------------------------
const headerCells = allKeys.map((k) => `<th>${htmlEscape(k)}</th>`).join("")
const bodyRows = customers
.map(
(c) =>
`<tr>${allKeys.map((k) => `<td>${htmlEscape(c[k])}</td>`).join("")}</tr>`,
)
.join("\n")
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Customers ${new Date().toLocaleDateString()}</title>
<style>
body { font-family: sans-serif; font-size: 13px; padding: 1rem; }
h1 { margin-bottom: 0.5rem; }
p.meta { color: #666; margin-bottom: 1rem; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; white-space: nowrap; }
th { background: #f0f0f0; position: sticky; top: 0; }
tr:nth-child(even) { background: #fafafa; }
</style>
</head>
<body>
<h1>Customer Export</h1>
<p class="meta">Generated: ${new Date().toISOString()} &nbsp;|&nbsp; Total: ${customers.length}</p>
<table>
<thead><tr>${headerCells}</tr></thead>
<tbody>
${bodyRows}
</tbody>
</table>
</body>
</html>`
const htmlPath = "customers.html"
writeFileSync(htmlPath, html, "utf-8")
console.log(`HTML written to ${htmlPath}`)