import { Application } from '@feathersjs/express'; import { HookContext } from '@feathersjs/feathers'; import blobService from 'feathers-blob'; import fs from 'fs-blob-store'; import moment from 'moment'; import Bluebird from 'bluebird'; import { PdfReader } from 'pdfreader' import _ from 'lodash'; const dir = './documents'; const blobStorage = fs(dir); const uploads = blobService({ Model: blobStorage }); // Async wrapper for pdfreader const parsePdfItems = async (fileName: string): Promise => { return new Promise((resolve, reject) => { const reader = new PdfReader(); const items: any[] = []; reader.parseFileItems(fileName, (err: Error, item: any) => { if (err) reject(err); else if (item) items.push(item); else resolve(items); }); }); }; const parseAccountId = (rows: any[], context: HookContext) => { const name = rows.find(row => row[0]?.startsWith('Наименование'))?.[0].slice(13); const row = rows.find(row => row[0]?.startsWith('Счет клиента')); const match = new RegExp(/Счет клиента (\w+) (\w+)/g).exec(row?.[0]); const code = match?.[1]; const currency = match?.[2]; return context.app .service('accounts') .find({ query: { code } }) .then((results: any[]) => { if (results.length) return results[0]; return context.app .service('accounts') .create({ code, name, currency }); }) .then((account: any) => account._id); }; const parseTransfersBill = async (context: HookContext): Promise => { const { id } = context.result; const fileName = `${dir}/${id}`; const items = await parsePdfItems(fileName); const hash = _.groupBy(items, 'y') const rows = _.map(hash, (elements: any[]) => _.map(elements, 'text')); // At this point we can remove the file context.service.remove(id); const accountId = await parseAccountId(rows, context); const transfers = rows.reduce((acc: any, cols: string[]) => { if (cols[0]?.startsWith('УНП')) { acc[acc.length - 1].vatId = cols[2]; acc[acc.length - 1].name = cols[3]; } else if (cols.length === 7) { const [dateString, doc, op, code, account, debet, credit] = cols; try { const date = moment.utc(dateString, 'DD.MM.YYYY').toISOString(); acc.push({ date, doc, op, code, account, debet, credit }); } catch (e) { console.log(`Skipping row because not a date: ${dateString}`); } } return acc; }, []); context.result = await Bluebird.mapSeries(transfers, async (transfer: any) => { const { date, vatId, name } = transfer; const debet = parseFloat(transfer.debet.replace(/ /g, '')); const credit = parseFloat(transfer.credit.replace(/ /g, '')); const operation = debet ? 'out' : 'in'; const amount = debet || credit; const contractorId = await context.app .service('contractors') .find({ query: { vatId } }) .then((results: any[]) => { if (results.length) return results[0]; return context.app .service('contractors') .create({ vatId, name }); }) .then((contractor: any) => contractor._id); return context.app.service('transfers').create({ date, operation, amount, contractorId, accountId, }); }); return context; }; export default (app: Application): void => { app.use('/uploads', uploads); app.service('uploads').hooks({ after: { create: parseTransfersBill, }, }); };