81e9dd224e
* feat(import): add customer and supplier parsing functionality - Implemented customer file parsing in `lib/import/customers/parser.ts` with support for Excel and CSV formats. - Created types for detected customer columns and parsed customer rows in `lib/import/customers/types.ts`. - Added tests for customer classification logic in `lib/import/shared/__tests__/classify.test.ts`. - Developed classification functions for customers and suppliers in `lib/import/shared/classify.ts`. - Introduced shared column utility functions in `lib/import/shared/column-utils.ts`. - Implemented supplier file parsing in `lib/import/suppliers/parser.ts` with validation for various fields. - Created types for detected supplier columns and parsed supplier rows in `lib/import/suppliers/types.ts`. - Added tests for supplier column detection and parsing in `lib/import/suppliers/__tests__/column-detector.test.ts` and `lib/import/suppliers/__tests__/parser.test.ts`. * fix(labels): update 'Svenskt företag' to 'Svenskt företag eller organisation' for clarity * feat(import): refactor encoding handling for Swedish files and add tests for character preservation * feat(recapt): implement clearRecaptIdentity function and integrate into logout flow * feat(bookkeeping): implement copy functionality and next voucher sequence retrieval * feat(import): enhance customer and supplier import functionality with normalization and event handling
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import * as XLSX from 'xlsx'
|
|
import { decodeFileContent } from './encoding'
|
|
|
|
/**
|
|
* Read a workbook from a raw file buffer, with correct encoding handling
|
|
* for CSV files.
|
|
*
|
|
* For binary spreadsheet formats (.xlsx, .xls, .ods), xlsx handles encoding
|
|
* via the embedded codepage and we pass the buffer through as `type: 'array'`.
|
|
*
|
|
* For CSV files, xlsx with `type: 'array'` decodes bytes as Latin-1, which
|
|
* mangles UTF-8 multi-byte sequences (e.g. Ö → Ö). We instead detect the
|
|
* source encoding (UTF-8 with optional BOM, or Windows-1252) and decode to
|
|
* a string before handing it to xlsx as `type: 'string'`.
|
|
*/
|
|
export function readWorkbookFromBuffer(buffer: ArrayBuffer, filename: string): XLSX.WorkBook {
|
|
const ext = filename.toLowerCase().split('.').pop() ?? ''
|
|
if (ext === 'csv') {
|
|
const content = decodeFileContent(buffer)
|
|
return XLSX.read(content, { type: 'string' })
|
|
}
|
|
return XLSX.read(buffer, { type: 'array' })
|
|
}
|
|
|
|
/**
|
|
* Read the workbook from `buffer` and return raw rows from its largest sheet.
|
|
*
|
|
* Picks the sheet with the most rows (a heuristic that handles files where
|
|
* the header sheet isn't the first one). Returns rows as a 2D string array
|
|
* with the header row included; cells default to empty string.
|
|
*/
|
|
export function readBestSheet(
|
|
buffer: ArrayBuffer,
|
|
filename: string,
|
|
): { sheetName: string; rawData: string[][] } {
|
|
const workbook = readWorkbookFromBuffer(buffer, filename)
|
|
|
|
let bestSheet = workbook.SheetNames[0]
|
|
let bestRowCount = 0
|
|
for (const name of workbook.SheetNames) {
|
|
const sheet = workbook.Sheets[name]
|
|
const range = XLSX.utils.decode_range(sheet['!ref'] || 'A1')
|
|
const rowCount = range.e.r - range.s.r + 1
|
|
if (rowCount > bestRowCount) {
|
|
bestRowCount = rowCount
|
|
bestSheet = name
|
|
}
|
|
}
|
|
|
|
const sheet = workbook.Sheets[bestSheet]
|
|
const rawData: string[][] = XLSX.utils.sheet_to_json(sheet, {
|
|
header: 1,
|
|
defval: '',
|
|
raw: false,
|
|
})
|
|
|
|
return { sheetName: bestSheet, rawData }
|
|
}
|