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:
commit
299cd83cc9
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "enableSerialNumber",
|
"fieldname": "enableSerialNumber",
|
||||||
"label": "Enable Serial Number.",
|
"label": "Enable Serial Number",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"section": "Features"
|
"section": "Features"
|
||||||
},
|
},
|
||||||
|
@ -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 });
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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');
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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;
|
||||||
|
@ -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 ?? '';
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user