2
0
mirror of https://github.com/frappe/books.git synced 2025-02-08 23:18:31 +00:00

Merge pull request #622 from 18alantom/fixes

fix: dashboard scroll, print view sync, exchange rate bug
- also fixes #611 #606 #607 #572 #582
This commit is contained in:
Alan 2023-05-08 23:49:37 -07:00 committed by GitHub
commit 299cd83cc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 151 additions and 42 deletions

View File

@ -20,6 +20,7 @@ import { isPesa } from '../utils/index';
import { getDbSyncError } from './errorHelpers'; import { getDbSyncError } from './errorHelpers';
import { import {
areDocValuesEqual, areDocValuesEqual,
getFormulaSequence,
getMissingMandatoryMessage, getMissingMandatoryMessage,
getPreDefaultValues, getPreDefaultValues,
setChildDocIdx, setChildDocIdx,
@ -815,9 +816,9 @@ export class Doc extends Observable<DocValue | Doc[]> {
} }
async _applyFormulaForFields(doc: Doc, fieldname?: string) { async _applyFormulaForFields(doc: Doc, fieldname?: string) {
const formulaFields = this.schema.fields.filter( const formulaFields = getFormulaSequence(this.formulas)
({ fieldname }) => this.formulas?.[fieldname] .map((f) => this.fyo.getField(this.schemaName, f))
); .filter(Boolean);
let changed = false; let changed = false;
for (const field of formulaFields) { for (const field of formulaFields) {

View File

@ -1,11 +1,11 @@
import { Fyo } from 'fyo'; import { Fyo } from 'fyo';
import { DocValue } from 'fyo/core/types'; import { DocValue } from 'fyo/core/types';
import { isPesa } from 'fyo/utils'; import { isPesa } from 'fyo/utils';
import { isEqual } from 'lodash'; import { cloneDeep, isEqual } from 'lodash';
import { Money } from 'pesa';
import { Field, FieldType, FieldTypeEnum } from 'schemas/types'; import { Field, FieldType, FieldTypeEnum } from 'schemas/types';
import { getIsNullOrUndef } from 'utils'; import { getIsNullOrUndef } from 'utils';
import { Doc } from './doc'; import { Doc } from './doc';
import { FormulaMap } from './types';
export function areDocValuesEqual( export function areDocValuesEqual(
dvOne: DocValue | Doc[], dvOne: DocValue | Doc[],
@ -149,3 +149,42 @@ export function setChildDocIdx(childDocs: Doc[]) {
childDocs[idx].idx = +idx; 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<string, string[] | undefined>);
return sequenceDependencies(cloneDeep(depMap));
}
function sequenceDependencies(
depMap: Record<string, string[] | undefined>
): 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);
}

View File

@ -1,11 +1,29 @@
import { Doc } from 'fyo/model/doc'; 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 { function getPaddedName(prefix: string, next: number, padZeros: number): string {
return prefix + next.toString().padStart(padZeros ?? 4, '0'); return prefix + next.toString().padStart(padZeros ?? 4, '0');
} }
export default class NumberSeries extends Doc { 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() { setCurrent() {
let current = this.get('current') as number | null; let current = this.get('current') as number | null;

View File

@ -384,6 +384,7 @@ export abstract class Invoice extends Transactional {
return await this.getExchangeRate(); return await this.getExchangeRate();
}, },
dependsOn: ['party', 'currency'],
}, },
netTotal: { formula: async () => this.getSum('items', 'amount', false) }, netTotal: { formula: async () => this.getSum('items', 'amount', false) },
taxes: { formula: async () => await this.getTaxSummary() }, taxes: { formula: async () => await this.getTaxSummary() },
@ -391,6 +392,7 @@ export abstract class Invoice extends Transactional {
baseGrandTotal: { baseGrandTotal: {
formula: async () => formula: async () =>
(this.grandTotal as Money).mul(this.exchangeRate! ?? 1), (this.grandTotal as Money).mul(this.exchangeRate! ?? 1),
dependsOn: ['grandTotal', 'exchangeRate'],
}, },
outstandingAmount: { outstandingAmount: {
formula: async () => { formula: async () => {
@ -471,9 +473,6 @@ export abstract class Invoice extends Transactional {
baseGrandTotal: () => baseGrandTotal: () =>
this.exchangeRate === 1 || this.baseGrandTotal!.isZero(), this.exchangeRate === 1 || this.baseGrandTotal!.isZero(),
grandTotal: () => !this.taxes?.length, grandTotal: () => !this.taxes?.length,
entryCurrency: () => !this.isMultiCurrency,
currency: () => !this.isMultiCurrency,
exchangeRate: () => !this.isMultiCurrency,
stockNotTransferred: () => !this.stockNotTransferred, stockNotTransferred: () => !this.stockNotTransferred,
outstandingAmount: () => outstandingAmount: () =>
!!this.outstandingAmount?.isZero() || !this.isSubmitted, !!this.outstandingAmount?.isZero() || !this.isSubmitted,

View File

@ -286,7 +286,7 @@ export abstract class AccountReport extends LedgerReport {
this.fromYear!, this.fromYear!,
this.fyo this.fyo
); );
toDate = fy.toDate; toDate = DateTime.fromISO(fy.toDate).plus({ days: 1 }).toISODate();
fromDate = fy.fromDate; fromDate = fy.fromDate;
} }

View File

@ -121,7 +121,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"target": "Currency", "target": "Currency",
"readOnly": true, "readOnly": true,
"tab": "Settings" "hidden": true
}, },
{ {
"fieldname": "exchangeRate", "fieldname": "exchangeRate",
@ -129,7 +129,7 @@
"fieldtype": "Float", "fieldtype": "Float",
"default": 1, "default": 1,
"readOnly": true, "readOnly": true,
"tab": "Settings" "hidden": true
}, },
{ {
"fieldname": "discountAfterTax", "fieldname": "discountAfterTax",

View File

@ -140,7 +140,7 @@
}, },
{ {
"fieldname": "hasSerialNumber", "fieldname": "hasSerialNumber",
"label": "Has Serial Number.", "label": "Has Serial Number",
"fieldtype": "Check", "fieldtype": "Check",
"default": false, "default": false,
"section": "Inventory" "section": "Inventory"

View File

@ -65,7 +65,7 @@
}, },
{ {
"fieldname": "enableSerialNumber", "fieldname": "enableSerialNumber",
"label": "Enable Serial Number.", "label": "Enable Serial Number",
"fieldtype": "Check", "fieldtype": "Check",
"section": "Features" "section": "Features"
}, },

View File

@ -35,9 +35,11 @@ export default defineComponent({
return { return {
timerId: null, timerId: null,
barcode: '', barcode: '',
cooldown: '',
} as { } as {
timerId: null | ReturnType<typeof setInterval>; timerId: null | ReturnType<typeof setTimeout>;
barcode: string; barcode: string;
cooldown: string;
}; };
}, },
mounted() { mounted() {
@ -64,6 +66,17 @@ export default defineComponent({
return this.error(this.t`Invalid barcode value ${barcode}.`); 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', { const items = (await this.fyo.db.getAll('Item', {
filters: { barcode }, filters: { barcode },
fields: ['name'], fields: ['name'],
@ -97,12 +110,10 @@ export default defineComponent({
return await this.setItemFromBarcode(); return await this.setItemFromBarcode();
} }
if (this.timerId !== null) { this.clearInterval();
clearInterval(this.timerId);
}
this.barcode += key; this.barcode += key;
this.timerId = setInterval(async () => { this.timerId = setTimeout(async () => {
await this.setItemFromBarcode(); await this.setItemFromBarcode();
this.barcode = ''; this.barcode = '';
}, 20); }, 20);
@ -115,9 +126,15 @@ export default defineComponent({
await this.selectItem(this.barcode); await this.selectItem(this.barcode);
this.barcode = ''; this.barcode = '';
if (this.timerId !== null) { this.clearInterval();
clearInterval(this.timerId); },
clearInterval() {
if (this.timerId === null) {
return;
} }
clearInterval(this.timerId);
this.timerId = null;
}, },
error(message: string) { error(message: string) {
showToast({ type: 'error', message }); showToast({ type: 'error', message });

View File

@ -2,7 +2,13 @@
<div> <div>
<!-- Datetime header --> <!-- Datetime header -->
<div class="flex justify-between items-center text-sm px-4 pt-4"> <div class="flex justify-between items-center text-sm px-4 pt-4">
<div class="text-blue-500"> <div
v-if="viewMonth !== month || viewYear !== year"
class="text-gray-900"
>
{{ `${months[viewMonth]}, ${viewYear}` }}
</div>
<div v-else class="text-blue-500">
{{ datetimeString }} {{ datetimeString }}
</div> </div>

View File

@ -131,7 +131,8 @@ export default {
}, },
async openNewDoc() { async openNewDoc() {
const schemaName = this.df.target; 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 filters = await this.getCreateFilters();
const { openQuickEdit } = await import('src/utils/ui'); const { openQuickEdit } = await import('src/utils/ui');

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="overflow-hidden h-screen" style="width: var(--w-desk)"> <div class="h-screen" style="width: var(--w-desk)">
<PageHeader :title="t`Dashboard`"> <PageHeader :title="t`Dashboard`">
<div <div
class=" class="
@ -20,11 +20,11 @@
</div> </div>
</PageHeader> </PageHeader>
<div class="no-scrollbar overflow-auto h-full"> <div
<div class="no-scrollbar overflow-auto"
style="min-width: var(--w-desk-fixed); min-height: var(--h-app)" style="height: calc(100vh - var(--h-row-largest) - 1px)"
class="overflow-auto" >
> <div style="min-width: var(--w-desk-fixed)" class="overflow-auto">
<Cashflow <Cashflow
class="p-4" class="p-4"
:common-period="period" :common-period="period"

View File

@ -91,21 +91,20 @@ export default defineComponent({
}; };
}, },
async mounted() { async mounted() {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name); await this.initialize();
await this.setTemplateList();
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
// @ts-ignore // @ts-ignore
window.pv = this; window.pv = this;
} }
},
await this.setTemplateFromDefault(); async activated() {
if (!this.templateDoc && this.templateList.length) { await this.initialize();
await this.onTemplateNameChange(this.templateList[0]); },
} unmounted() {
this.reset();
if (this.doc) { },
this.values = await getPrintTemplatePropValues(this.doc as Doc); deactivated() {
} this.reset();
}, },
computed: { computed: {
helperMessage() { helperMessage() {
@ -191,6 +190,24 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
async initialize() {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
await this.setTemplateList();
await this.setTemplateFromDefault();
if (!this.templateDoc && this.templateList.length) {
await this.onTemplateNameChange(this.templateList[0]);
}
if (this.doc) {
this.values = await getPrintTemplatePropValues(this.doc as Doc);
}
},
reset() {
this.doc = null;
this.values = null;
this.templateList = [];
this.templateDoc = null;
},
async onTemplateNameChange(value: string | null): Promise<void> { async onTemplateNameChange(value: string | null): Promise<void> {
if (!value) { if (!value) {
this.templateDoc = null; this.templateDoc = null;

View File

@ -18,7 +18,7 @@
{{ t`Save as PDF` }} {{ t`Save as PDF` }}
</Button> </Button>
<Button <Button
v-if="doc && displayDoc" v-if="doc && doc.isCustom && displayDoc"
:title="t`Toggle Edit Mode`" :title="t`Toggle Edit Mode`"
:icon="true" :icon="true"
@click="toggleEditMode" @click="toggleEditMode"
@ -315,6 +315,7 @@ export default defineComponent({
} }
}, },
async activated(): Promise<void> { async activated(): Promise<void> {
await this.initialize();
docsPathRef.value = docsPathMap.PrintTemplate ?? ''; docsPathRef.value = docsPathMap.PrintTemplate ?? '';
this.setShortcuts(); this.setShortcuts();
}, },
@ -323,6 +324,11 @@ export default defineComponent({
if (this.editMode) { if (this.editMode) {
this.disableEditMode(); this.disableEditMode();
} }
if (this.doc?.dirty) {
return;
}
this.reset();
}, },
methods: { methods: {
setShortcuts() { setShortcuts() {
@ -357,6 +363,10 @@ export default defineComponent({
await this.setDisplayInitialDoc(); await this.setDisplayInitialDoc();
}, },
reset() {
this.doc = null;
this.displayDoc = null;
},
getTemplateEditorState() { getTemplateEditorState() {
const fallback = this.doc?.template ?? ''; const fallback = this.doc?.template ?? '';

View File

@ -191,6 +191,7 @@ function getListViewList(fyo: Fyo): SearchItem[] {
ModelNameEnum.AccountingLedgerEntry, ModelNameEnum.AccountingLedgerEntry,
ModelNameEnum.Currency, ModelNameEnum.Currency,
ModelNameEnum.NumberSeries, ModelNameEnum.NumberSeries,
ModelNameEnum.PrintTemplate,
]; ];
const hasInventory = fyo.doc.singles.AccountingSettings?.enableInventory; const hasInventory = fyo.doc.singles.AccountingSettings?.enableInventory;

View File

@ -160,7 +160,7 @@ Dashboard,Dashboard,
Date,Datum, Date,Datum,
"Date Format","Datum Format", "Date Format","Datum Format",
Day,, Day,,
Debit,Lastschrift, Debit,Soll,
"Debit Note",, "Debit Note",,
Debtors,Debitoren, Debtors,Debitoren,
"Default Account",Standard-Konto, "Default Account",Standard-Konto,

1 ${0} ${1} already exists. ${0} ${1} existiert bereits.
160 Date Datum
161 Date Format Datum Format
162 Day
163 Debit Lastschrift Soll
164 Debit Note
165 Debtors Debitoren
166 Default Account Standard-Konto