mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 23:26:42 +02:00
feat(invoices): M8 AI tools — create/mark_paid/list/stats
The last open item from the plan. Missions can now draft invoices from chat context, mark customer payments, and read status for autonomous follow-up cadences. Tool catalog (packages/shared-ai/src/tools/schemas.ts) - create_invoice (propose) — clientName + lines[] + currency + due - mark_invoice_paid (propose) — by id, optional back-dated paidAt - list_invoices (auto) — with status + limit filter - get_invoice_stats (auto) — open/overdue/YTD per currency Had to widen the tool-parameter type vocabulary so create_invoice can declare lines as a typed array. Touched three places: - ToolSchema-side: the catalog's `type` string is already free-form so 'array' / 'object' just pass through - ModuleTool-side (apps/mana/apps/web/src/lib/data/tools/types.ts): added 'array' | 'object' to the union so TS doesn't narrow the executor's param signatures - function-schema translator (packages/shared-ai): mapParamType + JsonSchemaProperty both gained the two new types; the catalog-typo guard test now uses 'fruit' as its sentinel (array no longer unknown) Executor (apps/mana/apps/web/src/lib/modules/invoices/tools.ts) - coerceLines accepts either a real array or a JSON-stringified array (planners vary), skips malformed entries, converts major→minor units - create_invoice pulls the generated number back from Dexie so the success message shows "Entwurf 2026-0042 …" — the user recognises it - mark_invoice_paid normalises YYYY-MM-DD → ISO so the store's timestamp invariant (ISO throughout) stays intact - list_invoices derives overdue on read (consistent with useAllInvoices), returns major-unit amounts so the LLM reasons in user-facing numbers - get_invoice_stats returns counts + open/overdue/YTD per currency Registration: invoicesTools added to tools/init.ts. mana-ai drift guard is happy (41/41 green); webapp + shared-ai type-check 0 errors; full invoice test suite 59/59 green. Closes: docs/plans/invoices-module.md §M8. All plan milestones now DONE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d613e1846
commit
a4bc7d2ee3
7 changed files with 427 additions and 4 deletions
|
|
@ -87,8 +87,10 @@ describe('toolToFunctionSchema', () => {
|
|||
description: 'Broken',
|
||||
defaultPolicy: 'auto',
|
||||
parameters: [
|
||||
// `fruit` is genuinely not a JSON Schema type — the catalog-typo
|
||||
// guard exists to catch accidental one-off strings like this.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
{ name: 'p', type: 'array' as any, description: 'p', required: true },
|
||||
{ name: 'p', type: 'fruit' as any, description: 'p', required: true },
|
||||
],
|
||||
};
|
||||
expect(() => toolToFunctionSchema(tool)).toThrow(/Unsupported parameter type/);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,14 @@
|
|||
|
||||
import type { ToolSchema } from './schemas';
|
||||
|
||||
/** OpenAI-compatible JSON-Schema property type. */
|
||||
/** OpenAI-compatible JSON-Schema property type. `array` / `object` are
|
||||
* emitted without a nested `items` / `properties` schema — tools that
|
||||
* take structured payloads describe the expected shape inside their
|
||||
* human-readable description and parse in the executor. Tightening to
|
||||
* a full JSON-Schema tree would be strictly better but isn't required
|
||||
* by the OpenAI / Anthropic function-calling specs. */
|
||||
export interface JsonSchemaProperty {
|
||||
type: 'string' | 'number' | 'integer' | 'boolean';
|
||||
type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
|
||||
description?: string;
|
||||
enum?: readonly string[];
|
||||
}
|
||||
|
|
@ -53,6 +58,8 @@ function mapParamType(t: string): JsonSchemaProperty['type'] {
|
|||
case 'number':
|
||||
case 'integer':
|
||||
case 'boolean':
|
||||
case 'array':
|
||||
case 'object':
|
||||
return t;
|
||||
default:
|
||||
// Unknown types in the catalog are a bug, but don't silently
|
||||
|
|
|
|||
|
|
@ -1110,6 +1110,112 @@ export const AI_TOOL_CATALOG: readonly ToolSchema[] = [
|
|||
defaultPolicy: 'auto',
|
||||
parameters: [{ name: 'quizId', type: 'string', description: 'ID des Quiz', required: true }],
|
||||
},
|
||||
|
||||
// ── Invoices ─────────────────────────────────────────────
|
||||
{
|
||||
name: 'create_invoice',
|
||||
module: 'invoices',
|
||||
description:
|
||||
'Erstellt eine neue Rechnung als Entwurf. Setzt Kunde (Name + optional Adresse + E-Mail), Positionen (Titel, Menge, Einzelpreis in Hauptwaehrung), Faelligkeit. Nummer wird automatisch vergeben.',
|
||||
defaultPolicy: 'propose',
|
||||
parameters: [
|
||||
{
|
||||
name: 'clientName',
|
||||
type: 'string',
|
||||
description: 'Name des Kunden (erforderlich)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'clientEmail',
|
||||
type: 'string',
|
||||
description: 'E-Mail-Adresse des Kunden',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'clientAddress',
|
||||
type: 'string',
|
||||
description: 'Postanschrift des Kunden (mehrzeilig, Strasse + Nr, dann PLZ Ort)',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
description: 'Kurzer Betreff (z.B. "Beratung April")',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'currency',
|
||||
type: 'string',
|
||||
description: 'Waehrung (Standard: CHF)',
|
||||
required: false,
|
||||
enum: ['CHF', 'EUR', 'USD'],
|
||||
},
|
||||
{
|
||||
name: 'dueDate',
|
||||
type: 'string',
|
||||
description: 'Faelligkeitsdatum (YYYY-MM-DD). Ohne Angabe: +30 Tage ab heute.',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'lines',
|
||||
type: 'array',
|
||||
description:
|
||||
'Array von Positionen: [{ title: string, quantity: number, unitPrice: number (in Hauptwaehrung, z.B. 150.00), vatRate?: number, unit?: string }]. Mindestens eine Position.',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'mark_invoice_paid',
|
||||
module: 'invoices',
|
||||
description:
|
||||
'Markiert eine versendete oder ueberfaellige Rechnung als bezahlt. paidAt ist optional (Standard: heute, fuer rueckdatierte Eingaenge ein fruehes Datum setzen).',
|
||||
defaultPolicy: 'propose',
|
||||
parameters: [
|
||||
{
|
||||
name: 'invoiceId',
|
||||
type: 'string',
|
||||
description: 'ID der Rechnung (aus list_invoices)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'paidAt',
|
||||
type: 'string',
|
||||
description: 'Zahlungsdatum (ISO oder YYYY-MM-DD). Standard: jetzt.',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'list_invoices',
|
||||
module: 'invoices',
|
||||
description:
|
||||
'Listet Rechnungen auf. Optional nach Status (draft/sent/paid/overdue/void) und Limit gefiltert. Gibt ID, Nummer, Kunde, Status, Betrag, Faelligkeit zurueck.',
|
||||
defaultPolicy: 'auto',
|
||||
parameters: [
|
||||
{
|
||||
name: 'status',
|
||||
type: 'string',
|
||||
description: 'Nur diesen Status zeigen',
|
||||
required: false,
|
||||
enum: ['draft', 'sent', 'paid', 'overdue', 'void'],
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
description: 'Maximale Anzahl (Standard: 20)',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'get_invoice_stats',
|
||||
module: 'invoices',
|
||||
description:
|
||||
'Gibt Rechnungs-Kennzahlen zurueck: offene Summe, ueberfaellige Summe, YTD fakturiert + bezahlt (pro Waehrung, in Hauptwaehrung als Gleitkomma).',
|
||||
defaultPolicy: 'auto',
|
||||
parameters: [],
|
||||
},
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue