148 lines
3.1 KiB
TypeScript
148 lines
3.1 KiB
TypeScript
|
|
import fs from 'node:fs'
|
||
|
|
import path from 'node:path'
|
||
|
|
import { prisma } from '../src/lib/prisma'
|
||
|
|
|
||
|
|
type CsvRow = {
|
||
|
|
ID?: string
|
||
|
|
DescriptionOfID?: string
|
||
|
|
Vat?: string
|
||
|
|
Type?: string
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseCsvLine(line: string): string[] {
|
||
|
|
const result: string[] = []
|
||
|
|
let current = ''
|
||
|
|
let inQuotes = false
|
||
|
|
|
||
|
|
for (let index = 0; index < line.length; index++) {
|
||
|
|
const char = line[index]
|
||
|
|
|
||
|
|
if (char === '"') {
|
||
|
|
if (inQuotes && line[index + 1] === '"') {
|
||
|
|
current += '"'
|
||
|
|
index++
|
||
|
|
} else {
|
||
|
|
inQuotes = !inQuotes
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
if (char === ',' && !inQuotes) {
|
||
|
|
result.push(current)
|
||
|
|
current = ''
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
current += char
|
||
|
|
}
|
||
|
|
|
||
|
|
result.push(current)
|
||
|
|
return result.map(value => value.trim())
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseCsv(content: string): CsvRow[] {
|
||
|
|
const lines = content
|
||
|
|
.split(/\r?\n/)
|
||
|
|
.map(line => line.trim())
|
||
|
|
.filter(Boolean)
|
||
|
|
|
||
|
|
if (!lines.length) return []
|
||
|
|
|
||
|
|
const headers = parseCsvLine(lines[0])
|
||
|
|
return lines.slice(1).map(line => {
|
||
|
|
const cols = parseCsvLine(line)
|
||
|
|
const row: Record<string, string> = {}
|
||
|
|
for (let index = 0; index < headers.length; index++) {
|
||
|
|
row[headers[index]] = cols[index] || ''
|
||
|
|
}
|
||
|
|
return row
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
function toBooleanFlags(typeValue: string) {
|
||
|
|
const normalized = typeValue || ''
|
||
|
|
const isPublic = normalized.includes('شناسه عمومی')
|
||
|
|
const isImported = normalized.includes('وارداتی')
|
||
|
|
const isDomestic = !isImported
|
||
|
|
|
||
|
|
return { isPublic, isDomestic }
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseVat(vatValue: string) {
|
||
|
|
const parsed = Number(vatValue || '0')
|
||
|
|
if (!Number.isFinite(parsed)) return 0
|
||
|
|
return parsed
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const defaultPath =
|
||
|
|
'/Users/ahasani/Desktop/product_good_2026-05-01T11-35-57_part_1_28812448-0dd9-411c-99e1-334c28d781fe.csv'
|
||
|
|
const csvPath = process.argv[2] || defaultPath
|
||
|
|
const absolutePath = path.resolve(csvPath)
|
||
|
|
|
||
|
|
if (!fs.existsSync(absolutePath)) {
|
||
|
|
throw new Error(`CSV file not found: ${absolutePath}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
const raw = fs.readFileSync(absolutePath, 'utf8').replace(/^\uFEFF/, '')
|
||
|
|
const rows = parseCsv(raw)
|
||
|
|
|
||
|
|
let upserted = 0
|
||
|
|
let skipped = 0
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
const code = (row.ID || '').trim()
|
||
|
|
const name = (row.DescriptionOfID || '').trim()
|
||
|
|
const vat = parseVat((row.Vat || '').trim())
|
||
|
|
const type = (row.Type || '').trim()
|
||
|
|
const { isPublic, isDomestic } = toBooleanFlags(type)
|
||
|
|
|
||
|
|
if (!code || !name) {
|
||
|
|
skipped++
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
await prisma.stockKeepingUnits.upsert({
|
||
|
|
where: { code },
|
||
|
|
create: {
|
||
|
|
code,
|
||
|
|
name,
|
||
|
|
VAT: vat,
|
||
|
|
type: 'GOLD',
|
||
|
|
is_public: isPublic,
|
||
|
|
is_domestic: isDomestic,
|
||
|
|
},
|
||
|
|
update: {
|
||
|
|
name,
|
||
|
|
VAT: vat,
|
||
|
|
type: 'GOLD',
|
||
|
|
is_public: isPublic,
|
||
|
|
is_domestic: isDomestic,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
upserted++
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
JSON.stringify(
|
||
|
|
{
|
||
|
|
csvPath: absolutePath,
|
||
|
|
totalRows: rows.length,
|
||
|
|
upserted,
|
||
|
|
skipped,
|
||
|
|
},
|
||
|
|
null,
|
||
|
|
2,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
main()
|
||
|
|
.catch(error => {
|
||
|
|
console.error(error)
|
||
|
|
process.exit(1)
|
||
|
|
})
|
||
|
|
.finally(async () => {
|
||
|
|
await prisma.$disconnect()
|
||
|
|
})
|