diff --git a/fyo/core/converter.ts b/fyo/core/converter.ts index 1c402d51..a9f37bd7 100644 --- a/fyo/core/converter.ts +++ b/fyo/core/converter.ts @@ -164,7 +164,7 @@ function toDocString(value: RawValue, field: Field) { } function toDocDate(value: RawValue, field: Field) { - if (value === null) { + if (value === null || value === '') { return null; } @@ -181,6 +181,10 @@ function toDocDate(value: RawValue, field: Field) { } function toDocCurrency(value: RawValue, field: Field, fyo: Fyo) { + if (value === '') { + return fyo.pesa(0); + } + if (typeof value === 'string') { return fyo.pesa(value); } @@ -201,6 +205,10 @@ function toDocCurrency(value: RawValue, field: Field, fyo: Fyo) { } function toDocInt(value: RawValue, field: Field): number { + if (value === '') { + return 0; + } + if (typeof value === 'string') { value = parseInt(value); } @@ -209,6 +217,10 @@ function toDocInt(value: RawValue, field: Field): number { } function toDocFloat(value: RawValue, field: Field): number { + if (value === '') { + return 0; + } + if (typeof value === 'boolean') { return Number(value); } diff --git a/fyo/model/naming.ts b/fyo/model/naming.ts index f3fb62a6..6eba9b07 100644 --- a/fyo/model/naming.ts +++ b/fyo/model/naming.ts @@ -2,24 +2,24 @@ import { Fyo } from 'fyo'; import NumberSeries from 'fyo/models/NumberSeries'; import { DEFAULT_SERIES_START } from 'fyo/utils/consts'; import { BaseError } from 'fyo/utils/errors'; -import { Field, Schema } from 'schemas/types'; import { getRandomString } from 'utils'; import { Doc } from './doc'; -export function getNumberSeries(schema: Schema): Field | undefined { - const numberSeries = schema.fields.find( - (f) => f.fieldname === 'numberSeries' - ); - return numberSeries; -} - export function isNameAutoSet(schemaName: string, fyo: Fyo): boolean { const schema = fyo.schemaMap[schemaName]!; + if (schema.naming === 'manual') { + return false; + } + if (schema.naming === 'autoincrement') { return true; } - const numberSeries = getNumberSeries(schema); + if (schema.naming === 'random') { + return true; + } + + const numberSeries = fyo.getField(schema.name, 'numberSeries'); if (numberSeries) { return true; } diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index 1177af97..d1675083 100644 --- a/models/baseModels/InvoiceItem/InvoiceItem.ts +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -98,9 +98,9 @@ export abstract class InvoiceItem extends Doc { const itemList = doc.parentdoc!.items as Doc[]; const items = itemList.map((d) => d.item as string).filter(Boolean); - let itemNotFor = 'sales'; + let itemNotFor = 'Sales'; if (doc.isSales) { - itemNotFor = 'purchases'; + itemNotFor = 'Purchases'; } const baseFilter = { for: ['not in', [itemNotFor]] }; diff --git a/models/baseModels/Item/Item.ts b/models/baseModels/Item/Item.ts index 121db713..a7f07549 100644 --- a/models/baseModels/Item/Item.ts +++ b/models/baseModels/Item/Item.ts @@ -67,7 +67,7 @@ export class Item extends Doc { return [ { label: fyo.t`New Sale`, - condition: (doc) => !doc.notInserted && doc.for !== 'purchases', + condition: (doc) => !doc.notInserted && doc.for !== 'Purchases', action: async (doc, router) => { const invoice = await fyo.doc.getNewDoc('SalesInvoice'); await invoice.append('items', { @@ -80,7 +80,7 @@ export class Item extends Doc { }, { label: fyo.t`New Purchase`, - condition: (doc) => !doc.notInserted && doc.for !== 'sales', + condition: (doc) => !doc.notInserted && doc.for !== 'Sales', action: async (doc, router) => { const invoice = await fyo.doc.getNewDoc('PurchaseInvoice'); await invoice.append('items', { diff --git a/schemas/app/Item.json b/schemas/app/Item.json index 10456a88..20dccaa8 100644 --- a/schemas/app/Item.json +++ b/schemas/app/Item.json @@ -74,20 +74,21 @@ "fieldtype": "Select", "options": [ { - "value": "purchases", + "value": "Purchases", "label": "Purchases" }, { - "value": "sales", + "value": "Sales", "label": "Sales" }, { - "value": "both", + "value": "Both", "label": "Both" } ], "readOnly": true, - "default": "both" + "required": true, + "default": "Both" }, { "fieldname": "incomeAccount", diff --git a/schemas/app/PurchaseInvoiceItem.json b/schemas/app/PurchaseInvoiceItem.json index 5bb48e2f..df6eadbc 100644 --- a/schemas/app/PurchaseInvoiceItem.json +++ b/schemas/app/PurchaseInvoiceItem.json @@ -65,7 +65,8 @@ "fieldname": "hsnCode", "label": "HSN/SAC", "fieldtype": "Int", - "placeholder": "HSN/SAC Code" + "placeholder": "HSN/SAC Code", + "hidden": true } ], "tableFields": ["item", "tax", "quantity", "rate", "amount"], diff --git a/schemas/app/SalesInvoiceItem.json b/schemas/app/SalesInvoiceItem.json index 7407f42a..81cec3c6 100644 --- a/schemas/app/SalesInvoiceItem.json +++ b/schemas/app/SalesInvoiceItem.json @@ -66,7 +66,8 @@ "fieldname": "hsnCode", "label": "HSN/SAC", "fieldtype": "Int", - "placeholder": "HSN/SAC Code" + "placeholder": "HSN/SAC Code", + "hidden": true } ], "tableFields": ["item", "tax", "quantity", "rate", "amount"], diff --git a/src/components/Dropdown.vue b/src/components/Dropdown.vue index 56d593de..4bdc1b2b 100644 --- a/src/components/Dropdown.vue +++ b/src/components/Dropdown.vue @@ -206,6 +206,8 @@ export default { // valid selection let item = this.items[this.highlightedIndex]; await this.selectItem(item); + } else if (this.items.length === 1) { + await this.selectItem(this.items[0]) } }, highlightItemUp() { diff --git a/src/components/DropdownWithActions.vue b/src/components/DropdownWithActions.vue index fb631268..613a7a0b 100644 --- a/src/components/DropdownWithActions.vue +++ b/src/components/DropdownWithActions.vue @@ -31,7 +31,9 @@ export default { actions: { default: [] }, type: { type: String, default: 'secondary' }, }, - inject: ['doc'], + inject: { + doc: { default: null }, + }, components: { Dropdown, Button, diff --git a/src/dataImport.ts b/src/dataImport.ts index 79eb46da..91e4bc7c 100644 --- a/src/dataImport.ts +++ b/src/dataImport.ts @@ -1,8 +1,10 @@ import { Fyo, t } from 'fyo'; +import { Converter } from 'fyo/core/converter'; import { DocValueMap } from 'fyo/core/types'; import { Doc } from 'fyo/model/doc'; import { isNameAutoSet } from 'fyo/model/naming'; import { Noun, Verb } from 'fyo/telemetry/types'; +import { ModelNameEnum } from 'models/types'; import { Field, FieldType, @@ -11,16 +13,20 @@ import { SelectOption, TargetField, } from 'schemas/types'; -import { parseCSV } from '../utils/csvParser'; +import { + getDefaultMapFromList, + getMapFromList, + getValueMapFromList, +} from 'utils'; +import { generateCSV, parseCSV } from '../utils/csvParser'; export const importable = [ - 'SalesInvoice', - 'PurchaseInvoice', - 'Payment', - 'JournalEntry', - 'Customer', - 'Supplier', - 'Item', + ModelNameEnum.SalesInvoice, + ModelNameEnum.PurchaseInvoice, + ModelNameEnum.Payment, + ModelNameEnum.Party, + ModelNameEnum.Item, + ModelNameEnum.JournalEntry, ]; type Status = { @@ -29,16 +35,7 @@ type Status = { names: string[]; }; -type Exclusion = { - [key: string]: string[]; -}; - -type Map = Record; -type ObjectMap = Record; - -type LabelTemplateFieldMap = { - [key: string]: TemplateField; -}; +type Exclusion = Record; type LoadingStatusCallback = ( isMakingEntries: boolean, @@ -56,33 +53,9 @@ interface TemplateField { parentField: string; } -function formatValue(value: string, fieldtype: FieldType): unknown { - switch (fieldtype) { - case FieldTypeEnum.Date: - if (value === '') { - return ''; - } - return new Date(value); - case FieldTypeEnum.Currency: - // @ts-ignore - return this.fyo.pesa(value || 0); - case FieldTypeEnum.Int: - case FieldTypeEnum.Float: { - const n = parseFloat(value); - if (!Number.isNaN(n)) { - return n; - } - return 0; - } - default: - return value; - } -} - const exclusion: Exclusion = { Item: ['image'], - Supplier: ['address', 'outstandingAmount', 'supplier', 'image', 'customer'], - Customer: ['address', 'outstandingAmount', 'supplier', 'image', 'customer'], + Party: ['address', 'outstandingAmount', 'image'], }; function getFilteredDocFields( @@ -97,48 +70,68 @@ function getFilteredDocFields( parentField = ''; } - // @ts-ignore - const primaryFields: Field[] = fyo.schemaMap[schemaName]?.fields ?? []; + const primaryFields: Field[] = fyo.schemaMap[schemaName]!.fields; const fields: TemplateField[] = []; const tableTypes: string[][] = []; const exclusionFields: string[] = exclusion[schemaName] ?? []; - primaryFields.forEach((field) => { - const { label, fieldtype, fieldname, readOnly, required, hidden } = field; + for (const field of primaryFields) { + const { label, fieldtype, fieldname, required } = field; - if ( - !(fieldname === 'name' && !parentField) && - (readOnly || - (hidden && typeof hidden === 'number') || - exclusionFields.includes(fieldname)) - ) { - return; + if (shouldSkip(field, exclusionFields, parentField)) { + continue; } - if (fieldtype === FieldTypeEnum.Table && (field as TargetField).target) { - tableTypes.push([(field as TargetField).target, fieldname]); - return; + if (fieldtype === FieldTypeEnum.Table) { + const { target } = field as TargetField; + tableTypes.push([target, fieldname]); + continue; } - let options: SelectOption[] = []; - if ((field as OptionField).options !== undefined) { - options = (field as OptionField).options; - } + const options: SelectOption[] = (field as OptionField).options ?? []; fields.push({ label, fieldname, - schemaName: schemaName, + schemaName, options, fieldtype, parentField, - required: Boolean(required ?? false), + required: required ?? false, }); - }); + } return [fields, tableTypes]; } +function shouldSkip( + field: Field, + exclusionFields: string[], + parentField: string +): boolean { + if (field.meta) { + return true; + } + + if (field.fieldname === 'name' && parentField) { + return true; + } + + if (field.required) { + return false; + } + + if (exclusionFields.includes(field.fieldname)) { + return true; + } + + if (field.hidden || field.readOnly) { + return true; + } + + return false; +} + function getTemplateFields(schemaName: string, fyo: Fyo): TemplateField[] { const fields: TemplateField[] = []; if (!schemaName) { @@ -147,46 +140,28 @@ function getTemplateFields(schemaName: string, fyo: Fyo): TemplateField[] { const schemaNames: string[][] = [[schemaName]]; while (schemaNames.length > 0) { - const dt = schemaNames.pop(); - if (!dt) { + const sn = schemaNames.pop(); + if (!sn) { break; } - const [templateFields, tableTypes] = getFilteredDocFields(dt, fyo); + const [templateFields, tableTypes] = getFilteredDocFields(sn, fyo); fields.push(...templateFields); schemaNames.push(...tableTypes); } return fields; } -function getLabelFieldMap(templateFields: TemplateField[]): Map { - const map: Map = {}; - - 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 { schemaName: string; templateFields: TemplateField[]; - map: Map; + labelTemplateFieldMap: Record = {}; template: string; indices: number[] = []; parsedLabels: string[] = []; parsedValues: string[][] = []; - assignedMap: Map = {}; // target: import - requiredMap: Map = {}; - labelTemplateFieldMap: LabelTemplateFieldMap = {}; + assignedMap: Record = {}; // target: import + requiredMap: Record = {}; shouldSubmit: boolean = false; labelIndex: number = -1; csv: string[][] = []; @@ -196,38 +171,30 @@ export class Importer { this.schemaName = schemaName; this.fyo = fyo; this.templateFields = getTemplateFields(schemaName, this.fyo); - this.map = getLabelFieldMap(this.templateFields); - this.template = getTemplate(this.templateFields); - this.assignedMap = this.assignableLabels.reduce((acc: Map, k) => { - acc[k] = ''; - return acc; - }, {}); - this.requiredMap = this.templateFields.reduce((acc: Map, k) => { - acc[k.label] = k.required; - return acc; - }, {}); - this.labelTemplateFieldMap = this.templateFields.reduce( - (acc: LabelTemplateFieldMap, k) => { - acc[k.label] = k; - return acc; - }, - {} - ); + this.template = generateCSV([this.templateFields.map((t) => t.label)]); + this.labelTemplateFieldMap = getMapFromList(this.templateFields, 'label'); + this.assignedMap = getDefaultMapFromList(this.templateFields, '', 'label'); + this.requiredMap = getValueMapFromList( + this.templateFields, + 'label', + 'required' + ) as Record; } get assignableLabels() { const req: string[] = []; const nreq: string[] = []; - Object.keys(this.map).forEach((k) => { - if (this.requiredMap[k]) { - req.push(k); - return; + + for (const label in this.labelTemplateFieldMap) { + if (this.requiredMap[label]) { + req.push(label); + continue; } - nreq.push(k); - }); + nreq.push(label); + } - return [...req, ...nreq]; + return [req, nreq].flat(); } get unassignedLabels() { @@ -332,23 +299,23 @@ export class Importer { }); } - getDocs(): Map[] { + getDocs(): DocValueMap[] { const fields = this.columnLabels.map((k) => this.labelTemplateFieldMap[k]); const nameIndex = fields.findIndex(({ fieldname }) => fieldname === 'name'); - const docMap: ObjectMap = {}; + const docMap: Record = {}; const assignedMatrix = this.assignedMatrix; for (let r = 0; r < assignedMatrix.length; r++) { const row = assignedMatrix[r]; - const cts: ObjectMap = {}; + const cts: Record = {}; const name = row[nameIndex]; docMap[name] ??= {}; for (let f = 0; f < fields.length; f++) { const field = fields[f]; - const value = formatValue(row[f], field.fieldtype); + const value = Converter.toDocValue(row[f], field, this.fyo); if (field.parentField) { cts[field.parentField] ??= {}; @@ -361,7 +328,7 @@ export class Importer { for (const k of Object.keys(cts)) { docMap[name][k] ??= []; - (docMap[name][k] as Map[]).push(cts[k]); + (docMap[name][k] as DocValueMap[]).push(cts[k]); } } @@ -423,9 +390,8 @@ export class Importer { this.parsedValues.push(emptyRow); } - async makeEntry(doc: Doc, docObj: Map) { - await doc.setMultiple(docObj as DocValueMap); - await doc.sync(); + async makeEntry(doc: Doc, docObj: DocValueMap) { + await doc.setAndSync(docObj); if (this.shouldSubmit) { await doc.submit(); } diff --git a/src/pages/DataImport.vue b/src/pages/DataImport.vue index f5a7e08d..986bfcbb 100644 --- a/src/pages/DataImport.vue +++ b/src/pages/DataImport.vue @@ -1,5 +1,6 @@