@kahitsan/ksui 0.46.1 → 0.47.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "ksui is a standalone set of SolidJS UI components for KahitSan/Hilinga and any SolidJS app. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with only solid-js externalized; it depends on nothing but solid-js + lucide-solid and injects its own CSS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Opt-in tax-ID capture next to the payee picker: shows the picked payee's TIN
|
|
2
|
+
// and address, offers inputs when they are missing, and warns when withholding
|
|
3
|
+
// is on but no TIN is known.
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { render, fireEvent, waitFor } from "@solidjs/testing-library";
|
|
6
|
+
import TransactionForm, { type TransactionFormProps } from "./TransactionForm";
|
|
7
|
+
import { isValidTin } from "./payee-tin";
|
|
8
|
+
|
|
9
|
+
function baseProps(overrides: Partial<TransactionFormProps> = {}): TransactionFormProps {
|
|
10
|
+
const noop = vi.fn();
|
|
11
|
+
return {
|
|
12
|
+
error: "",
|
|
13
|
+
saving: false,
|
|
14
|
+
category: "expense",
|
|
15
|
+
setCategory: noop,
|
|
16
|
+
subcategory: "",
|
|
17
|
+
setSubcategory: noop,
|
|
18
|
+
sourceAccount: "",
|
|
19
|
+
setSourceAccount: noop,
|
|
20
|
+
destAccount: "",
|
|
21
|
+
setDestAccount: noop,
|
|
22
|
+
amount: "",
|
|
23
|
+
setAmount: noop,
|
|
24
|
+
description: "",
|
|
25
|
+
setDescription: noop,
|
|
26
|
+
notes: "",
|
|
27
|
+
setNotes: noop,
|
|
28
|
+
date: "2026-09-10",
|
|
29
|
+
setDate: noop,
|
|
30
|
+
isPrivate: false,
|
|
31
|
+
setIsPrivate: noop,
|
|
32
|
+
sharedWith: [],
|
|
33
|
+
setSharedWith: noop,
|
|
34
|
+
sharedRoleCodes: [],
|
|
35
|
+
setSharedRoleCodes: noop,
|
|
36
|
+
backdateReason: "",
|
|
37
|
+
setBackdateReason: noop,
|
|
38
|
+
payee: "Acme Hardware",
|
|
39
|
+
setPayee: noop,
|
|
40
|
+
payeeId: 7,
|
|
41
|
+
setPayeeId: noop,
|
|
42
|
+
refNumber: "",
|
|
43
|
+
setRefNumber: noop,
|
|
44
|
+
taxType: "vat_inclusive",
|
|
45
|
+
setTaxType: noop,
|
|
46
|
+
hasEwt: false,
|
|
47
|
+
setHasEwt: noop,
|
|
48
|
+
ewtRate: "",
|
|
49
|
+
setEwtRate: noop,
|
|
50
|
+
payableKind: "",
|
|
51
|
+
setPayableKind: noop,
|
|
52
|
+
dueDate: "",
|
|
53
|
+
setDueDate: noop,
|
|
54
|
+
chequeNumber: "",
|
|
55
|
+
setChequeNumber: noop,
|
|
56
|
+
pdcStatus: "",
|
|
57
|
+
setPdcStatus: noop,
|
|
58
|
+
transferFeeEnabled: false,
|
|
59
|
+
setTransferFeeEnabled: noop,
|
|
60
|
+
transferFeeAmount: "",
|
|
61
|
+
setTransferFeeAmount: noop,
|
|
62
|
+
allowTransferFee: false,
|
|
63
|
+
pendingFiles: [],
|
|
64
|
+
setPendingFiles: noop,
|
|
65
|
+
accounts: [],
|
|
66
|
+
orgMembers: [],
|
|
67
|
+
shareableRoles: [],
|
|
68
|
+
isAdmin: true,
|
|
69
|
+
canShare: false,
|
|
70
|
+
isBackdated: false,
|
|
71
|
+
saleItems: [],
|
|
72
|
+
setSaleItems: noop,
|
|
73
|
+
saleClient: null,
|
|
74
|
+
setSaleClient: noop,
|
|
75
|
+
saleVoucher: null,
|
|
76
|
+
setSaleVoucher: noop,
|
|
77
|
+
saleManualDiscount: "",
|
|
78
|
+
setSaleManualDiscount: noop,
|
|
79
|
+
onSubmit: noop,
|
|
80
|
+
submitLabel: "Save",
|
|
81
|
+
...overrides,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const tinProps = (extra: Partial<TransactionFormProps> = {}) =>
|
|
86
|
+
baseProps({
|
|
87
|
+
payeeTin: "",
|
|
88
|
+
setPayeeTin: vi.fn(),
|
|
89
|
+
payeeAddress: "",
|
|
90
|
+
setPayeeAddress: vi.fn(),
|
|
91
|
+
...extra,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
function mockPayee(record: Record<string, unknown>) {
|
|
95
|
+
vi.stubGlobal(
|
|
96
|
+
"fetch",
|
|
97
|
+
vi.fn(async () => new Response(JSON.stringify(record), { status: 200 })),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
beforeEach(() => vi.unstubAllGlobals());
|
|
102
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
103
|
+
|
|
104
|
+
describe("isValidTin", () => {
|
|
105
|
+
it("accepts 9 or 12 digits with or without dashes", () => {
|
|
106
|
+
expect(isValidTin("123456789")).toBe(true);
|
|
107
|
+
expect(isValidTin("123-456-789")).toBe(true);
|
|
108
|
+
expect(isValidTin("123-456-789-000")).toBe(true);
|
|
109
|
+
expect(isValidTin("123456789000")).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
it("rejects other lengths and non-digits", () => {
|
|
112
|
+
expect(isValidTin("12345678")).toBe(false);
|
|
113
|
+
expect(isValidTin("1234567890")).toBe(false);
|
|
114
|
+
expect(isValidTin("12a-456-789")).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
it("treats blank as valid because the field is optional", () => {
|
|
117
|
+
expect(isValidTin("")).toBe(true);
|
|
118
|
+
expect(isValidTin(" ")).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("TransactionForm payee tax ID", () => {
|
|
123
|
+
it("renders nothing extra when the host does not opt in", async () => {
|
|
124
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: null, address: null });
|
|
125
|
+
const { queryByTestId } = render(() => <TransactionForm {...baseProps()} />);
|
|
126
|
+
await Promise.resolve();
|
|
127
|
+
expect(queryByTestId("form-payee-tin-input")).toBeNull();
|
|
128
|
+
expect(queryByTestId("form-payee-tin-readonly")).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("shows the saved TIN and address read-only and offers no input", async () => {
|
|
132
|
+
mockPayee({
|
|
133
|
+
id: 7,
|
|
134
|
+
name: "Acme Hardware",
|
|
135
|
+
kind: "vendor",
|
|
136
|
+
tin: "123-456-789-00000",
|
|
137
|
+
address: "1 Main St",
|
|
138
|
+
});
|
|
139
|
+
const { findByTestId, queryByTestId } = render(() => <TransactionForm {...tinProps()} />);
|
|
140
|
+
const line = await findByTestId("form-payee-tin-readonly");
|
|
141
|
+
expect(line.textContent).toContain("123-456-789-00000");
|
|
142
|
+
expect(line.textContent).toContain("1 Main St");
|
|
143
|
+
expect(queryByTestId("form-payee-tin-input")).toBeNull();
|
|
144
|
+
expect(queryByTestId("form-payee-address-input")).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("offers TIN and address inputs when the payee has neither", async () => {
|
|
148
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: null, address: null });
|
|
149
|
+
const setPayeeTin = vi.fn();
|
|
150
|
+
const setPayeeAddress = vi.fn();
|
|
151
|
+
const { findByTestId } = render(() => (
|
|
152
|
+
<TransactionForm {...tinProps({ setPayeeTin, setPayeeAddress })} />
|
|
153
|
+
));
|
|
154
|
+
const tin = (await findByTestId("form-payee-tin-input")) as HTMLInputElement;
|
|
155
|
+
fireEvent.input(tin, { target: { value: "123-456-789" } });
|
|
156
|
+
expect(setPayeeTin).toHaveBeenCalledWith("123-456-789");
|
|
157
|
+
const addr = (await findByTestId("form-payee-address-input")) as HTMLInputElement;
|
|
158
|
+
fireEvent.input(addr, { target: { value: "2 Side St" } });
|
|
159
|
+
expect(setPayeeAddress).toHaveBeenCalledWith("2 Side St");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("asks only for the address when the TIN is already saved", async () => {
|
|
163
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: "123456789", address: "" });
|
|
164
|
+
const { findByTestId, queryByTestId } = render(() => <TransactionForm {...tinProps()} />);
|
|
165
|
+
await findByTestId("form-payee-address-input");
|
|
166
|
+
expect(queryByTestId("form-payee-tin-input")).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("flags a malformed TIN", async () => {
|
|
170
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: null, address: null });
|
|
171
|
+
const { findByTestId, queryByTestId } = render(() => (
|
|
172
|
+
<TransactionForm {...tinProps({ payeeTin: "12-34" })} />
|
|
173
|
+
));
|
|
174
|
+
await findByTestId("form-payee-tin-input");
|
|
175
|
+
expect(queryByTestId("form-payee-tin-error")).toBeTruthy();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("does not flag a valid TIN", async () => {
|
|
179
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: null, address: null });
|
|
180
|
+
const { findByTestId, queryByTestId } = render(() => (
|
|
181
|
+
<TransactionForm {...tinProps({ payeeTin: "123-456-789" })} />
|
|
182
|
+
));
|
|
183
|
+
await findByTestId("form-payee-tin-input");
|
|
184
|
+
expect(queryByTestId("form-payee-tin-error")).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("warns, without blocking, when withholding is on and no TIN is known", async () => {
|
|
188
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: null, address: null });
|
|
189
|
+
const { findByTestId } = render(() => <TransactionForm {...tinProps({ hasEwt: true })} />);
|
|
190
|
+
const warn = await findByTestId("form-payee-tin-ewt-warning");
|
|
191
|
+
expect(warn.textContent).toMatch(/TIN/);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("drops the warning once a TIN is typed or already saved", async () => {
|
|
195
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: null, address: null });
|
|
196
|
+
const typed = render(() => (
|
|
197
|
+
<TransactionForm {...tinProps({ hasEwt: true, payeeTin: "123-456-789" })} />
|
|
198
|
+
));
|
|
199
|
+
await typed.findByTestId("form-payee-tin-input");
|
|
200
|
+
expect(typed.queryByTestId("form-payee-tin-ewt-warning")).toBeNull();
|
|
201
|
+
typed.unmount();
|
|
202
|
+
|
|
203
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "vendor", tin: "123456789", address: "x" });
|
|
204
|
+
const saved = render(() => <TransactionForm {...tinProps({ hasEwt: true })} />);
|
|
205
|
+
await saved.findByTestId("form-payee-tin-readonly");
|
|
206
|
+
expect(saved.queryByTestId("form-payee-tin-ewt-warning")).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("shows nothing before a payee is chosen", async () => {
|
|
210
|
+
mockPayee({});
|
|
211
|
+
const { queryByTestId } = render(() => (
|
|
212
|
+
<TransactionForm {...tinProps({ payee: "", payeeId: null, hasEwt: true })} />
|
|
213
|
+
));
|
|
214
|
+
await waitFor(() => expect(queryByTestId("form-payee-tin-input")).toBeNull());
|
|
215
|
+
expect(queryByTestId("form-payee-tin-ewt-warning")).toBeNull();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("stays out of sales", async () => {
|
|
219
|
+
mockPayee({ id: 7, name: "Acme Hardware", kind: "customer", tin: null, address: null });
|
|
220
|
+
const { queryByTestId } = render(() => <TransactionForm {...tinProps({ category: "sale" })} />);
|
|
221
|
+
await Promise.resolve();
|
|
222
|
+
expect(queryByTestId("form-payee-tin-input")).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -25,6 +25,7 @@ import TransactionAccountFields from "./TransactionAccountFields";
|
|
|
25
25
|
import TransactionPayableFields from "./TransactionPayableFields";
|
|
26
26
|
import ComboBox from "./ComboBox";
|
|
27
27
|
import type { ClientOption, PayeeOption, PayeeKind } from "./picker-types";
|
|
28
|
+
import { isValidTin } from "./payee-tin";
|
|
28
29
|
import VoucherPicker, { type VoucherOption } from "./VoucherPicker";
|
|
29
30
|
import MentionTextarea from "./MentionTextarea";
|
|
30
31
|
import SearchableSelect from "./SearchableSelect";
|
|
@@ -105,6 +106,13 @@ async function createPayee(
|
|
|
105
106
|
return (await res.json()) as PayeeOption;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
async function loadPayee(id: number): Promise<PayeeOption | null> {
|
|
110
|
+
const r = await fetch(`/api/payees/${id}`, { credentials: "include" });
|
|
111
|
+
if (!r.ok) return null;
|
|
112
|
+
const json = (await r.json()) as PayeeOption & { data?: PayeeOption };
|
|
113
|
+
return json.data ?? json;
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
function payeeSecondary(p: PayeeOption): string | null {
|
|
109
117
|
if (!p.default_subcategory && p.kind === "vendor") return null;
|
|
110
118
|
return (
|
|
@@ -248,6 +256,13 @@ export interface TransactionFormProps {
|
|
|
248
256
|
setPayee: (v: string) => void;
|
|
249
257
|
payeeId: number | null;
|
|
250
258
|
setPayeeId: (v: number | null) => void;
|
|
259
|
+
/** Opt in to tax-ID capture for the picked payee by supplying `setPayeeTin`.
|
|
260
|
+
* The form shows the saved TIN/address, or inputs when they are missing, and
|
|
261
|
+
* reports typed values here; the host decides when and how to save them. */
|
|
262
|
+
payeeTin?: string;
|
|
263
|
+
setPayeeTin?: (v: string) => void;
|
|
264
|
+
payeeAddress?: string;
|
|
265
|
+
setPayeeAddress?: (v: string) => void;
|
|
251
266
|
refNumber: string;
|
|
252
267
|
setRefNumber: (v: string) => void;
|
|
253
268
|
taxType: string;
|
|
@@ -360,6 +375,27 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
360
375
|
setSelectedPayee(null);
|
|
361
376
|
}
|
|
362
377
|
});
|
|
378
|
+
// A payee picked from search already carries its tax ID; one restored from a
|
|
379
|
+
// saved row does not, so load it before deciding what to show.
|
|
380
|
+
createEffect(() => {
|
|
381
|
+
const sel = selectedPayee();
|
|
382
|
+
if (!props.setPayeeTin || !sel || sel.tin !== undefined) return;
|
|
383
|
+
const id = sel.id;
|
|
384
|
+
loadPayee(id)
|
|
385
|
+
.then((rec) => {
|
|
386
|
+
const cur = selectedPayee();
|
|
387
|
+
if (!rec || !cur || cur.id !== id) return;
|
|
388
|
+
setSelectedPayee({ ...cur, tin: rec.tin ?? null, address: rec.address ?? null });
|
|
389
|
+
})
|
|
390
|
+
.catch(() => {});
|
|
391
|
+
});
|
|
392
|
+
const taxPayee = () =>
|
|
393
|
+
props.setPayeeTin && props.category !== "sale" && catConfig().showPayee
|
|
394
|
+
? selectedPayee()
|
|
395
|
+
: null;
|
|
396
|
+
const savedTin = () => !!taxPayee()?.tin?.trim();
|
|
397
|
+
const savedAddress = () => !!taxPayee()?.address?.trim();
|
|
398
|
+
const tinKnown = () => taxPayee()?.tin !== undefined;
|
|
363
399
|
let dragCounter = 0;
|
|
364
400
|
let formFileInput: HTMLInputElement | undefined;
|
|
365
401
|
|
|
@@ -756,6 +792,8 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
756
792
|
setSelectedPayee(p);
|
|
757
793
|
props.setPayee(p ? p.name : "");
|
|
758
794
|
props.setPayeeId(p ? p.id : null);
|
|
795
|
+
props.setPayeeTin?.("");
|
|
796
|
+
props.setPayeeAddress?.("");
|
|
759
797
|
}}
|
|
760
798
|
/>
|
|
761
799
|
</FormField>
|
|
@@ -774,6 +812,62 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
774
812
|
</Show>
|
|
775
813
|
</FormField>
|
|
776
814
|
</div>
|
|
815
|
+
<Show when={taxPayee() && tinKnown()}>
|
|
816
|
+
<div class="space-y-2" data-testid="form-payee-tax-id">
|
|
817
|
+
<Show when={savedTin()}>
|
|
818
|
+
<p
|
|
819
|
+
data-testid="form-payee-tin-readonly"
|
|
820
|
+
class="text-xs text-[var(--ks-fg-subtle,#71717a)]"
|
|
821
|
+
>
|
|
822
|
+
TIN {taxPayee()!.tin}
|
|
823
|
+
{savedAddress() ? ` · ${taxPayee()!.address}` : ""}
|
|
824
|
+
</p>
|
|
825
|
+
</Show>
|
|
826
|
+
<div class="grid grid-cols-2 gap-4">
|
|
827
|
+
<Show when={!savedTin()}>
|
|
828
|
+
<FormField label="TIN (optional)">
|
|
829
|
+
<input
|
|
830
|
+
type="text"
|
|
831
|
+
inputmode="numeric"
|
|
832
|
+
data-testid="form-payee-tin-input"
|
|
833
|
+
value={props.payeeTin ?? ""}
|
|
834
|
+
onInput={(e) => props.setPayeeTin!(e.currentTarget.value)}
|
|
835
|
+
class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
|
|
836
|
+
placeholder="000-000-000-000"
|
|
837
|
+
/>
|
|
838
|
+
<Show when={!isValidTin(props.payeeTin ?? "")}>
|
|
839
|
+
<p
|
|
840
|
+
data-testid="form-payee-tin-error"
|
|
841
|
+
class="mt-1 text-[10px] text-[var(--ks-danger,#ef4444)]"
|
|
842
|
+
>
|
|
843
|
+
A TIN has 9 digits, or 12 with the branch code.
|
|
844
|
+
</p>
|
|
845
|
+
</Show>
|
|
846
|
+
</FormField>
|
|
847
|
+
</Show>
|
|
848
|
+
<Show when={!savedAddress() && props.setPayeeAddress}>
|
|
849
|
+
<FormField label="Address (optional)">
|
|
850
|
+
<input
|
|
851
|
+
type="text"
|
|
852
|
+
data-testid="form-payee-address-input"
|
|
853
|
+
value={props.payeeAddress ?? ""}
|
|
854
|
+
onInput={(e) => props.setPayeeAddress!(e.currentTarget.value)}
|
|
855
|
+
class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
|
|
856
|
+
placeholder="Printed on the invoice"
|
|
857
|
+
/>
|
|
858
|
+
</FormField>
|
|
859
|
+
</Show>
|
|
860
|
+
</div>
|
|
861
|
+
<Show when={props.hasEwt && !savedTin() && !(props.payeeTin ?? "").trim()}>
|
|
862
|
+
<p
|
|
863
|
+
data-testid="form-payee-tin-ewt-warning"
|
|
864
|
+
class="text-[11px] text-[var(--ks-warning,#f59e0b)]"
|
|
865
|
+
>
|
|
866
|
+
Withholding needs the payee's TIN for the 2307 certificate. You can save without it.
|
|
867
|
+
</p>
|
|
868
|
+
</Show>
|
|
869
|
+
</div>
|
|
870
|
+
</Show>
|
|
777
871
|
</Show>
|
|
778
872
|
<Show when={!catConfig().showPayee}>
|
|
779
873
|
<FormField label="Reference #">
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Tax-ID helpers shared by the transaction form and its hosts, so both sides
|
|
2
|
+
// apply the same rule before a TIN is saved.
|
|
3
|
+
|
|
4
|
+
const TIN_PATTERN = /^\d{9}(\d{3})?$/;
|
|
5
|
+
|
|
6
|
+
/** Trims the input and drops dashes and spaces: "123-456-789" -> "123456789". */
|
|
7
|
+
export function normalizeTin(value: string): string {
|
|
8
|
+
return value.replace(/[\s-]/g, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A TIN is 9 digits, or 12 with the branch code. Blank is valid (optional). */
|
|
12
|
+
export function isValidTin(value: string): boolean {
|
|
13
|
+
const digits = normalizeTin(value);
|
|
14
|
+
return digits === "" || TIN_PATTERN.test(digits);
|
|
15
|
+
}
|
|
@@ -14,6 +14,9 @@ export interface PayeeOption {
|
|
|
14
14
|
kind: PayeeKind;
|
|
15
15
|
default_subcategory?: string | null;
|
|
16
16
|
notes?: string | null;
|
|
17
|
+
/** Tax ID on record. `undefined` means not loaded yet; `null`/"" means none. */
|
|
18
|
+
tin?: string | null;
|
|
19
|
+
address?: string | null;
|
|
17
20
|
is_active?: boolean;
|
|
18
21
|
}
|
|
19
22
|
|
package/src/index.ts
CHANGED
|
@@ -212,6 +212,7 @@ export {
|
|
|
212
212
|
// Shared domain option shapes for the common pickers, decoupled from any
|
|
213
213
|
// component (still imported across transactions / counter / payees).
|
|
214
214
|
export type { ClientOption, PayeeOption, PayeeKind } from "./components/composite/picker-types";
|
|
215
|
+
export { isValidTin, normalizeTin } from "./components/composite/payee-tin";
|
|
215
216
|
|
|
216
217
|
export { default as VoucherPicker, calculateDiscount } from "./components/composite/VoucherPicker";
|
|
217
218
|
export type { VoucherOption } from "./components/composite/VoucherPicker";
|