Admin E-Mail-Speicher: belegter Speicher wie in konsoleH, klare Vorgabe je Kunde
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
12a24514a4
commit
cc42ab16c1
4 files changed
+69
-13
No files matched your search
@@ -53,6 +53,8 @@ function taskView(event, customer) {
|
||||
customerName: customer?.name || customer?.companyName || customer?.code || event.customerId,
|
||||
customerCode: customer?.code || '',
|
||||
email: customer?.email || '',
|
||||
// Das Portal kennt keine eigene Mail-Domain des Kunden; gezeigt wird die Domain der Kontakt-Adresse
|
||||
contactDomain: String(customer?.email || '').split('@')[1] || '',
|
||||
subscriptionId: event.subscriptionId,
|
||||
productKey,
|
||||
productLabel: productLabel(productKey),
|
||||
@@ -71,13 +73,22 @@ function taskView(event, customer) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Auf dem Hetzner-Paket vergebene GB: 5 GB je laufendem Abo plus 5 GB je gebuchtem Paket (F40). */
|
||||
async function allocatedGb() {
|
||||
/**
|
||||
* Auf dem Hetzner-Paket vergebene GB, so wie konsoleH sie haelt (F40): 5 GB je laufendem Abo plus 5 GB je
|
||||
* Paket. Eine Verringerung gilt in konsoleH erst, wenn die Aufgabe "zum Periodenende" erledigt ist; bis
|
||||
* dahin zaehlt je Abo die groessere Menge aus storageUnits und der Ausgangsmenge (fromUnits) der Aufgabe.
|
||||
*/
|
||||
async function allocatedGb(openTasks) {
|
||||
const held = new Map()
|
||||
for (const e of openTasks) {
|
||||
if (storageTaskKind(e) !== 'periodenende') continue
|
||||
held.set(e.subscriptionId, Math.max(held.get(e.subscriptionId) || 0, Number(e.details?.fromUnits) || 0))
|
||||
}
|
||||
const subs = await listDocuments(config.collections.subscriptions, [
|
||||
Query.equal('status', [...ACTIVE_STATUSES]),
|
||||
Query.limit(1000),
|
||||
])
|
||||
return subs.reduce((sum, sub) => sum + gbOf(sub.storageUnits), 0)
|
||||
return subs.reduce((sum, sub) => sum + gbOf(Math.max(Number(sub.storageUnits) || 0, held.get(sub.$id) || 0)), 0)
|
||||
}
|
||||
|
||||
/** Offene Wechsel mit Stripe abgleichen; reconcilePendingSwitch prueft selbst, ob die Zahlung verfallen ist. */
|
||||
@@ -94,11 +105,11 @@ storageTasksAdminRouter.get('/', async (_req, res) => {
|
||||
try {
|
||||
await reconcileOpenSwitches()
|
||||
const since = new Date(Date.now() - FAILED_VISIBLE_MS).toISOString()
|
||||
const [open, failedAll, gb] = await Promise.all([
|
||||
const [open, failedAll] = await Promise.all([
|
||||
listOpenStorageTasks(),
|
||||
listContractEventsOf('speicher', 'gescheitert'),
|
||||
allocatedGb(),
|
||||
])
|
||||
const gb = await allocatedGb(open)
|
||||
const failed = failedAll.filter((e) => String(e.$createdAt || '') >= since)
|
||||
const customers = await customersOf([...open, ...failed])
|
||||
const { name, gb: packageGb, warnGb } = config.mailStorage.hetznerPackage
|
||||
@@ -117,13 +128,15 @@ storageTasksAdminRouter.get('/', async (_req, res) => {
|
||||
|
||||
storageTasksAdminRouter.post('/:id/done', async (req, res) => {
|
||||
try {
|
||||
const staffEmail = staffEmailOf(req)
|
||||
if (!staffEmail) return res.status(400).json({ error: 'E-Mail-Adresse des Mitarbeiters fehlt in der Sitzung' })
|
||||
const event = await getContractEvent(req.params.id).catch((err) => {
|
||||
if (err.status === 404) return null
|
||||
throw err
|
||||
})
|
||||
if (!event || event.type !== 'speicher') return res.status(404).json({ error: 'Speicher-Aufgabe nicht gefunden' })
|
||||
if (event.status !== 'offen') return res.status(409).json({ error: `Diese Aufgabe ist nicht offen (Status: ${event.status})` })
|
||||
await completeContractEvent(event.$id, staffEmailOf(req))
|
||||
await completeContractEvent(event.$id, staffEmail)
|
||||
const done = await getContractEvent(event.$id)
|
||||
const customer = await getDocument(config.collections.customers, done.customerId).catch(() => null)
|
||||
return res.json({ task: taskView(done, customer) })
|
||||
|
||||
Reference in new issue
Block a user