2
0
mirror of https://github.com/frappe/books.git synced 2024-11-15 01:44:04 +00:00
books/src/dataImport.ts

253 lines
5.5 KiB
TypeScript
Raw Normal View History

2022-02-21 12:27:01 +00:00
import { Field, FieldType } from '@/types/model';
import frappe from 'frappe';
2022-02-22 08:43:56 +00:00
import { parseCSV } from './csvParser';
2022-02-21 12:27:01 +00:00
2022-02-21 10:56:57 +00:00
export const importable = [
'SalesInvoice',
'PurchaseInvoice',
'Payment',
'JournalEntry',
'Customer',
'Supplier',
'Item',
];
2022-02-21 12:27:01 +00:00
2022-02-22 08:43:56 +00:00
type Exclusion = {
[key: string]: string[];
};
type Map = {
2022-02-23 07:22:14 +00:00
[key: string]: string | boolean | object;
2022-02-22 08:43:56 +00:00
};
2022-02-21 12:27:01 +00:00
interface TemplateField {
label: string;
fieldname: string;
required: boolean;
2022-02-23 07:22:14 +00:00
doctype: string;
options?: string[];
fieldtype: FieldType;
2022-02-21 12:27:01 +00:00
}
2022-02-22 08:43:56 +00:00
const exclusion: Exclusion = {
Item: ['image'],
2022-02-23 07:22:14 +00:00
Supplier: ['address', 'outstandingAmount', 'supplier', 'image', 'customer'],
Customer: ['address', 'outstandingAmount', 'supplier', 'image', 'customer'],
2022-02-21 12:27:01 +00:00
};
2022-02-23 07:22:14 +00:00
function getFilteredDocFields(doctype: string): [TemplateField[], string[]] {
2022-02-21 12:27:01 +00:00
const fields: TemplateField[] = [];
// @ts-ignore
const primaryFields: Field[] = frappe.models[doctype].fields;
const tableTypes: string[] = [];
2022-02-23 07:22:14 +00:00
const exclusionFields: string[] = exclusion[doctype] ?? [];
2022-02-21 12:27:01 +00:00
primaryFields.forEach(
2022-02-23 07:22:14 +00:00
({
label,
fieldtype,
childtype,
fieldname,
readOnly,
required,
hidden,
options,
}) => {
if (
readOnly ||
(hidden && typeof hidden === 'number') ||
exclusionFields.includes(fieldname)
) {
2022-02-22 08:43:56 +00:00
return;
}
2022-02-21 12:27:01 +00:00
if (fieldtype === FieldType.Table && childtype) {
tableTypes.push(childtype);
2022-02-23 07:22:14 +00:00
return;
2022-02-21 12:27:01 +00:00
}
fields.push({
label,
fieldname,
2022-02-23 07:22:14 +00:00
doctype,
options,
fieldtype,
2022-02-21 12:27:01 +00:00
required: Boolean(required ?? false),
});
}
);
2022-02-23 07:22:14 +00:00
return [fields, tableTypes];
}
2022-02-21 12:27:01 +00:00
2022-02-23 07:22:14 +00:00
function getTemplateFields(doctype: string): TemplateField[] {
const fields: TemplateField[] = [];
if (!doctype) {
return [];
}
const doctypes: string[] = [doctype];
while (doctypes.length > 0) {
const dt = doctypes.pop();
if (!dt) {
break;
}
2022-02-21 12:27:01 +00:00
2022-02-23 07:22:14 +00:00
const [templateFields, tableTypes] = getFilteredDocFields(dt);
fields.push(...templateFields);
doctypes.push(...tableTypes);
}
2022-02-21 12:27:01 +00:00
return fields;
}
2022-02-22 08:43:56 +00:00
function getLabelFieldMap(templateFields: TemplateField[]): Map {
const map: Map = {};
2022-02-21 12:27:01 +00:00
templateFields.reduce((acc, tf) => {
const key = tf.label as string;
acc[key] = tf.fieldname;
return acc;
}, map);
return map;
}
function getTemplate(templateFields: TemplateField[]): string {
const labels = templateFields.map(({ label }) => `"${label}"`).join(',');
return [labels, ''].join('\n');
}
export class Importer {
doctype: string;
templateFields: TemplateField[];
2022-02-22 08:43:56 +00:00
map: Map;
template: string;
2022-02-22 13:03:21 +00:00
indices: number[] = [];
2022-02-22 08:43:56 +00:00
parsedLabels: string[] = [];
2022-02-22 13:03:21 +00:00
parsedValues: string[][] = [];
2022-02-22 08:43:56 +00:00
assignedMap: Map = {}; // target: import
2022-02-23 06:03:15 +00:00
requiredMap: Map = {};
2022-02-23 07:22:14 +00:00
labelFieldMap: Map = {};
2022-02-21 12:27:01 +00:00
constructor(doctype: string) {
this.doctype = doctype;
this.templateFields = getTemplateFields(doctype);
2022-02-22 08:43:56 +00:00
this.map = getLabelFieldMap(this.templateFields);
this.template = getTemplate(this.templateFields);
this.assignedMap = this.assignableLabels.reduce((acc: Map, k) => {
acc[k] = '';
return acc;
}, {});
2022-02-23 06:03:15 +00:00
this.requiredMap = this.templateFields.reduce((acc: Map, k) => {
acc[k.label] = k.required;
return acc;
}, {});
2022-02-23 07:22:14 +00:00
this.labelFieldMap = this.templateFields.reduce((acc: Map, k) => {
acc[k.label] = k;
return acc;
}, {});
2022-02-22 08:43:56 +00:00
}
get assignableLabels() {
2022-02-23 06:03:15 +00:00
const req: string[] = [];
const nreq: string[] = [];
Object.keys(this.map).forEach((k) => {
if (this.requiredMap[k]) {
req.push(k);
return;
}
nreq.push(k);
});
return [...req, ...nreq];
2022-02-22 08:43:56 +00:00
}
get unassignedLabels() {
const assigned = Object.keys(this.assignedMap).map(
(k) => this.assignedMap[k]
);
return this.parsedLabels.filter((l) => !assigned.includes(l));
2022-02-21 12:27:01 +00:00
}
2022-02-22 13:03:21 +00:00
get columnLabels() {
2022-02-23 06:03:15 +00:00
const req: string[] = [];
const nreq: string[] = [];
2022-02-22 08:43:56 +00:00
2022-02-22 13:03:21 +00:00
this.assignableLabels.forEach((k) => {
2022-02-23 06:03:15 +00:00
if (!this.assignedMap[k]) {
2022-02-22 08:43:56 +00:00
return;
}
2022-02-23 06:03:15 +00:00
if (this.requiredMap[k]) {
req.push(k);
return;
}
nreq.push(k);
2022-02-22 08:43:56 +00:00
});
2022-02-23 06:03:15 +00:00
return [...req, ...nreq];
2022-02-22 13:03:21 +00:00
}
get assignedMatrix() {
this.indices = this.columnLabels
.map((k) => this.assignedMap[k])
.filter(Boolean)
2022-02-23 06:03:15 +00:00
.map((k) => this.parsedLabels.indexOf(k as string));
2022-02-22 13:03:21 +00:00
const rows = this.parsedValues.length;
const cols = this.columnLabels.length;
const matrix = [];
for (let i = 0; i < rows; i++) {
const row = [];
for (let j = 0; j < cols; j++) {
const ix = this.indices[j];
const value = this.parsedValues[i][ix] ?? '';
row.push(value);
}
matrix.push(row);
}
return matrix;
}
dropRow(i: number) {
this.parsedValues = this.parsedValues.filter((_, ix) => i !== ix);
}
updateValue(value: string, i: number, j: number) {
this.parsedValues[i][this.indices[j]] = value ?? '';
2022-02-21 12:27:01 +00:00
}
2022-02-22 08:43:56 +00:00
selectFile(text: string): boolean {
const csv = parseCSV(text);
this.parsedLabels = csv[0];
const values = csv.slice(1);
if (values.some((v) => v.length !== this.parsedLabels.length)) {
return false;
}
2022-02-22 13:03:21 +00:00
this.parsedValues = values;
2022-02-22 08:43:56 +00:00
this._setAssigned();
return true;
}
_setAssigned() {
const labels = [...this.parsedLabels];
labels.forEach((l) => {
if (this.assignedMap[l] !== '') {
return;
}
this.assignedMap[l] = l;
});
2022-02-21 12:27:01 +00:00
}
}
2022-02-22 08:43:56 +00:00
// @ts-ignore
2022-02-23 07:22:14 +00:00
window.im = importable;
// @ts-ignore
window.gtf = getTemplateFields;