{"record":{"id":"e0a5e059d041f304","repo":"jackwener/OpenCLI","slug":"amount-must-be-a-positive-number-with-up-to-two-de","errorCode":null,"errorMessage":"Amount must be a positive number with up to two decimals: ${amount}","messagePattern":"Amount must be a positive number with up to two decimals: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/mercury/utils.js","lineNumber":52,"sourceCode":"function parseReceiptPath(kwargs) {\n    const receipt = path.resolve(requireString(kwargs, 'receipt'));\n    let stat;\n    try {\n        stat = fs.statSync(receipt, { throwIfNoEntry: false });\n    }\n    catch (error) {\n        throw new ArgumentError(`Receipt file cannot be read: ${receipt}`, error instanceof Error ? error.message : undefined);\n    }\n    if (!stat || !stat.isFile()) {\n        throw new ArgumentError(`Receipt file does not exist: ${receipt}`);\n    }\n    return receipt;\n}\n\nfunction parseAmount(kwargs) {\n    const amount = requireString(kwargs, 'amount').replace(/,/g, '');\n    if (!/^\\d+(\\.\\d{1,2})?$/.test(amount) || Number(amount) <= 0) {\n        throw new ArgumentError(`Amount must be a positive number with up to two decimals: ${amount}`);\n    }\n    return amount;\n}\n\nfunction parseCurrency(kwargs) {\n    const currency = optionalString(kwargs, 'currency', 'CNY').toUpperCase();\n    if (!/^[A-Z]{3}$/.test(currency)) {\n        throw new ArgumentError(`Currency must be a three-letter ISO currency code: ${currency}`);\n    }\n    return currency;\n}\n\nfunction parseDate(kwargs) {\n    const date = requireString(kwargs, 'date');\n    const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n    if (!match) {\n        throw new ArgumentError(`Date must be YYYY-MM-DD: ${date}`);\n    }","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/mercury/utils.js#L34-L70","documentation":"parseAmount strips commas and validates the amount string against ^\\d+(\\.\\d{1,2})?$ and requires it be > 0. Anything with symbols, currency codes, thousands separators other than commas, exponents, negatives, or more than two decimals is rejected with this ArgumentError.","triggerScenarios":"amount values like '$12.50', '12,50' (European decimal comma, comma stripped leaving 1250), '-3', '0', '0.00', '12.345', '1e3', or empty string after comma removal.","commonSituations":"Copy-pasting amounts from invoices that include currency symbols; European number formatting using '.' as thousands separator ('1.234,56' -> '1234,56' fails); passing numbers with 3+ decimals from spreadsheets.","solutions":["Supply the amount as a plain decimal string with '.' as the decimal separator and no currency symbols: e.g. '1234.56'.","Remove thousands separators before passing (or use commas only as thousands separators, which the parser strips).","Round/trim to at most two decimal places and ensure the value is greater than zero.","If amounts come from another system, sanitize them first: strip symbols, convert ',' decimals to '.', then format with toFixed(2)."],"exampleFix":"// before\n--amount \"¥1,234.567\"\n// after\n--amount \"1234.57\"","handlingStrategy":"validation","validationCode":"function isValidAmount(a) {\n  const s = String(a).replace(/,/g, '');\n  return /^\\d+(\\.\\d{1,2})?$/.test(s) && Number(s) > 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await reimburse({ amount });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('Amount must be a positive number')) {\n    console.error('Expected format: digits with optional .xx, e.g. 1234.56');\n  } else throw e;\n}","preventionTips":["Strip currency symbols before passing amounts","Convert European '1.234,56' formats to '1234.56' first","Round money to 2 decimals with toFixed(2) upstream","Never pass negative or zero amounts to reimbursement"],"tags":["argument-validation","input-validation","number-format","cli"],"backgroundTag":"invalid-input-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}