Projektarten: Zuordnen gibt Warnungen von assignProject weiter

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-09-30 18:30:53 +00:00
1 parent 36c7822783
commit 3e6f35f836
2 files changed
+51 -2

No files matched your search

+6 -2
View File
@@ -50,6 +50,7 @@ export async function completeAssignment(
const customer = !!forCustomer
let createdTicketId = null
let finalTicketId = null
const warnings = []
if (customer) {
if (!(await loadOrNull(config.collections.customers, customerId))) throw fail(400, 'Kunde nicht gefunden.')
@@ -80,7 +81,9 @@ export async function completeAssignment(
}
try {
await assign(projectId, { customerId, ticketId: finalTicketId }, { now })
const assigned = await assign(projectId, { customerId, ticketId: finalTicketId }, { now })
// assignProject schluckt Umzug-/Nachtrage-Fehler und meldet sie als Warnungen.
if (assigned?.warnings?.length) warnings.push(...assigned.warnings)
} catch (err) {
if (createdTicketId) await deleteTicket(createdTicketId).catch(() => {})
throw err
@@ -100,9 +103,10 @@ export async function completeAssignment(
await updateProject(projectId, { subdomain: preview.subdomain, projectType: 'preview' })
} catch (err) {
// Art und Zuordnung sind gespeichert - die Preview laesst sich nachtraeglich einschalten.
result.warnings = [`Die Preview konnte nicht eingeschaltet werden (${err.message}).`]
warnings.push(`Die Preview konnte nicht eingeschaltet werden (${err.message}).`)
}
}
if (warnings.length) result.warnings = warnings
return result
})
}
+45
View File
@@ -44,6 +44,10 @@ before(async () => {
newProj('p9', 'doppelt'),
newProj('p10', 'prev'),
newProj('p11', 'route'),
newProj('p13', 'warn'),
newProj('p14', 'race'),
newProj('p15', 'kunde'),
newProj('p16', 'eins'),
newProj('p12', 'schon', { kind: 'app', kindSetBy: 'Max', kindSetAt: '2026-09-29T08:00:00.000Z' }),
],
workorders: [
@@ -176,6 +180,47 @@ test('Website mit Subdomain ⇒ updateWebsiteProject(preview)', async () => {
assert.deepEqual(s.calls.update, [['p10', { subdomain: 'prev', projectType: 'preview' }]])
})
test('Warnungen von assignProject und Preview-Fehler werden zusammengefuehrt', async () => {
const s = stubs()
s.deps.assignProject = async () => ({ ok: true, warnings: ['Umzug fehlgeschlagen'] })
s.deps.updateWebsiteProject = async () => { throw new Error('Gitea aus') }
const r = await completeAssignment('p13', { kind: 'website', forCustomer: true, customerId: 'c1', ticketId: 't1', preview: { subdomain: 'w' } }, opts(s.deps))
assert.equal(r.ticketId, 't1')
assert.equal(r.warnings.length, 2)
assert.equal(r.warnings[0], 'Umzug fehlgeschlagen')
assert.match(r.warnings[1], /Preview/)
assert.equal(aw.get('websiteProjects', 'p13').kind, 'website')
})
test('gleichzeitiger Doppelaufruf ⇒ genau ein Ticket und ein 409', async () => {
const s = stubs()
const before = aw.all('workorders').length
const body = { kind: 'app', forCustomer: true, customerId: 'c1', newTicket: { topic: 'race' } }
const res = await Promise.allSettled([completeAssignment('p14', body, opts(s.deps)), completeAssignment('p14', body, opts(s.deps))])
assert.equal(res.filter((x) => x.status === 'fulfilled').length, 1)
const rej = res.find((x) => x.status === 'rejected')
assert.equal(rej.reason.status, 409)
assert.equal(aw.all('workorders').length, before + 1)
})
test('forCustomer mit unbekanntem Kunden ⇒ 400', async () => {
const s = stubs()
await assert.rejects(
() => completeAssignment('p15', { kind: 'website', forCustomer: true, customerId: 'nix', newTicket: { topic: 'x' } }, opts(s.deps)),
(e) => e.status === 400
)
assert.equal(aw.get('websiteProjects', 'p15').kind, undefined)
})
test('ticketId und newTicket beide oder keins ⇒ 400', async () => {
const s = stubs()
const base = { kind: 'website', forCustomer: true, customerId: 'c1' }
for (const extra of [{ ticketId: 't1', newTicket: { topic: 'x' } }, {}]) {
await assert.rejects(() => completeAssignment('p16', { ...base, ...extra }, opts(s.deps)), (e) => e.status === 400)
}
assert.equal(aw.get('websiteProjects', 'p16').kind, undefined)
})
test('Route: ohne Staff-JWT ⇒ 401, mit JWT intern 200, bereits zugeordnet 409, unbekannt 404', async () => {
const post = (id, body, jwt) =>
fetch(`${baseUrl}/api/admin/website-projects/${id}/zuordnen`, {