@paro.io/expert-shared-components 1.14.51 → 1.14.52
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/lib/components/TaxAxis/TaxAxisShell.js +19 -3
- package/lib/tax-axis/components/documents/TaxAxisDocuments.d.ts +3 -1
- package/lib/tax-axis/components/documents/TaxAxisDocuments.js +32 -50
- package/lib/tax-axis/lib/data/documents.d.ts +3 -3
- package/lib/tax-axis/lib/data/documents.js +221 -26
- package/lib/tax-axis/lib/types/index.d.ts +3 -0
- package/package.json +1 -1
|
@@ -56,6 +56,20 @@ function toInt(value) {
|
|
|
56
56
|
const parsed = Number(String(value || '0').replace(/[^\d.-]/g, ''));
|
|
57
57
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
58
58
|
}
|
|
59
|
+
const ENTITY_DISPLAY_TO_KEY = {
|
|
60
|
+
'S-Corporation': 'S_CORP',
|
|
61
|
+
'C-Corporation': 'C_CORP',
|
|
62
|
+
'LLC': 'LLC',
|
|
63
|
+
'Partnership': 'PARTNERSHIP',
|
|
64
|
+
'Sole Proprietor': 'SOLE_PROP',
|
|
65
|
+
'Sole Proprietorship': 'SOLE_PROP',
|
|
66
|
+
};
|
|
67
|
+
function entityTypeKey(entity) {
|
|
68
|
+
var _a;
|
|
69
|
+
if (!entity)
|
|
70
|
+
return undefined;
|
|
71
|
+
return (_a = ENTITY_DISPLAY_TO_KEY[entity]) !== null && _a !== void 0 ? _a : entity;
|
|
72
|
+
}
|
|
59
73
|
function buildSessionInput(profile) {
|
|
60
74
|
const taxYear = Number(profile.year) || new Date().getFullYear();
|
|
61
75
|
return {
|
|
@@ -138,7 +152,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
138
152
|
setStep('DOCUMENT_UPLOAD');
|
|
139
153
|
}), [createSessionIfNeeded]);
|
|
140
154
|
const handleUploadDocument = (0, react_1.useCallback)((doc, file) => __awaiter(void 0, void 0, void 0, function* () {
|
|
141
|
-
var _a;
|
|
155
|
+
var _a, _b;
|
|
142
156
|
const ensuredSessionId = sessionId || (profile ? yield createSessionIfNeeded(profile) : null);
|
|
143
157
|
if (!ensuredSessionId) {
|
|
144
158
|
throw new Error('Unable to upload without a session.');
|
|
@@ -168,7 +182,9 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
168
182
|
fileName: file.name,
|
|
169
183
|
fileType: file.type || 'application/octet-stream',
|
|
170
184
|
fileSize: file.size,
|
|
171
|
-
|
|
185
|
+
// doc.documentType is the canonical lowercase snake-case key (e.g. "profit_loss").
|
|
186
|
+
// doc.id equals doc.documentType for the v2 catalog.
|
|
187
|
+
documentType: (_b = doc.documentType) !== null && _b !== void 0 ? _b : doc.id,
|
|
172
188
|
s3Key: uploadClient.state.fileName,
|
|
173
189
|
};
|
|
174
190
|
return taxAxisApi.uploadFile(uploadInput);
|
|
@@ -252,7 +268,7 @@ const TaxAxisShell = ({ taxAxisApi, userContext = 'expert', initialSessionId, in
|
|
|
252
268
|
react_1.default.createElement(TaxAxisProspectReport_1.TaxAxisProspectReport, { profile: profile, userContext: userContext, onUpgrade: () => setStep('DOCUMENT_UPLOAD'), onPresent: () => setStep('PRESENTATION'), onReset: handleReset })));
|
|
253
269
|
case 'DOCUMENT_UPLOAD':
|
|
254
270
|
return (react_1.default.createElement(ShellContainer, null,
|
|
255
|
-
react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, userContext: userContext, onUploadDocument: handleUploadDocument, onDeleteDocument: handleDeleteDocument, fetchUploadedDocuments: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
271
|
+
react_1.default.createElement(TaxAxisDocuments_1.TaxAxisDocuments, { profile: profile, entityType: entityTypeKey(profile.entity), userContext: userContext, onUploadDocument: handleUploadDocument, onDeleteDocument: handleDeleteDocument, fetchUploadedDocuments: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
256
272
|
const ensuredSessionId = sessionId || (profile ? yield createSessionIfNeeded(profile) : null);
|
|
257
273
|
if (!ensuredSessionId) {
|
|
258
274
|
return [];
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ClientProfile, TaxAxisScreenProps } from "../../lib/types";
|
|
3
|
+
import { TaxAxisEntityTypeKey } from "../../lib/data/documents";
|
|
3
4
|
import { DocState } from "./DocumentCard";
|
|
4
5
|
export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
|
|
5
6
|
profile: ClientProfile;
|
|
7
|
+
entityType?: TaxAxisEntityTypeKey | string | null;
|
|
6
8
|
onContinue: () => void;
|
|
7
9
|
onBack: () => void;
|
|
8
10
|
onUploadDocument?: (doc: DocState, file: File) => Promise<void | {
|
|
@@ -21,4 +23,4 @@ export interface TaxAxisDocumentsProps extends TaxAxisScreenProps {
|
|
|
21
23
|
updatedAt?: string | null;
|
|
22
24
|
}>>;
|
|
23
25
|
}
|
|
24
|
-
export declare function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
|
|
26
|
+
export declare function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext, }: TaxAxisDocumentsProps): React.JSX.Element;
|
|
@@ -37,24 +37,6 @@ const react_1 = __importStar(require("react"));
|
|
|
37
37
|
const documents_1 = require("../../lib/data/documents");
|
|
38
38
|
const TaxAxisButton_1 = require("../shared/TaxAxisButton");
|
|
39
39
|
const DocumentTier_1 = require("./DocumentTier");
|
|
40
|
-
const STUB_FILENAMES = {
|
|
41
|
-
"1120s": "2025_1120S.pdf",
|
|
42
|
-
"state-return": "2025_State_Return.pdf",
|
|
43
|
-
"payroll": "2025_Payroll.xlsx",
|
|
44
|
-
"pnl": "QBO_PnL_2025.pdf",
|
|
45
|
-
"balance": "QBO_BS_2025.pdf",
|
|
46
|
-
"cashflow": "QBO_CashFlow_2025.pdf",
|
|
47
|
-
"fixed-assets": "Fixed_Assets.xlsx",
|
|
48
|
-
"prior-returns": "Prior_Returns.pdf",
|
|
49
|
-
};
|
|
50
|
-
const HELP_OVERRIDES = {
|
|
51
|
-
"1120s": "Improves accuracy of every strategy",
|
|
52
|
-
"payroll": "Critical for Entity Structure and QBI calculations",
|
|
53
|
-
"fixed-assets": "Unlocks Cost Segregation (potential $15K\u201385K)",
|
|
54
|
-
"state-return": "If client operates in income-tax states",
|
|
55
|
-
"cashflow": "Helps with income deferral and timing strategies",
|
|
56
|
-
"prior-returns": "Improves confidence intervals (more years = tighter estimates)",
|
|
57
|
-
};
|
|
58
40
|
const TIER_DEFS = [
|
|
59
41
|
{
|
|
60
42
|
key: "required",
|
|
@@ -64,7 +46,7 @@ const TIER_DEFS = [
|
|
|
64
46
|
labelColor: "#C53030",
|
|
65
47
|
badgeColor: "red",
|
|
66
48
|
badgeText: "REQUIRED",
|
|
67
|
-
ids: [
|
|
49
|
+
ids: [],
|
|
68
50
|
},
|
|
69
51
|
{
|
|
70
52
|
key: "recommended",
|
|
@@ -74,7 +56,7 @@ const TIER_DEFS = [
|
|
|
74
56
|
labelColor: "#FB9A1D",
|
|
75
57
|
badgeColor: "orange",
|
|
76
58
|
badgeText: "RECOMMENDED",
|
|
77
|
-
ids: [
|
|
59
|
+
ids: [],
|
|
78
60
|
},
|
|
79
61
|
{
|
|
80
62
|
key: "conditional",
|
|
@@ -84,16 +66,21 @@ const TIER_DEFS = [
|
|
|
84
66
|
labelColor: "#C8CCE5",
|
|
85
67
|
badgeColor: "neutral",
|
|
86
68
|
badgeText: "CONDITIONAL",
|
|
87
|
-
ids: [
|
|
69
|
+
ids: [],
|
|
88
70
|
},
|
|
89
71
|
];
|
|
90
|
-
function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
|
|
91
|
-
const
|
|
92
|
-
|
|
72
|
+
function TaxAxisDocuments({ profile, entityType, onContinue, onBack, onUploadDocument, onDeleteDocument, fetchUploadedDocuments, userContext: _userContext = "expert", }) {
|
|
73
|
+
const docSpecs = (0, react_1.useMemo)(() => (0, documents_1.getDocSpecs)(entityType !== null && entityType !== void 0 ? entityType : undefined), [entityType]);
|
|
74
|
+
// Build tier defs dynamically from the doc spec list so they're always in sync.
|
|
75
|
+
const tierDefs = (0, react_1.useMemo)(() => {
|
|
76
|
+
return TIER_DEFS.map((def) => (Object.assign(Object.assign({}, def), { ids: docSpecs
|
|
77
|
+
.filter((s) => s.tier === def.key)
|
|
78
|
+
.map((s) => s.id) })));
|
|
79
|
+
}, [docSpecs]);
|
|
93
80
|
const [docs, setDocs] = (0, react_1.useState)(() => docSpecs.map((s) => (Object.assign(Object.assign({}, s), { status: "empty", fileName: null, parseError: null }))));
|
|
94
81
|
const [uploadedDocIds, setUploadedDocIds] = (0, react_1.useState)({});
|
|
95
82
|
const [continueError, setContinueError] = (0, react_1.useState)(null);
|
|
96
|
-
const pollForParseStatus = (idx, fileName,
|
|
83
|
+
const pollForParseStatus = (idx, fileName, documentType) => __awaiter(this, void 0, void 0, function* () {
|
|
97
84
|
if (!fetchUploadedDocuments)
|
|
98
85
|
return;
|
|
99
86
|
const maxAttempts = 80;
|
|
@@ -101,11 +88,11 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
101
88
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
102
89
|
try {
|
|
103
90
|
const uploaded = yield fetchUploadedDocuments();
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const candidates =
|
|
91
|
+
// Match by documentType first (exact lowercase key), fall back to fileName.
|
|
92
|
+
const typeMatch = uploaded.filter((doc) => doc.fileName === fileName &&
|
|
93
|
+
String(doc.documentType || "").toLowerCase() === documentType.toLowerCase());
|
|
94
|
+
const nameMatch = uploaded.filter((doc) => doc.fileName === fileName);
|
|
95
|
+
const candidates = typeMatch.length > 0 ? typeMatch : nameMatch;
|
|
109
96
|
const match = candidates
|
|
110
97
|
.slice()
|
|
111
98
|
.sort((a, b) => new Date(String(b.updatedAt || "")).getTime() -
|
|
@@ -128,7 +115,7 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
128
115
|
}
|
|
129
116
|
}
|
|
130
117
|
}
|
|
131
|
-
catch (
|
|
118
|
+
catch (_pollError) {
|
|
132
119
|
// keep polling
|
|
133
120
|
}
|
|
134
121
|
yield new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
@@ -141,13 +128,14 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
141
128
|
return;
|
|
142
129
|
setContinueError(null);
|
|
143
130
|
setDocs((prev) => prev.map((d, i) => i === idx
|
|
144
|
-
? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name
|
|
131
|
+
? Object.assign(Object.assign({}, d), { status: "validating", fileName: file.name, parseError: null }) : d));
|
|
145
132
|
try {
|
|
146
133
|
const uploadResult = onUploadDocument
|
|
147
134
|
? yield onUploadDocument(selectedDoc, file)
|
|
148
135
|
: undefined;
|
|
149
|
-
const nextFileName = file.name
|
|
150
|
-
|
|
136
|
+
const nextFileName = file.name;
|
|
137
|
+
// Use the canonical documentType from the catalog entry.
|
|
138
|
+
const documentType = selectedDoc.documentType;
|
|
151
139
|
const immediateStatus = String((uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.status) || "").toUpperCase();
|
|
152
140
|
if (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.documentId) {
|
|
153
141
|
setUploadedDocIds((prev) => (Object.assign(Object.assign({}, prev), { [idx]: uploadResult.documentId })));
|
|
@@ -162,9 +150,8 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
162
150
|
? Object.assign(Object.assign({}, d), { status: "failed", fileName: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.fileName) || nextFileName, parseError: (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.parseError) || "Parsing failed. Please upload a correct document." }) : d));
|
|
163
151
|
return;
|
|
164
152
|
}
|
|
165
|
-
setDocs((prev) => prev.map((d, i) => i === idx
|
|
166
|
-
|
|
167
|
-
yield pollForParseStatus(idx, nextFileName, expectedDocumentType);
|
|
153
|
+
setDocs((prev) => prev.map((d, i) => i === idx ? Object.assign(Object.assign({}, d), { status: "parsing", fileName: nextFileName }) : d));
|
|
154
|
+
yield pollForParseStatus(idx, nextFileName, documentType);
|
|
168
155
|
}
|
|
169
156
|
catch (uploadError) {
|
|
170
157
|
const errorMessage = uploadError instanceof Error
|
|
@@ -185,8 +172,8 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
185
172
|
try {
|
|
186
173
|
yield onDeleteDocument(docId);
|
|
187
174
|
}
|
|
188
|
-
catch (
|
|
189
|
-
//
|
|
175
|
+
catch (_deleteError) {
|
|
176
|
+
// clear from UI even if backend delete fails
|
|
190
177
|
}
|
|
191
178
|
}
|
|
192
179
|
setUploadedDocIds((prev) => {
|
|
@@ -209,16 +196,11 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
209
196
|
const validCount = docs.filter((d) => d.status === "valid").length;
|
|
210
197
|
const failedCount = docs.filter((d) => d.status === "failed").length;
|
|
211
198
|
const busyCount = docs.filter((d) => d.status === "validating" || d.status === "parsing").length;
|
|
212
|
-
const coveragePct = validCount === 0
|
|
213
|
-
?
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
? 60
|
|
218
|
-
: validCount <= 4
|
|
219
|
-
? 75
|
|
220
|
-
: validCount <= 6
|
|
221
|
-
? 90
|
|
199
|
+
const coveragePct = validCount === 0 ? 32
|
|
200
|
+
: validCount === 1 ? 50
|
|
201
|
+
: validCount === 2 ? 60
|
|
202
|
+
: validCount <= 4 ? 75
|
|
203
|
+
: validCount <= 6 ? 90
|
|
222
204
|
: 100;
|
|
223
205
|
const strategiesCovered = Math.round((25 * coveragePct) / 100);
|
|
224
206
|
return (react_1.default.createElement("div", { className: "max-w-[580px] mx-auto" },
|
|
@@ -262,7 +244,7 @@ function TaxAxisDocuments({ profile, onContinue, onBack, onUploadDocument, onDel
|
|
|
262
244
|
border: "1px solid rgba(197,48,48,0.3)",
|
|
263
245
|
color: "#FEB2B2",
|
|
264
246
|
} }, continueError)),
|
|
265
|
-
|
|
247
|
+
tierDefs.map((tier) => (react_1.default.createElement(DocumentTier_1.DocumentTier, { key: tier.key, tier: tier, docs: docs, helpOverrides: {}, onUpload: handleUpload, onClear: handleClear, onRemove: handleRemove }))),
|
|
266
248
|
react_1.default.createElement("div", { className: "flex gap-3 mt-6" },
|
|
267
249
|
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { variant: "secondary", onClick: onBack }, "Back"),
|
|
268
250
|
react_1.default.createElement(TaxAxisButton_1.TaxAxisButton, { onClick: handleContinue, className: "flex-1", disabled: busyCount > 0 }, busyCount > 0
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
export
|
|
3
|
-
export declare function getDocSpecs(
|
|
1
|
+
import { DocSpec } from "../types";
|
|
2
|
+
export type TaxAxisEntityTypeKey = "S_CORP" | "C_CORP" | "PARTNERSHIP" | "LLC" | "SOLE_PROP";
|
|
3
|
+
export declare function getDocSpecs(entityType?: TaxAxisEntityTypeKey | string | null): DocSpec[];
|
|
@@ -1,33 +1,228 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// ═══════════════════════════════════════════════════════════════════
|
|
3
|
-
// TaxAxis — Document
|
|
4
|
-
//
|
|
3
|
+
// TaxAxis — Document catalog (v2, aligned with parser v2 / LLM parser)
|
|
4
|
+
//
|
|
5
|
+
// documentType values are the canonical lowercase snake-case keys that the
|
|
6
|
+
// backend documentCatalog uses. These are sent verbatim on the
|
|
7
|
+
// uploadTaxAxisDocument mutation.
|
|
5
8
|
// ═══════════════════════════════════════════════════════════════════
|
|
6
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.DOC_SPECS_BASE = void 0;
|
|
8
10
|
exports.getDocSpecs = getDocSpecs;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const DOC_META = {
|
|
12
|
+
federal_tax_return: {
|
|
13
|
+
name: "Federal Tax Return (1120S / 1065 / 1040 / 1120)",
|
|
14
|
+
strategies: ["QBI §199A", "S-Corp Structure", "§179 Expensing", "Bonus Depreciation", "Business Interest §163(j)"],
|
|
15
|
+
tier: "recommended",
|
|
16
|
+
help: "Improves accuracy of every strategy. Startups may not have prior returns.",
|
|
17
|
+
},
|
|
18
|
+
profit_loss: {
|
|
19
|
+
name: "Profit & Loss Statement (Current Year)",
|
|
20
|
+
strategies: ["QBI §199A", "R&D Credit", "Business Meals", "Professional Fees", "Home Office"],
|
|
21
|
+
tier: "required",
|
|
22
|
+
help: "Required — current year P&L is the minimum input for savings calculations.",
|
|
23
|
+
},
|
|
24
|
+
balance_sheet: {
|
|
25
|
+
name: "Balance Sheet (Current Year)",
|
|
26
|
+
strategies: ["Business Interest §163(j)", "§179 Expensing", "Opportunity Zone"],
|
|
27
|
+
tier: "required",
|
|
28
|
+
help: "Required — needed for asset-based strategies and financial health assessment.",
|
|
29
|
+
},
|
|
30
|
+
payroll_records: {
|
|
31
|
+
name: "Payroll Records (W-3 / 941)",
|
|
32
|
+
strategies: ["S-Corp Salary", "HSA Maximization", "WOTC", "Overtime Deduction"],
|
|
33
|
+
tier: "recommended",
|
|
34
|
+
help: "Critical for Entity Structure and QBI calculations.",
|
|
35
|
+
},
|
|
36
|
+
fixed_asset_schedule: {
|
|
37
|
+
name: "Fixed Asset Schedule",
|
|
38
|
+
strategies: ["§179", "Bonus Depreciation §168(k)", "Cost Segregation"],
|
|
39
|
+
tier: "conditional",
|
|
40
|
+
help: "Required for real estate clients (cost segregation). Optional otherwise.",
|
|
41
|
+
},
|
|
42
|
+
state_tax_return: {
|
|
43
|
+
name: "State Return(s)",
|
|
44
|
+
strategies: ["SALT / PTE Optimization", "State Nexus Analysis"],
|
|
45
|
+
tier: "conditional",
|
|
46
|
+
help: "Required if client operates in income-tax states.",
|
|
47
|
+
},
|
|
48
|
+
schedule_k1_s_corp: {
|
|
49
|
+
name: "Schedule K-1 (S-Corp, 1120S)",
|
|
50
|
+
strategies: ["QBI §199A", "S-Corp Basis", "Distributions"],
|
|
51
|
+
tier: "recommended",
|
|
52
|
+
help: "One per shareholder — needed for QBI and basis calculations.",
|
|
53
|
+
},
|
|
54
|
+
schedule_k1_partnership: {
|
|
55
|
+
name: "Schedule K-1 (Partnership, 1065)",
|
|
56
|
+
strategies: ["QBI §199A", "SE Tax", "At-Risk Basis"],
|
|
57
|
+
tier: "recommended",
|
|
58
|
+
help: "One per partner — needed for SE tax and QBI calculations.",
|
|
59
|
+
},
|
|
60
|
+
schedule_se: {
|
|
61
|
+
name: "Schedule SE (Self-Employment Tax)",
|
|
62
|
+
strategies: ["SE Tax Deduction", "S-Corp Conversion"],
|
|
63
|
+
tier: "conditional",
|
|
64
|
+
help: "Required for sole props and GP partners with SE income.",
|
|
65
|
+
},
|
|
66
|
+
form_8829: {
|
|
67
|
+
name: "Form 8829 (Home Office)",
|
|
68
|
+
strategies: ["Home Office Deduction"],
|
|
69
|
+
tier: "conditional",
|
|
70
|
+
help: "Only for sole proprietors who use part of their home for business.",
|
|
71
|
+
},
|
|
72
|
+
form_2553: {
|
|
73
|
+
name: "Form 2553 (S-Corp Election)",
|
|
74
|
+
strategies: ["S-Corp Structure", "Entity Restructuring"],
|
|
75
|
+
tier: "conditional",
|
|
76
|
+
help: "Confirms S-Corp election date and effective year — needed for entity analysis.",
|
|
77
|
+
},
|
|
78
|
+
w3: {
|
|
79
|
+
name: "W-3 (Transmittal of W-2s)",
|
|
80
|
+
strategies: ["S-Corp Salary", "HSA Maximization", "WOTC"],
|
|
81
|
+
tier: "recommended",
|
|
82
|
+
help: "Total wages across all employees — cross-checks payroll records.",
|
|
83
|
+
},
|
|
84
|
+
form_1099_nec_summary: {
|
|
85
|
+
name: "1099-NEC Summary",
|
|
86
|
+
strategies: ["Contractor vs Employee", "R&D Credit"],
|
|
87
|
+
tier: "conditional",
|
|
88
|
+
help: "Required if client paid $600+ to contractors.",
|
|
89
|
+
},
|
|
90
|
+
form_4562: {
|
|
91
|
+
name: "Form 4562 (Depreciation & Amortization)",
|
|
92
|
+
strategies: ["§179", "Bonus Depreciation", "Cost Segregation"],
|
|
93
|
+
tier: "conditional",
|
|
94
|
+
help: "Detailed depreciation schedule — unlocks Section 179 and bonus dep strategies.",
|
|
95
|
+
},
|
|
96
|
+
form_8889: {
|
|
97
|
+
name: "Form 8889 (HSA)",
|
|
98
|
+
strategies: ["HSA Maximization"],
|
|
99
|
+
tier: "conditional",
|
|
100
|
+
help: "Only if client has or is considering an HSA-eligible HDHP.",
|
|
101
|
+
},
|
|
102
|
+
rent_roll: {
|
|
103
|
+
name: "Rent Roll",
|
|
104
|
+
strategies: ["Real Estate — Cost Segregation", "QBI §199A (Rental)"],
|
|
105
|
+
tier: "conditional",
|
|
106
|
+
help: "Required for real estate clients to assess rental income and vacancy.",
|
|
107
|
+
},
|
|
108
|
+
mortgage_loan_documents: {
|
|
109
|
+
name: "Mortgage / Loan Documents",
|
|
110
|
+
strategies: ["Business Interest §163(j)", "Real Estate Strategies"],
|
|
111
|
+
tier: "conditional",
|
|
112
|
+
help: "1098s and loan statements — needed for interest limitation analysis.",
|
|
113
|
+
},
|
|
114
|
+
operating_agreement: {
|
|
115
|
+
name: "Operating Agreement / Partnership Agreement",
|
|
116
|
+
strategies: ["Entity Restructuring", "Special Allocations §704(b)"],
|
|
117
|
+
tier: "conditional",
|
|
118
|
+
help: "LLCs and partnerships — confirms ownership %, distributions, elections.",
|
|
119
|
+
},
|
|
120
|
+
rd_activity_documentation: {
|
|
121
|
+
name: "R&D Activity Documentation",
|
|
122
|
+
strategies: ["R&D Credit §41"],
|
|
123
|
+
tier: "conditional",
|
|
124
|
+
help: "Only if client conducts qualifying research activities.",
|
|
125
|
+
},
|
|
126
|
+
vehicle_mileage_log: {
|
|
127
|
+
name: "Vehicle Mileage Log",
|
|
128
|
+
strategies: ["Vehicle Deduction", "Standard vs Actual Method"],
|
|
129
|
+
tier: "conditional",
|
|
130
|
+
help: "Required for sole props claiming vehicle deductions.",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Accepted file types — aligned with BE ALLOWED_EXTENSIONS (no .doc)
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
const ACCEPT_PDF_DOCX = [".pdf", ".docx"];
|
|
137
|
+
const ACCEPT_PDF_DOCX_XLSX = [".pdf", ".docx", ".xlsx", ".xls", ".csv"];
|
|
138
|
+
const ACCEPT_PDF_DOCX_XLSX_IMG = [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".png", ".jpg", ".jpeg"];
|
|
139
|
+
const ACCEPT_BY_TYPE = {
|
|
140
|
+
federal_tax_return: ACCEPT_PDF_DOCX,
|
|
141
|
+
profit_loss: ACCEPT_PDF_DOCX_XLSX,
|
|
142
|
+
balance_sheet: ACCEPT_PDF_DOCX_XLSX,
|
|
143
|
+
payroll_records: ACCEPT_PDF_DOCX_XLSX,
|
|
144
|
+
fixed_asset_schedule: ACCEPT_PDF_DOCX_XLSX,
|
|
145
|
+
state_tax_return: ACCEPT_PDF_DOCX,
|
|
146
|
+
schedule_k1_s_corp: ACCEPT_PDF_DOCX,
|
|
147
|
+
schedule_k1_partnership: ACCEPT_PDF_DOCX,
|
|
148
|
+
schedule_se: ACCEPT_PDF_DOCX,
|
|
149
|
+
form_8829: ACCEPT_PDF_DOCX,
|
|
150
|
+
form_2553: ACCEPT_PDF_DOCX,
|
|
151
|
+
w3: ACCEPT_PDF_DOCX_XLSX_IMG,
|
|
152
|
+
form_1099_nec_summary: ACCEPT_PDF_DOCX_XLSX,
|
|
153
|
+
form_4562: ACCEPT_PDF_DOCX,
|
|
154
|
+
form_8889: ACCEPT_PDF_DOCX,
|
|
155
|
+
rent_roll: ACCEPT_PDF_DOCX_XLSX,
|
|
156
|
+
mortgage_loan_documents: ACCEPT_PDF_DOCX,
|
|
157
|
+
operating_agreement: ACCEPT_PDF_DOCX,
|
|
158
|
+
rd_activity_documentation: ACCEPT_PDF_DOCX_XLSX,
|
|
159
|
+
vehicle_mileage_log: ACCEPT_PDF_DOCX_XLSX,
|
|
160
|
+
};
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Entity → document types map (mirrors BE ENTITY_DOCUMENT_MAP exactly)
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
const S_CORP_TYPES = [
|
|
165
|
+
"profit_loss", "balance_sheet",
|
|
166
|
+
"federal_tax_return", "payroll_records", "w3", "schedule_k1_s_corp",
|
|
167
|
+
"form_2553", "form_1099_nec_summary", "state_tax_return", "form_4562",
|
|
168
|
+
"form_8889", "fixed_asset_schedule", "rent_roll", "mortgage_loan_documents",
|
|
169
|
+
"operating_agreement", "rd_activity_documentation",
|
|
19
170
|
];
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
171
|
+
const C_CORP_TYPES = [
|
|
172
|
+
"profit_loss", "balance_sheet",
|
|
173
|
+
"federal_tax_return", "payroll_records", "w3",
|
|
174
|
+
"form_1099_nec_summary", "state_tax_return", "form_4562", "form_8889",
|
|
175
|
+
"fixed_asset_schedule", "rent_roll", "mortgage_loan_documents",
|
|
176
|
+
"operating_agreement", "rd_activity_documentation",
|
|
177
|
+
];
|
|
178
|
+
const PARTNERSHIP_TYPES = [
|
|
179
|
+
"profit_loss", "balance_sheet",
|
|
180
|
+
"federal_tax_return", "payroll_records", "w3", "schedule_k1_partnership",
|
|
181
|
+
"schedule_se", "form_1099_nec_summary", "state_tax_return", "form_4562",
|
|
182
|
+
"form_8889", "fixed_asset_schedule", "rent_roll", "mortgage_loan_documents",
|
|
183
|
+
"operating_agreement", "rd_activity_documentation",
|
|
184
|
+
];
|
|
185
|
+
const SOLE_PROP_TYPES = [
|
|
186
|
+
"profit_loss", "balance_sheet",
|
|
187
|
+
"federal_tax_return", "schedule_se", "form_8829", "form_1099_nec_summary",
|
|
188
|
+
"state_tax_return", "form_4562", "form_8889", "fixed_asset_schedule",
|
|
189
|
+
"vehicle_mileage_log", "rent_roll", "mortgage_loan_documents",
|
|
190
|
+
"rd_activity_documentation",
|
|
191
|
+
];
|
|
192
|
+
const LLC_TYPES = Array.from(new Set([
|
|
193
|
+
...S_CORP_TYPES, ...C_CORP_TYPES, ...PARTNERSHIP_TYPES, ...SOLE_PROP_TYPES,
|
|
194
|
+
]));
|
|
195
|
+
const ENTITY_DOCUMENT_TYPES = {
|
|
196
|
+
S_CORP: S_CORP_TYPES,
|
|
197
|
+
C_CORP: C_CORP_TYPES,
|
|
198
|
+
PARTNERSHIP: PARTNERSHIP_TYPES,
|
|
199
|
+
SOLE_PROP: SOLE_PROP_TYPES,
|
|
200
|
+
LLC: LLC_TYPES,
|
|
201
|
+
};
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Build DocSpec list for a given entity type key
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
function buildDocSpec(documentType) {
|
|
206
|
+
const meta = DOC_META[documentType];
|
|
207
|
+
if (!meta)
|
|
208
|
+
return null;
|
|
209
|
+
return {
|
|
210
|
+
id: documentType,
|
|
211
|
+
documentType,
|
|
212
|
+
name: meta.name,
|
|
213
|
+
accept: ACCEPT_BY_TYPE[documentType] || ACCEPT_PDF_DOCX,
|
|
214
|
+
strategies: meta.strategies,
|
|
215
|
+
required: meta.tier === "required" ? true : meta.tier === "recommended" ? "optional" : "conditional",
|
|
216
|
+
tier: meta.tier,
|
|
217
|
+
help: meta.help,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function getDocSpecs(entityType) {
|
|
221
|
+
var _a;
|
|
222
|
+
const types = entityType
|
|
223
|
+
? ((_a = ENTITY_DOCUMENT_TYPES[entityType]) !== null && _a !== void 0 ? _a : LLC_TYPES)
|
|
224
|
+
: LLC_TYPES;
|
|
225
|
+
return types
|
|
226
|
+
.map(buildDocSpec)
|
|
227
|
+
.filter((s) => s !== null);
|
|
33
228
|
}
|
|
@@ -153,12 +153,15 @@ export interface ProspectStrategyMeta {
|
|
|
153
153
|
typicalRange: string;
|
|
154
154
|
prospectNote: string;
|
|
155
155
|
}
|
|
156
|
+
export type DocTier = "required" | "recommended" | "conditional";
|
|
156
157
|
export interface DocSpec {
|
|
157
158
|
id: string;
|
|
159
|
+
documentType: string;
|
|
158
160
|
name: string;
|
|
159
161
|
accept: string[];
|
|
160
162
|
strategies: string[];
|
|
161
163
|
required: boolean | "optional" | "conditional";
|
|
164
|
+
tier: DocTier;
|
|
162
165
|
help: string;
|
|
163
166
|
}
|
|
164
167
|
export interface SidebarLookupEntry {
|