diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index e52b81b6..c3abdc1c 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -20,6 +20,7 @@ import { isPesa } from '../utils/index'; import { getDbSyncError } from './errorHelpers'; import { areDocValuesEqual, + getFormulaSequence, getMissingMandatoryMessage, getPreDefaultValues, setChildDocIdx, @@ -815,9 +816,9 @@ export class Doc extends Observable { } async _applyFormulaForFields(doc: Doc, fieldname?: string) { - const formulaFields = this.schema.fields.filter( - ({ fieldname }) => this.formulas?.[fieldname] - ); + const formulaFields = getFormulaSequence(this.formulas) + .map((f) => this.fyo.getField(this.schemaName, f)) + .filter(Boolean); let changed = false; for (const field of formulaFields) { diff --git a/fyo/model/helpers.ts b/fyo/model/helpers.ts index 006515ef..711c9fcb 100644 --- a/fyo/model/helpers.ts +++ b/fyo/model/helpers.ts @@ -1,11 +1,11 @@ import { Fyo } from 'fyo'; import { DocValue } from 'fyo/core/types'; import { isPesa } from 'fyo/utils'; -import { isEqual } from 'lodash'; -import { Money } from 'pesa'; +import { cloneDeep, isEqual } from 'lodash'; import { Field, FieldType, FieldTypeEnum } from 'schemas/types'; import { getIsNullOrUndef } from 'utils'; import { Doc } from './doc'; +import { FormulaMap } from './types'; export function areDocValuesEqual( dvOne: DocValue | Doc[], @@ -149,3 +149,42 @@ export function setChildDocIdx(childDocs: Doc[]) { childDocs[idx].idx = +idx; } } + +export function getFormulaSequence(formulas: FormulaMap) { + const depMap = Object.keys(formulas).reduce((acc, k) => { + acc[k] = formulas[k]?.dependsOn; + return acc; + }, {} as Record); + return sequenceDependencies(cloneDeep(depMap)); +} + +function sequenceDependencies( + depMap: Record +): string[] { + /** + * Sufficiently okay algo to sequence dependents after + * their dependencies + */ + const keys = Object.keys(depMap); + + const independent = keys.filter((k) => !depMap[k]?.length); + const dependent = keys.filter((k) => depMap[k]?.length); + + const keyset = new Set(independent); + + for (const k of dependent) { + const deps = depMap[k] ?? []; + deps.push(k); + + while (deps.length) { + const d = deps.shift()!; + if (keyset.has(d)) { + continue; + } + + keyset.add(d); + } + } + + return Array.from(keyset).filter((k) => k in depMap); +} diff --git a/fyo/models/NumberSeries.ts b/fyo/models/NumberSeries.ts index 661b516b..3b9f485f 100644 --- a/fyo/models/NumberSeries.ts +++ b/fyo/models/NumberSeries.ts @@ -1,11 +1,29 @@ import { Doc } from 'fyo/model/doc'; -import { ReadOnlyMap } from 'fyo/model/types'; +import { ReadOnlyMap, ValidationMap } from 'fyo/model/types'; +import { ValidationError } from 'fyo/utils/errors'; + +const invalidNumberSeries = /[/\=\?\&\%]/; function getPaddedName(prefix: string, next: number, padZeros: number): string { return prefix + next.toString().padStart(padZeros ?? 4, '0'); } export default class NumberSeries extends Doc { + validations: ValidationMap = { + name: (value) => { + if (typeof value !== 'string') { + return; + } + + if (invalidNumberSeries.test(value)) { + throw new ValidationError( + this.fyo + .t`The following characters cannot be used ${'/, ?, &, =, %'} in a Number Series name.` + ); + } + }, + }; + setCurrent() { let current = this.get('current') as number | null; diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index fe4dcd25..53a95486 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -384,6 +384,7 @@ export abstract class Invoice extends Transactional { return await this.getExchangeRate(); }, + dependsOn: ['party', 'currency'], }, netTotal: { formula: async () => this.getSum('items', 'amount', false) }, taxes: { formula: async () => await this.getTaxSummary() }, @@ -391,6 +392,7 @@ export abstract class Invoice extends Transactional { baseGrandTotal: { formula: async () => (this.grandTotal as Money).mul(this.exchangeRate! ?? 1), + dependsOn: ['grandTotal', 'exchangeRate'], }, outstandingAmount: { formula: async () => { @@ -471,9 +473,6 @@ export abstract class Invoice extends Transactional { baseGrandTotal: () => this.exchangeRate === 1 || this.baseGrandTotal!.isZero(), grandTotal: () => !this.taxes?.length, - entryCurrency: () => !this.isMultiCurrency, - currency: () => !this.isMultiCurrency, - exchangeRate: () => !this.isMultiCurrency, stockNotTransferred: () => !this.stockNotTransferred, outstandingAmount: () => !!this.outstandingAmount?.isZero() || !this.isSubmitted, diff --git a/reports/AccountReport.ts b/reports/AccountReport.ts index 530a750f..0ead699d 100644 --- a/reports/AccountReport.ts +++ b/reports/AccountReport.ts @@ -286,7 +286,7 @@ export abstract class AccountReport extends LedgerReport { this.fromYear!, this.fyo ); - toDate = fy.toDate; + toDate = DateTime.fromISO(fy.toDate).plus({ days: 1 }).toISODate(); fromDate = fy.fromDate; } diff --git a/schemas/app/Invoice.json b/schemas/app/Invoice.json index 37fcee58..9295cd4b 100644 --- a/schemas/app/Invoice.json +++ b/schemas/app/Invoice.json @@ -121,7 +121,7 @@ "fieldtype": "Link", "target": "Currency", "readOnly": true, - "tab": "Settings" + "hidden": true }, { "fieldname": "exchangeRate", @@ -129,7 +129,7 @@ "fieldtype": "Float", "default": 1, "readOnly": true, - "tab": "Settings" + "hidden": true }, { "fieldname": "discountAfterTax", diff --git a/schemas/app/Item.json b/schemas/app/Item.json index 4d5ec0b2..3adaed2c 100644 --- a/schemas/app/Item.json +++ b/schemas/app/Item.json @@ -140,7 +140,7 @@ }, { "fieldname": "hasSerialNumber", - "label": "Has Serial Number.", + "label": "Has Serial Number", "fieldtype": "Check", "default": false, "section": "Inventory" diff --git a/schemas/app/inventory/InventorySettings.json b/schemas/app/inventory/InventorySettings.json index 47ffe024..f403a7ca 100644 --- a/schemas/app/inventory/InventorySettings.json +++ b/schemas/app/inventory/InventorySettings.json @@ -65,7 +65,7 @@ }, { "fieldname": "enableSerialNumber", - "label": "Enable Serial Number.", + "label": "Enable Serial Number", "fieldtype": "Check", "section": "Features" }, diff --git a/src/components/Controls/Barcode.vue b/src/components/Controls/Barcode.vue index 219f0141..3bb5af64 100644 --- a/src/components/Controls/Barcode.vue +++ b/src/components/Controls/Barcode.vue @@ -35,9 +35,11 @@ export default defineComponent({ return { timerId: null, barcode: '', + cooldown: '', } as { - timerId: null | ReturnType; + timerId: null | ReturnType; barcode: string; + cooldown: string; }; }, mounted() { @@ -64,6 +66,17 @@ export default defineComponent({ return this.error(this.t`Invalid barcode value ${barcode}.`); } + /** + * Between two entries of the same item, this adds + * a cooldown period of 100ms. This is to prevent + * double entry. + */ + if (this.cooldown === barcode) { + return; + } + this.cooldown = barcode; + setTimeout(() => (this.cooldown = ''), 100); + const items = (await this.fyo.db.getAll('Item', { filters: { barcode }, fields: ['name'], @@ -97,12 +110,10 @@ export default defineComponent({ return await this.setItemFromBarcode(); } - if (this.timerId !== null) { - clearInterval(this.timerId); - } + this.clearInterval(); this.barcode += key; - this.timerId = setInterval(async () => { + this.timerId = setTimeout(async () => { await this.setItemFromBarcode(); this.barcode = ''; }, 20); @@ -115,9 +126,15 @@ export default defineComponent({ await this.selectItem(this.barcode); this.barcode = ''; - if (this.timerId !== null) { - clearInterval(this.timerId); + this.clearInterval(); + }, + clearInterval() { + if (this.timerId === null) { + return; } + + clearInterval(this.timerId); + this.timerId = null; }, error(message: string) { showToast({ type: 'error', message }); diff --git a/src/components/Controls/DatetimePicker.vue b/src/components/Controls/DatetimePicker.vue index ddf44caa..636e07f2 100644 --- a/src/components/Controls/DatetimePicker.vue +++ b/src/components/Controls/DatetimePicker.vue @@ -2,7 +2,13 @@
-
+
+ {{ `${months[viewMonth]}, ${viewYear}` }} +
+
{{ datetimeString }}
diff --git a/src/components/Controls/Link.vue b/src/components/Controls/Link.vue index 03a0683f..f8e40c12 100644 --- a/src/components/Controls/Link.vue +++ b/src/components/Controls/Link.vue @@ -131,7 +131,8 @@ export default { }, async openNewDoc() { const schemaName = this.df.target; - const name = this.linkValue; + const name = + this.linkValue || fyo.doc.getTemporaryName(fyo.schemaMap[schemaName]); const filters = await this.getCreateFilters(); const { openQuickEdit } = await import('src/utils/ui'); diff --git a/src/pages/Dashboard/Dashboard.vue b/src/pages/Dashboard/Dashboard.vue index fd084cc5..74081da7 100644 --- a/src/pages/Dashboard/Dashboard.vue +++ b/src/pages/Dashboard/Dashboard.vue @@ -1,5 +1,5 @@