@kevludwig/lexware-office 0.4.1 → 0.4.2

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
@@ -90,7 +90,7 @@ The ledger needs somewhere to keep one small JSON record per invoice. `JsonStore
90
90
 
91
91
  ## Tests
92
92
 
93
- `npm test` runs 56 tests against a fake API — no account, no network. `test/fake-api.ts` shows the pattern: routes answer by method and path, and every call is recorded.
93
+ `npm test` runs 59 tests against a fake API — no account, no network. `test/fake-api.ts` shows the pattern: routes answer by method and path, and every call is recorded.
94
94
 
95
95
  ## Not affiliated with Lexware
96
96
 
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { DEFAULT_APP_URL, DEFAULT_BASE_URL, LexwareApiError, createLexwareClient
2
2
  export { contactEmail, createCustomer, findContactByNumber, findContactsByName, MIN_NAME_SEARCH_LENGTH, getContact, resolveContactByName, type Contact, type CreatedCustomer, type NewCustomer, } from "./contacts.ts";
3
3
  export { addDays, berlinDay, daysSince } from "./dates.ts";
4
4
  export { downloadInvoicePdf, findOverdueInvoice, findOverdueInvoices, type OverdueInvoice, } from "./receivables.ts";
5
- export { DEFAULT_REMINDER_POLICY, daysPastReminderDue, earliestReminderDate, isDueImmediately, isOpenForReminder, reminderDueDate, reminderLedger, selectDueReminders, type DueReminder, type ReminderHold, type ReminderLedger, type ReminderPolicy, type ReminderRecord, type ReminderSelection, type ReminderStatus, mailContentSha256, paymentReminderEffect, sendReminder, type PaymentReminderEffect, type ReminderTarget, type SendReminderOutcome, } from "./reminders.ts";
5
+ export { DEFAULT_REMINDER_POLICY, daysPastReminderDue, earliestReminderDate, isDueImmediately, isOpenForReminder, reminderDueDate, nextPaymentDay, germanNationwideHolidays, germanPaymentDay, reminderLedger, selectDueReminders, type DueReminder, type ReminderHold, type ReminderLedger, type ReminderPolicy, type ReminderRecord, type ReminderSelection, type ReminderStatus, mailContentSha256, paymentReminderEffect, sendReminder, type PaymentReminderEffect, type ReminderTarget, type SendReminderOutcome, } from "./reminders.ts";
6
6
  export { MailNotSentError, defaultReminderMail, reminderSubject, type ReminderMail, type ReminderMailData, type ReminderSender, type RenderReminderMail, type SendReminderMail, } from "./reminder-mail.ts";
7
7
  export { keySegment, memoryStore, type JsonStore } from "./store.ts";
8
8
  export { DUPLICATE_TOTAL_TOLERANCE, ITEMS_TOTAL_TOLERANCE, lineNet, netTotal, round2, taxFromGross, taxFromNet, type PricedLine, } from "./money.ts";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { DEFAULT_APP_URL, DEFAULT_BASE_URL, LexwareApiError, createLexwareClient
2
2
  export { contactEmail, createCustomer, findContactByNumber, findContactsByName, MIN_NAME_SEARCH_LENGTH, getContact, resolveContactByName, } from "./contacts.js";
3
3
  export { addDays, berlinDay, daysSince } from "./dates.js";
4
4
  export { downloadInvoicePdf, findOverdueInvoice, findOverdueInvoices, } from "./receivables.js";
5
- export { DEFAULT_REMINDER_POLICY, daysPastReminderDue, earliestReminderDate, isDueImmediately, isOpenForReminder, reminderDueDate, reminderLedger, selectDueReminders, mailContentSha256, paymentReminderEffect, sendReminder, } from "./reminders.js";
5
+ export { DEFAULT_REMINDER_POLICY, daysPastReminderDue, earliestReminderDate, isDueImmediately, isOpenForReminder, reminderDueDate, nextPaymentDay, germanNationwideHolidays, germanPaymentDay, reminderLedger, selectDueReminders, mailContentSha256, paymentReminderEffect, sendReminder, } from "./reminders.js";
6
6
  export { MailNotSentError, defaultReminderMail, reminderSubject, } from "./reminder-mail.js";
7
7
  export { keySegment, memoryStore } from "./store.js";
8
8
  export { DUPLICATE_TOTAL_TOLERANCE, ITEMS_TOTAL_TOLERANCE, lineNet, netTotal, round2, taxFromGross, taxFromNet, } from "./money.js";
@@ -26,7 +26,20 @@ export interface ReminderPolicy {
26
26
  pendingValidHours: number;
27
27
  /** A declined reminder stays quiet this many days. Default: 7. */
28
28
  declinedQuietDays: number;
29
+ /**
30
+ * Whether a day (yyyy-MM-dd) counts as a day by which money can arrive.
31
+ * Set, a due date on another day moves to the next one that does — as § 193
32
+ * BGB moves a deadline off weekends and holidays. Not set: due dates count
33
+ * as they are. `germanPaymentDay` covers weekends and nationwide holidays.
34
+ */
35
+ isPaymentDay?: (day: string) => boolean;
29
36
  }
37
+ /** The day itself, or the next one `isPaymentDay` accepts (at most two weeks on). */
38
+ export declare function nextPaymentDay(day: string, isPaymentDay?: (day: string) => boolean): string;
39
+ /** Germany's nationwide public holidays of a year (yyyy-MM-dd). Regional ones are left to the caller. */
40
+ export declare function germanNationwideHolidays(year: number): string[];
41
+ /** A weekday that is no nationwide public holiday in Germany — when banks book. */
42
+ export declare function germanPaymentDay(day: string): boolean;
30
43
  export declare const DEFAULT_REMINDER_POLICY: ReminderPolicy;
31
44
  interface Dated {
32
45
  voucherDate: string;
package/dist/reminders.js CHANGED
@@ -19,6 +19,56 @@ import { downloadInvoicePdf, findOverdueInvoice, findOverdueInvoices } from "./r
19
19
  import { createHash } from "node:crypto";
20
20
  import { MailNotSentError } from "./reminder-mail.js";
21
21
  import { keySegment } from "./store.js";
22
+ /** The day itself, or the next one `isPaymentDay` accepts (at most two weeks on). */
23
+ export function nextPaymentDay(day, isPaymentDay) {
24
+ if (!isPaymentDay)
25
+ return day;
26
+ let current = day;
27
+ for (let step = 0; step < 14 && !isPaymentDay(current); step++)
28
+ current = addDays(current, 1);
29
+ return current;
30
+ }
31
+ /** Easter Sunday of a year (Gregorian, anonymous algorithm), yyyy-MM-dd. */
32
+ function easterSunday(year) {
33
+ const a = year % 19;
34
+ const b = Math.floor(year / 100);
35
+ const c = year % 100;
36
+ const d = Math.floor(b / 4);
37
+ const e = b % 4;
38
+ const f = Math.floor((b + 8) / 25);
39
+ const g = Math.floor((b - f + 1) / 3);
40
+ const h = (19 * a + b - d - g + 15) % 30;
41
+ const i = Math.floor(c / 4);
42
+ const k = c % 4;
43
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
44
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
45
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
46
+ const dayOfMonth = ((h + l - 7 * m + 114) % 31) + 1;
47
+ return `${year}-${String(month).padStart(2, "0")}-${String(dayOfMonth).padStart(2, "0")}`;
48
+ }
49
+ /** Germany's nationwide public holidays of a year (yyyy-MM-dd). Regional ones are left to the caller. */
50
+ export function germanNationwideHolidays(year) {
51
+ const easter = easterSunday(year);
52
+ return [
53
+ `${year}-01-01`,
54
+ addDays(easter, -2),
55
+ addDays(easter, 1),
56
+ `${year}-05-01`,
57
+ addDays(easter, 39),
58
+ addDays(easter, 50),
59
+ `${year}-10-03`,
60
+ `${year}-12-25`,
61
+ `${year}-12-26`,
62
+ ];
63
+ }
64
+ /** A weekday that is no nationwide public holiday in Germany — when banks book. */
65
+ export function germanPaymentDay(day) {
66
+ const date = new Date(`${day.slice(0, 10)}T00:00:00Z`);
67
+ const weekday = date.getUTCDay();
68
+ if (weekday === 0 || weekday === 6)
69
+ return false;
70
+ return !germanNationwideHolidays(date.getUTCFullYear()).includes(day.slice(0, 10));
71
+ }
22
72
  export const DEFAULT_REMINDER_POLICY = {
23
73
  minDaysOverdue: 3,
24
74
  immediatePaymentTermDays: 14,
@@ -31,9 +81,10 @@ export function isDueImmediately(invoice) {
31
81
  }
32
82
  /** The day from which lateness counts (yyyy-MM-dd). */
33
83
  export function reminderDueDate(invoice, policy = DEFAULT_REMINDER_POLICY) {
34
- return isDueImmediately(invoice)
84
+ const due = isDueImmediately(invoice)
35
85
  ? addDays(invoice.voucherDate, policy.immediatePaymentTermDays)
36
86
  : invoice.dueDate.slice(0, 10);
87
+ return nextPaymentDay(due, policy.isPaymentDay);
37
88
  }
38
89
  /** Days past the reminder due date. */
39
90
  export function daysPastReminderDue(invoice, policy = DEFAULT_REMINDER_POLICY, today) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevludwig/lexware-office",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Typed client and domain logic for the Lexware Office Public API: contacts, receivables, payment reminder rules.",
5
5
  "type": "module",
6
6
  "license": "MIT",