Add Documents tab and include document metadata in financial export
Validate / validate (push) Successful in 26s
Validate / validate (push) Successful in 26s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,9 @@ import {
|
|||||||
users,
|
users,
|
||||||
companyBankAccounts,
|
companyBankAccounts,
|
||||||
companyCards,
|
companyCards,
|
||||||
companyAddresses
|
companyAddresses,
|
||||||
|
companyDocuments,
|
||||||
|
companyDocumentVersions
|
||||||
} from '../db/schema.js';
|
} from '../db/schema.js';
|
||||||
import { csvBuild } from '$lib/utils/csv.js';
|
import { csvBuild } from '$lib/utils/csv.js';
|
||||||
import { CARRIER_LABELS } from '../shipping/index.js';
|
import { CARRIER_LABELS } from '../shipping/index.js';
|
||||||
@@ -74,6 +76,7 @@ export async function buildFinancialExport(
|
|||||||
` company_bank_accounts.csv — company bank accounts`,
|
` company_bank_accounts.csv — company bank accounts`,
|
||||||
` company_cards.csv — company credit/debit cards (last 4 only)`,
|
` company_cards.csv — company credit/debit cards (last 4 only)`,
|
||||||
` company_addresses.csv — legal/shipping/billing/other addresses`,
|
` company_addresses.csv — legal/shipping/billing/other addresses`,
|
||||||
|
` company_documents.csv — uploaded document metadata (files not bundled)`,
|
||||||
` projects.csv — all projects (active + inactive)`,
|
` projects.csv — all projects (active + inactive)`,
|
||||||
` parties.csv — all customers/suppliers (incl. archived; see deletedAt)`,
|
` parties.csv — all customers/suppliers (incl. archived; see deletedAt)`,
|
||||||
` employees.csv — all employees (incl. terminated/archived)`,
|
` employees.csv — all employees (incl. terminated/archived)`,
|
||||||
@@ -210,6 +213,77 @@ export async function buildFinancialExport(
|
|||||||
zip.file('company_addresses.csv', withBom(csvBuild(rows)));
|
zip.file('company_addresses.csv', withBom(csvBuild(rows)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── company_documents.csv ──────────────────────────
|
||||||
|
{
|
||||||
|
const docRows = await db
|
||||||
|
.select()
|
||||||
|
.from(companyDocuments)
|
||||||
|
.where(eq(companyDocuments.companyId, companyId))
|
||||||
|
.orderBy(asc(companyDocuments.category), asc(companyDocuments.title));
|
||||||
|
|
||||||
|
// Latest version per document (joined)
|
||||||
|
const latestByDoc = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
versionNumber: number;
|
||||||
|
fileName: string;
|
||||||
|
mimeType: string;
|
||||||
|
sizeBytes: number;
|
||||||
|
uploadedBy: string | null;
|
||||||
|
uploadedAt: Date;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
if (docRows.length > 0) {
|
||||||
|
const versionRows = await db
|
||||||
|
.select({
|
||||||
|
documentId: companyDocumentVersions.documentId,
|
||||||
|
versionNumber: companyDocumentVersions.versionNumber,
|
||||||
|
fileName: companyDocumentVersions.fileName,
|
||||||
|
mimeType: companyDocumentVersions.mimeType,
|
||||||
|
sizeBytes: companyDocumentVersions.sizeBytes,
|
||||||
|
uploadedBy: companyDocumentVersions.uploadedBy,
|
||||||
|
uploadedAt: companyDocumentVersions.uploadedAt
|
||||||
|
})
|
||||||
|
.from(companyDocumentVersions)
|
||||||
|
.where(
|
||||||
|
inArray(
|
||||||
|
companyDocumentVersions.documentId,
|
||||||
|
docRows.map((d) => d.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for (const v of versionRows) {
|
||||||
|
const existing = latestByDoc.get(v.documentId);
|
||||||
|
if (!existing || v.versionNumber > existing.versionNumber) {
|
||||||
|
latestByDoc.set(v.documentId, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows: unknown[][] = [
|
||||||
|
[
|
||||||
|
'id', 'category', 'customLabel', 'title', 'description', 'expiresAt',
|
||||||
|
'currentVersion', 'currentFilename', 'currentSizeBytes', 'currentMimeType',
|
||||||
|
'uploadedBy', 'uploadedAt', 'deletedAt', 'createdAt', 'updatedAt'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
for (const d of docRows) {
|
||||||
|
const latest = latestByDoc.get(d.id);
|
||||||
|
rows.push([
|
||||||
|
d.id, d.category, d.customLabel ?? '', d.title, d.description ?? '',
|
||||||
|
d.expiresAt ?? '',
|
||||||
|
latest?.versionNumber ?? '',
|
||||||
|
latest?.fileName ?? '',
|
||||||
|
latest?.sizeBytes ?? '',
|
||||||
|
latest?.mimeType ?? '',
|
||||||
|
latest?.uploadedBy ?? '',
|
||||||
|
latest?.uploadedAt.toISOString() ?? '',
|
||||||
|
d.deletedAt ? d.deletedAt.toISOString() : '',
|
||||||
|
d.createdAt.toISOString(), d.updatedAt.toISOString()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
zip.file('company_documents.csv', withBom(csvBuild(rows)));
|
||||||
|
}
|
||||||
|
|
||||||
// ── projects.csv ────────────────────────────────────
|
// ── projects.csv ────────────────────────────────────
|
||||||
const projectRows = await db
|
const projectRows = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -33,7 +33,10 @@
|
|||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(data.companyRoles.some((r) => r === 'admin' || r === 'manager' || r === 'accountant')
|
...(data.companyRoles.some((r) => r === 'admin' || r === 'manager' || r === 'accountant')
|
||||||
? [{ href: `/companies/${data.company.id}/profile`, label: 'Profile' }]
|
? [
|
||||||
|
{ href: `/companies/${data.company.id}/profile`, label: 'Profile' },
|
||||||
|
{ href: `/companies/${data.company.id}/documents`, label: 'Documents' }
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
...(data.companyRoles.includes('admin') || data.companyRoles.includes('accountant')
|
...(data.companyRoles.includes('admin') || data.companyRoles.includes('accountant')
|
||||||
? [{ href: `/companies/${data.company.id}/export`, label: 'Export' }]
|
? [{ href: `/companies/${data.company.id}/export`, label: 'Export' }]
|
||||||
|
|||||||
Reference in New Issue
Block a user