@taxora/sdk 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -301,6 +301,303 @@ const results = await client.vat.search('Alpha Handels', 25 /* perPage */);
301
301
 
302
302
  ---
303
303
 
304
+ ## 🔎 Smart Enrichment (reverse VAT lookup)
305
+
306
+ Resolve a company **name + address + country → VAT number + confidence**. A confident match
307
+ returns synchronously; harder cases (and all bulk batches) are processed asynchronously and
308
+ delivered via the `enrichment.completed` webhook — poll with `get(jobId)` in the meantime.
309
+ Requires the Smart Enrichment add-on to be active on your account.
310
+
311
+ ```ts
312
+ // Single lookup
313
+ let job = await client.smartEnrichment.lookup({
314
+ companyName: 'Example Company GmbH',
315
+ country: 'AT',
316
+ street: 'Main Street 10',
317
+ postalCode: '1010',
318
+ city: 'Vienna',
319
+ });
320
+
321
+ if (job.isProcessing()) {
322
+ // Resolved asynchronously — poll until done (or wait for the webhook)
323
+ job = await client.smartEnrichment.waitForCompletion(job.jobId, {
324
+ pollIntervalMs: 2000, // optional, default 2000
325
+ timeoutMs: 120_000, // optional, default 120000 — throws TaxoraException on timeout
326
+ });
327
+ }
328
+
329
+ const result = job.result();
330
+ console.log(result?.status); // 'found' | 'no_vat_exists' | 'not_found'
331
+ console.log(result?.vatNumber); // e.g. 'ATU12345678'
332
+ console.log(result?.confidence); // 0–100
333
+
334
+ // Bulk lookup (always async → webhook + polling)
335
+ const bulk = await client.smartEnrichment.bulkLookup([
336
+ { companyName: 'A GmbH', country: 'DE', city: 'Berlin' },
337
+ { companyName: 'B SARL', country: 'FR' },
338
+ ]);
339
+ const done = await client.smartEnrichment.waitForCompletion(bulk.jobId);
340
+ for (const r of done.results) {
341
+ console.log(r.status, r.vatNumber ?? '—', r.matchedAddress ?? '');
342
+ }
343
+
344
+ // Lookup history (paginated, newest first; perPage is capped at 100 server-side)
345
+ const history = await client.smartEnrichment.history(1, 25, 'Example GmbH' /* optional free-text search */);
346
+ console.log(`${history.total} lookups`);
347
+ console.log(history.stats?.total, history.stats?.found); // account-wide tiles (independent of the search filter)
348
+ for (const row of history) {
349
+ console.log(row.queryCompanyName, '→', row.result?.vatNumber ?? row.status);
350
+ }
351
+ ```
352
+
353
+ ### Quota usage & billing history
354
+
355
+ ```ts
356
+ const usage = await client.smartEnrichment.usage();
357
+ console.log(usage.period, `${usage.used}/${usage.included}`, 'remaining:', usage.remaining);
358
+ console.log(usage.overageCount, '×', usage.overagePrice, '=', usage.overageAmount);
359
+ for (const entry of usage.history) {
360
+ console.log(entry.period, entry.matches, entry.amount, entry.state); // 'billed' | 'pending'
361
+ }
362
+ ```
363
+
364
+ ### CSV export
365
+
366
+ Download your lookups as CSV in the bulk-input shape plus the resolved VAT columns (bulk jobs
367
+ are flattened to one line per input row). All filters are optional.
368
+
369
+ ```ts
370
+ import { SmartEnrichmentStatus } from '@taxora/sdk';
371
+ import { writeFileSync } from 'fs';
372
+
373
+ const csv = await client.smartEnrichment.export({
374
+ dateFrom: '2026-01-01', // job created_at lower bound (YYYY-MM-DD)
375
+ dateTo: '2026-06-30', // job created_at upper bound (YYYY-MM-DD)
376
+ minConfidence: 80, // 0–100
377
+ status: [SmartEnrichmentStatus.FOUND, SmartEnrichmentStatus.NO_VAT_EXISTS],
378
+ onlyFound: true, // only rows that resolved to a VAT number
379
+ });
380
+ writeFileSync('smart-enrichment.csv', csv);
381
+ ```
382
+
383
+ ### Statistics
384
+
385
+ Aggregated lookup statistics: headline totals, a time series and breakdowns by source, outcome
386
+ and confidence band. Defaults to the last 12 months and a monthly interval (server-side).
387
+
388
+ ```ts
389
+ const stats = await client.smartEnrichment.statistics({
390
+ dateFrom: '2026-01-01', // optional (YYYY-MM-DD)
391
+ dateTo: '2026-06-30', // optional (YYYY-MM-DD)
392
+ interval: 'month', // 'day' | 'week' | 'month'
393
+ });
394
+
395
+ console.log(stats.totals.items, 'items,', stats.totals.found, `found (${stats.totals.foundRate}%)`);
396
+ console.log('avg confidence:', stats.totals.avgConfidence);
397
+ for (const bucket of stats.timeSeries) {
398
+ console.log(`${bucket.bucket}: ${bucket.found}/${bucket.items}`);
399
+ }
400
+ for (const band of stats.confidenceBuckets) {
401
+ console.log(band.bucket, band.count); // high / medium / low
402
+ }
403
+ ```
404
+
405
+ > Note: `no_vat_exists` means the company was identified but legitimately has **no** VAT/UID
406
+ > number (common for purely-domestic German firms). It is a definitive answer — not a failure —
407
+ > and is not billed.
408
+
409
+ ---
410
+
411
+ ## 🇫🇷 E-Reporting / Compliance (DGFiP Flux 10)
412
+
413
+ Full French e-reporting integration: enroll a company with the compliance provider, record and
414
+ submit transactions (individually or via CSV import), and track the resulting DGFiP tax reports.
415
+ All `compliance` routes require an active E-Reporting subscription / feature grant on your
416
+ account — except `requestEReportingAccess()`, which is how you ask for one.
417
+
418
+ ### Request feature activation
419
+
420
+ Not enabled yet? This route is deliberately **not** gated — it requests activation of the
421
+ E-Reporting feature for your account (the authenticated user's name/email take precedence over
422
+ the form values):
423
+
424
+ ```ts
425
+ await client.eReporting.requestEReportingAccess({
426
+ company: 'Example GmbH',
427
+ phone: '+43 660 1234567',
428
+ message: 'Please activate e-reporting for our account.',
429
+ language: 'de', // locale of the confirmation mail
430
+ });
431
+ ```
432
+
433
+ ### 1. SIRENE lookup (prefill enrollment data)
434
+
435
+ Resolve company data by SIRET, SIREN or French VAT number via the French SIRENE registry.
436
+ **Rate-limited to 3 requests/minute per user** (protects the external French government API).
437
+
438
+ ```ts
439
+ const company = await client.eReporting.sireneLookup('FR32123456789'); // or a SIRET/SIREN
440
+
441
+ console.log(company.companyName, company.siret);
442
+ console.log(company.nafCode, company.enterpriseSize); // e.g. "47", "pme"
443
+ ```
444
+
445
+ ### 2. Create an enrollment
446
+
447
+ Creates the local enrollment, provisions the provider account and (by default) activates the
448
+ DGFiP regime. Either `siret` or `siren` is required. On provider failure the API responds 502
449
+ (an `HttpException`) and the enrollment is persisted in an error state for retries.
450
+
451
+ ```ts
452
+ const enrollment = await client.eReporting.createEnrollment({
453
+ siret: company.siret!, // or siren: company.siren
454
+ companyName: company.companyName,
455
+ address: company.address ?? undefined,
456
+ city: company.city ?? undefined,
457
+ postalcode: company.postalcode ?? undefined,
458
+ email: 'tax@acme.fr',
459
+ nafCode: company.nafCode!, // 2-digit NAF division, e.g. "47"
460
+ enterpriseSize: 'pme', // 'micro' | 'pme' | 'eti' | 'ge'
461
+ typeOperation: 'mixed', // 'services' | 'goods' | 'mixed'
462
+ reportingStartDate: '2026-09-01', // or a Date
463
+ autoActivate: true, // optional — false creates the account without regime activation
464
+ });
465
+
466
+ console.log(enrollment.id, enrollment.status, enrollment.statusLabel);
467
+
468
+ const page = await client.eReporting.listEnrollments(1, 25); // paginated
469
+ const single = await client.eReporting.getEnrollment(enrollment.id);
470
+ ```
471
+
472
+ ### 3. Record & submit transactions
473
+
474
+ Records the transaction and (by default) submits it right away; pass `submitNow: false` to defer.
475
+ Creation is **idempotent** on the invoice's natural key (enrollment, type, invoice number,
476
+ invoice date, counterparty VAT) — a client retry returns the existing transaction instead of
477
+ creating a duplicate.
478
+
479
+ > **Note:** Immediate per-invoice submission (and `submitTransaction()`) requires the
480
+ > **e-invoicing** feature on your account. In pure e-reporting mode transactions are only
481
+ > recorded here and reported automatically via the aggregated daily ledgers —
482
+ > `submitTransaction()` then returns a 422 `ValidationException` explaining this.
483
+
484
+ ```ts
485
+ const tx = await client.eReporting.createTransaction({
486
+ complianceEnrollmentId: enrollment.id,
487
+ transactionType: 'b2c_outbound', // | 'b2b_domestic_outbound' | 'b2b_domestic_inbound'
488
+ // | 'crossborder_outbound' | 'crossborder_inbound'
489
+ invoiceNumber: 'TKT-2026-00001',
490
+ invoiceDate: '2026-07-01', // or a Date
491
+ currency: 'EUR', // optional, default EUR
492
+ subtotal: 100,
493
+ taxAmount: 20,
494
+ total: 120,
495
+ invoiceLines: [
496
+ {
497
+ description: 'Ticket',
498
+ quantity: 1,
499
+ price: 100,
500
+ taxes: [{ name: 'TVA', percent: 20, category: 'S' }], // category E needs a VATEX `comment`
501
+ },
502
+ ],
503
+ submitNow: false, // record now, submit later
504
+ });
505
+
506
+ // Submit (or retry) a pending/error transaction later — replays the stored invoice lines
507
+ const submitted = await client.eReporting.submitTransaction(tx.id);
508
+ console.log(submitted.state, submitted.stateLabel); // e.g. "submitted"
509
+
510
+ // Manage recorded transactions (only `pending` ones can be edited/deleted)
511
+ const transactions = await client.eReporting.listTransactions({
512
+ dateFrom: '2026-07-01', // or a Date
513
+ dateTo: '2026-07-31',
514
+ state: 'pending', // 'pending' | 'sending' | 'submitted' | 'error'
515
+ transactionType: 'b2c_outbound',
516
+ page: 1,
517
+ perPage: 25,
518
+ });
519
+ const one = await client.eReporting.getTransaction(tx.id);
520
+ await client.eReporting.updateTransaction(tx.id, { total: 130, taxAmount: 21.67 });
521
+ await client.eReporting.deleteTransaction(tx.id); // resolves void on 204
522
+ ```
523
+
524
+ ### CSV import
525
+
526
+ Bulk-import from a semicolon-separated CSV (one row per invoice line; rows sharing an
527
+ `invoice_number` are grouped into one transaction). Idempotent: re-uploading skips rows whose
528
+ invoice already exists.
529
+
530
+ ```ts
531
+ import { readFileSync } from 'fs';
532
+
533
+ const result = await client.eReporting.importTransactions(
534
+ enrollment.id,
535
+ readFileSync('transactions.csv', 'utf8'),
536
+ 'transactions.csv', // optional filename
537
+ );
538
+
539
+ console.log(result.created, result.skippedDuplicates);
540
+ for (const error of result.errors) console.warn(error); // row-level errors
541
+ ```
542
+
543
+ ### 4. Track tax reports
544
+
545
+ Every submitted transaction is tracked through the aggregated DGFiP ledger lifecycle
546
+ (`new → sent → acknowledged → registered` or `refused`/`error`).
547
+
548
+ ```ts
549
+ const reports = await client.eReporting.listTaxReports(1, 25, 'refused'); // state filter optional
550
+ for (const report of reports) {
551
+ console.log(report.state, report.stateLabel, report.isTerminal, report.refusalReason);
552
+ }
553
+
554
+ const report = await client.eReporting.getTaxReport(reports.rows[0]!.id);
555
+ console.log(report.baseAmount, report.taxAmount, report.totalAmount); // precision-safe strings
556
+ ```
557
+
558
+ ### VAT rates & DGFiP tax categories
559
+
560
+ The canonical rates for a reporting country (the seller's reporting-country rates). Use these to
561
+ build the `taxes` entries of your invoice lines.
562
+
563
+ ```ts
564
+ const rates = await client.eReporting.getVatRates('FR'); // defaults to FR when omitted
565
+
566
+ for (const rate of rates.rates) {
567
+ console.log(rate.percent, rate.category, rate.taxName, rate.requiresVatex);
568
+ // 20 'S' 'TVA' false … 0 'E' 'TVA' true (VATEX code required in the line's comment)
569
+ }
570
+ ```
571
+
572
+ ### Revenue statistics
573
+
574
+ Read-only turnover aggregation over your e-reporting transactions — totals, a time series and
575
+ breakdowns by transaction type, counterparty country and state. All headline figures are
576
+ expressed in the most frequent currency in the range (`primaryCurrency`); when more than one
577
+ currency is present, `isMultiCurrency` is `true` and `byCurrency` lists each one. Monetary values
578
+ are returned as 4-decimal strings to preserve precision.
579
+
580
+ ```typescript
581
+ const stats = await client.eReporting.getRevenueStatistics({
582
+ dateFrom: '2026-01-01', // optional — defaults to 12 months ago (server-side)
583
+ dateTo: new Date(), // optional — defaults to today
584
+ interval: 'month', // 'day' | 'week' | 'month' (default 'month')
585
+ transactionType: 'b2c_outbound', // optional filter
586
+ state: 'submitted', // optional filter
587
+ });
588
+
589
+ console.log(stats.primaryCurrency); // e.g. EUR
590
+ console.log(stats.totals.total); // e.g. 120000.0000
591
+ for (const bucket of stats.timeSeries) {
592
+ console.log(`${bucket.bucket}: ${bucket.total}`); // 2026-01: 10800.0000
593
+ }
594
+ for (const row of stats.byCountry) {
595
+ console.log(`${row.country} → ${row.total}`); // 'unknown' groups NULL partners
596
+ }
597
+ ```
598
+
599
+ ---
600
+
304
601
  ## 📜 Certificates
305
602
 
306
603
  ### Single Certificate (PDF)