mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
Merge final accounting follow-ups into CI stack
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
e3e74d369f
9 files changed
+17
-6
No files matched your search
@@ -166,7 +166,9 @@ provider adapters with missing prices still do.
|
||||
|
||||
Budget policy updates may omit unchanged settings, including the amount for an
|
||||
existing policy. Omitted settings are read under the accounting transaction lock.
|
||||
Creating a policy still requires an amount. Generic agent and company budget
|
||||
Creating a policy still requires an amount. Partial edits preserve an explicitly
|
||||
disabled policy; the legacy monthly-cap endpoints explicitly enable a positive
|
||||
cap and disable a zero cap. Generic agent and company budget
|
||||
edits deliver hard-stop cancellation after their transaction commits.
|
||||
|
||||
Status-card update costs retain the ledger's fractional-cent storage precision.
|
||||
|
||||
@@ -9,6 +9,7 @@ const artifactDirectory = path.join(root, "coverage/accounting/mutations");
|
||||
const targets = { deduplication: "deduplicates a retried receipt", company: "isolates company reporting", projection: "conserves agent projections", threshold: "stops at the exact budget boundary" };
|
||||
const evidence = [];
|
||||
const persist = () => writeFile(path.join(root, "coverage/accounting/mutations.json"), JSON.stringify({ generatedAt: new Date().toISOString(), evidence }, null, 2));
|
||||
await rm(artifactDirectory, { recursive: true, force: true });
|
||||
await mkdir(artifactDirectory, { recursive: true });
|
||||
try {
|
||||
for (const name of ["baseline", ...Object.keys(targets)]) {
|
||||
|
||||
@@ -311,6 +311,10 @@ databaseDescribe("cost accounting reliability (PostgreSQL)", () => {
|
||||
await budgets.upsertPolicy(f.company.id, { ...identity, amount: 200, hardStopEnabled: false, warnPercent: 60, notifyEnabled: false }, "other-operator");
|
||||
const saved = await budgets.upsertPolicy(f.company.id, upsertBudgetPolicySchema.parse({ ...identity, reservationCents: "10" }), "board");
|
||||
expect(saved).toMatchObject({ amount: 200, hardStopEnabled: false, warnPercent: 60, notifyEnabled: false, reservationCents: "10.0000000" });
|
||||
await budgets.upsertPolicy(f.company.id, { ...identity, isActive: false }, "other-operator");
|
||||
const disabled = await budgets.upsertPolicy(f.company.id, upsertBudgetPolicySchema.parse({ ...identity, amount: 300 }), "board");
|
||||
expect(await db.select({ amount: budgetPolicies.amount, isActive: budgetPolicies.isActive }).from(budgetPolicies).where(eq(budgetPolicies.id, disabled.policyId))).toEqual([{ amount: 300, isActive: false }]);
|
||||
expect(disabled).toMatchObject({ amount: 0, isActive: false, hardStopEnabled: false, warnPercent: 60, notifyEnabled: false, reservationCents: "10.0000000" });
|
||||
await expect(budgets.upsertPolicy(f.company.id, { scopeType: "project", scopeId: f.project.id, reservationCents: "1" }, "board")).rejects.toThrow("Amount is required");
|
||||
});
|
||||
|
||||
|
||||
@@ -398,6 +398,7 @@ describe("cost routes", () => {
|
||||
scopeType: "agent",
|
||||
scopeId: "agent-1",
|
||||
amount: 2500,
|
||||
isActive: true,
|
||||
windowKind: "calendar_month_utc",
|
||||
},
|
||||
"board-user",
|
||||
|
||||
@@ -279,8 +279,9 @@ export function companyRoutes(db: Db, storage?: StorageService, options?: Compan
|
||||
const router = Router();
|
||||
const importTransferSpoolRoot =
|
||||
options?.importTransferSpoolRoot ?? resolveDefaultImportTransferSpoolRoot();
|
||||
const heartbeat = heartbeatService(db);
|
||||
const svc = companyService(db, { cancelWorkForScope: heartbeat.cancelBudgetScopeWork });
|
||||
const svc = companyService(db, {
|
||||
cancelWorkForScope: (scope) => heartbeatService(db).cancelBudgetScopeWork(scope),
|
||||
});
|
||||
const agents = agentService(db);
|
||||
const portability = companyPortabilityService(db, storage);
|
||||
const access = accessService(db);
|
||||
|
||||
@@ -325,6 +325,7 @@ export function costRoutes(
|
||||
scopeType: "company",
|
||||
scopeId: companyId,
|
||||
amount: req.body.budgetMonthlyCents,
|
||||
isActive: req.body.budgetMonthlyCents > 0,
|
||||
windowKind: "calendar_month_utc",
|
||||
},
|
||||
req.actor.userId ?? "board",
|
||||
@@ -346,6 +347,7 @@ export function costRoutes(
|
||||
scopeType: "agent",
|
||||
scopeId: agent.id,
|
||||
amount: req.body.budgetMonthlyCents,
|
||||
isActive: req.body.budgetMonthlyCents > 0,
|
||||
windowKind: "calendar_month_utc",
|
||||
},
|
||||
req.actor.type === "board" ? req.actor.userId ?? "board" : null,
|
||||
|
||||
@@ -831,7 +831,7 @@ export function agentService(db: Db, budgetHooks: BudgetServiceHooks = {}) {
|
||||
|
||||
if (normalizedPatch.budgetMonthlyCents !== undefined) {
|
||||
await budgetServiceInTransaction(txDb, publications).upsertPolicy(existing.companyId, {
|
||||
scopeType: "agent", scopeId: id, amount: normalizedPatch.budgetMonthlyCents, windowKind: "calendar_month_utc",
|
||||
scopeType: "agent", scopeId: id, amount: normalizedPatch.budgetMonthlyCents, isActive: normalizedPatch.budgetMonthlyCents > 0, windowKind: "calendar_month_utc",
|
||||
}, options?.recordRevision?.createdByUserId ?? null);
|
||||
}
|
||||
const normalizedUpdated = await agentService(txDb).getById(updated.id);
|
||||
|
||||
@@ -627,7 +627,7 @@ export function budgetServiceInTransaction(db: Db, publications: ActivityPublica
|
||||
|
||||
if (!existing && input.amount === undefined) throw unprocessable("Amount is required for a new budget policy");
|
||||
const amount = Math.max(0, Math.floor(input.amount ?? existing!.amount));
|
||||
const nextIsActive = amount > 0 && (input.isActive ?? (input.amount !== undefined ? true : existing?.isActive ?? true));
|
||||
const nextIsActive = amount > 0 && (input.isActive ?? existing?.isActive ?? true);
|
||||
const now = new Date();
|
||||
const row = existing
|
||||
? await db
|
||||
|
||||
@@ -439,7 +439,7 @@ export function companyService(db: Db, budgetHooks: BudgetServiceHooks = {}) {
|
||||
|
||||
if (data.budgetMonthlyCents !== undefined) {
|
||||
await budgetServiceInTransaction(tx as unknown as Db, budgetPublications).upsertPolicy(id, {
|
||||
scopeType: "company", scopeId: id, amount: data.budgetMonthlyCents, windowKind: "calendar_month_utc",
|
||||
scopeType: "company", scopeId: id, amount: data.budgetMonthlyCents, isActive: data.budgetMonthlyCents > 0, windowKind: "calendar_month_utc",
|
||||
}, actor.actorType === "user" ? actor.actorId : null);
|
||||
const [budgetUpdated] = await tx.select().from(companies).where(eq(companies.id, id));
|
||||
Object.assign(updated, budgetUpdated);
|
||||
|
||||
Reference in new issue
Block a user