fix(api): wrap audit log writes inside their parent transactions

Prevents mutations from committing without an audit trail if the
auditLog.create call fails after the main write already succeeded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 16:40:10 +02:00
parent a01f99561d
commit 3c0179fcec
25 changed files with 758 additions and 656 deletions
@@ -238,41 +238,43 @@ export async function applyEffortRules(
const rules = toEffortRuleEngineInputs(ruleSet.rules);
const result = expandScopeToEffort(scopeItems, rules);
if (input.mode === "replace") {
await ctx.db.estimateDemandLine.deleteMany({
where: { estimateVersionId: version.id },
});
}
await ctx.db.$transaction(async (tx) => {
if (input.mode === "replace") {
await tx.estimateDemandLine.deleteMany({
where: { estimateVersionId: version.id },
});
}
if (result.lines.length > 0) {
await ctx.db.estimateDemandLine.createMany({
data: buildEstimateDemandLineRows({
estimateVersionId: version.id,
currency: estimate.baseCurrency,
ruleSet: { id: ruleSet.id, name: ruleSet.name },
lines: result.lines,
}),
});
}
if (result.lines.length > 0) {
await tx.estimateDemandLine.createMany({
data: buildEstimateDemandLineRows({
estimateVersionId: version.id,
currency: estimate.baseCurrency,
ruleSet: { id: ruleSet.id, name: ruleSet.name },
lines: result.lines,
}),
});
}
await ctx.db.auditLog.create({
data: {
entityType: "Estimate",
entityId: estimate.id,
action: "UPDATE",
...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
changes: {
after: {
effortRulesApplied: {
ruleSetId: ruleSet.id,
ruleSetName: ruleSet.name,
mode: input.mode,
linesGenerated: result.lines.length,
warnings: result.warnings,
await tx.auditLog.create({
data: {
entityType: "Estimate",
entityId: estimate.id,
action: "UPDATE",
...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
changes: {
after: {
effortRulesApplied: {
ruleSetId: ruleSet.id,
ruleSetName: ruleSet.name,
mode: input.mode,
linesGenerated: result.lines.length,
warnings: result.warnings,
},
},
},
},
},
});
});
return {