die heldin script add
This commit is contained in:
156
export-customers.ts
Normal file
156
export-customers.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
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, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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()} | 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}`)
|
||||
Reference in New Issue
Block a user