diff --git a/src/rates.mjs b/src/rates.mjs index fcdde8d..fbf2dc3 100644 --- a/src/rates.mjs +++ b/src/rates.mjs @@ -139,7 +139,7 @@ export function parseRate(spec) { // A flat fee with no period stated is a project fee, not an hourly one: "$5000 // for the project" is how it is written, and defaulting it to per-hour would // silently multiply the invoice by every hour tracked. - if (!sawPeriod && rate.unit === "flat" && rate.cap === null) rate.per = "hour"; + if (!sawPeriod && rate.unit === "flat" && rate.cap === null) rate.per = "project"; if (rate.cap !== null && rate.unit === "flat") { throw new Error("upto: caps a unit, so say what it caps — $100/hour/agent/upto:4"); } diff --git a/test/rates.test.mjs b/test/rates.test.mjs index 64c1694..4c5d973 100644 --- a/test/rates.test.mjs +++ b/test/rates.test.mjs @@ -87,6 +87,21 @@ test("periods convert, and a floor rounds up before the multiply", () => { assert.equal(chargeFor({ seconds: 15 * 60 }, floored).hours, 0.25, "the tracked time is still the truth"); }); +test("a bare flat fee is a project fee, not an hourly one", () => { + // A lone price with no period stated ("$5000 for the project") must not fall + // through to the per-hour default: chargeFor would then multiply it by every + // tracked hour, turning a $5,000 project fee into a $50,000 invoice over a + // 10-hour job. It is charged once, like any other project fee. + const bare = parseRate("$5000"); + assert.equal(bare.per, "project", "a bare flat fee defaults to a project fee"); + const charge = chargeFor({ seconds: 10 * 3600 }, bare); + assert.equal(charge.flat, true, "billed once, not per hour"); + assert.equal(charge.amount, null, "the invoice adds the fee, chargeFor does not scale it"); + // A crypto flat fee reads the same way, and an explicit period still wins. + assert.equal(parseRate("250 USDC").per, "project"); + assert.equal(parseRate("$100/hour").per, "hour", "a stated period is untouched"); +}); + test("a flat project fee is not earned per entry", () => { const project = parseRate("$5000/project"); const charge = chargeFor({ seconds: 3600 }, project);