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

incr: fix a bunch of vue files

- no modes in the first pane
This commit is contained in:
18alantom 2022-04-28 18:07:07 +05:30
parent 1bcd0f0afb
commit a2cd1bac21
21 changed files with 361 additions and 287 deletions

View File

@ -348,7 +348,9 @@ export default class DatabaseCore extends DatabaseBase {
} }
async #removeColumns(schemaName: string, targetColumns: string[]) { async #removeColumns(schemaName: string, targetColumns: string[]) {
// TODO: Implement this for sqlite const fields = this.schemaMap[schemaName]?.fields.map((f) => f.fieldname);
const tableRows = await this.getAll(schemaName, { fields });
this.prestigeTheTable(schemaName, tableRows);
} }
#getError(err: Error) { #getError(err: Error) {

View File

@ -88,6 +88,8 @@ export class Converter {
return toRawFloat(value, field); return toRawFloat(value, field);
case FieldTypeEnum.Check: case FieldTypeEnum.Check:
return toRawCheck(value, field); return toRawCheck(value, field);
case FieldTypeEnum.Link:
return toRawLink(value, field);
default: default:
return toRawString(value, field); return toRawString(value, field);
} }
@ -335,6 +337,18 @@ function toRawString(value: DocValue, field: Field): string | null {
throwError(value, field, 'raw'); throwError(value, field, 'raw');
} }
function toRawLink(value: DocValue, field: Field): string | null {
if (value === null || !(value as string)?.length) {
return null;
}
if (typeof value === 'string') {
return value;
}
throwError(value, field, 'raw');
}
function throwError<T>(value: T, field: Field, type: 'raw' | 'doc'): never { function throwError<T>(value: T, field: Field, type: 'raw' | 'doc'): never {
throw new ValueError( throw new ValueError(
`invalid ${type} conversion '${value}' of type ${typeof value} found, field: ${JSON.stringify( `invalid ${type} conversion '${value}' of type ${typeof value} found, field: ${JSON.stringify(

View File

@ -6,7 +6,7 @@ import {
ConflictError, ConflictError,
MandatoryError, MandatoryError,
NotFoundError, NotFoundError,
ValidationError, ValidationError
} from 'fyo/utils/errors'; } from 'fyo/utils/errors';
import Observable from 'fyo/utils/observable'; import Observable from 'fyo/utils/observable';
import Money from 'pesa/dist/types/src/money'; import Money from 'pesa/dist/types/src/money';
@ -15,7 +15,7 @@ import {
FieldTypeEnum, FieldTypeEnum,
OptionField, OptionField,
Schema, Schema,
TargetField, TargetField
} from 'schemas/types'; } from 'schemas/types';
import { getIsNullOrUndef, getMapFromList, getRandomString } from 'utils'; import { getIsNullOrUndef, getMapFromList, getRandomString } from 'utils';
import { markRaw } from 'vue'; import { markRaw } from 'vue';
@ -24,7 +24,7 @@ import {
areDocValuesEqual, areDocValuesEqual,
getMissingMandatoryMessage, getMissingMandatoryMessage,
getPreDefaultValues, getPreDefaultValues,
shouldApplyFormula, shouldApplyFormula
} from './helpers'; } from './helpers';
import { setName } from './naming'; import { setName } from './naming';
import { import {
@ -42,7 +42,7 @@ import {
ReadOnlyMap, ReadOnlyMap,
RequiredMap, RequiredMap,
TreeViewSettings, TreeViewSettings,
ValidationMap, ValidationMap
} from './types'; } from './types';
import { validateOptions, validateRequired } from './validationFunction'; import { validateOptions, validateRequired } from './validationFunction';
@ -139,14 +139,16 @@ export class Doc extends Observable<DocValue | Doc[]> {
} }
// set value and trigger change // set value and trigger change
async set(fieldname: string | DocValueMap, value?: DocValue | Doc[]) { async set(
fieldname: string | DocValueMap,
value?: DocValue | Doc[]
): Promise<boolean> {
if (typeof fieldname === 'object') { if (typeof fieldname === 'object') {
await this.setMultiple(fieldname as DocValueMap); return await this.setMultiple(fieldname as DocValueMap);
return;
} }
if (!this._canSet(fieldname, value)) { if (!this._canSet(fieldname, value)) {
return; return false;
} }
this._setDirty(true); this._setDirty(true);
@ -168,12 +170,19 @@ export class Doc extends Observable<DocValue | Doc[]> {
} else { } else {
await this._applyChange(fieldname); await this._applyChange(fieldname);
} }
return true;
} }
async setMultiple(docValueMap: DocValueMap) { async setMultiple(docValueMap: DocValueMap): Promise<boolean> {
let hasSet = false;
for (const fieldname in docValueMap) { for (const fieldname in docValueMap) {
await this.set(fieldname, docValueMap[fieldname] as DocValue | Doc[]); hasSet ||= await this.set(
fieldname,
docValueMap[fieldname] as DocValue | Doc[]
);
} }
return hasSet;
} }
_canSet(fieldname: string, value?: DocValue | Doc[]): boolean { _canSet(fieldname: string, value?: DocValue | Doc[]): boolean {
@ -197,12 +206,14 @@ export class Doc extends Observable<DocValue | Doc[]> {
return !areDocValuesEqual(currentValue as DocValue, value as DocValue); return !areDocValuesEqual(currentValue as DocValue, value as DocValue);
} }
async _applyChange(fieldname: string) { async _applyChange(fieldname: string): Promise<boolean> {
await this._applyFormula(fieldname); await this._applyFormula(fieldname);
await this.trigger('change', { await this.trigger('change', {
doc: this, doc: this,
changed: fieldname, changed: fieldname,
}); });
return true;
} }
_setDefaults() { _setDefaults() {
@ -232,13 +243,29 @@ export class Doc extends Observable<DocValue | Doc[]> {
// push child row and trigger change // push child row and trigger change
this.push(fieldname, docValueMap); this.push(fieldname, docValueMap);
this._dirty = true; this._dirty = true;
await this._applyChange(fieldname); return await this._applyChange(fieldname);
}
async remove(fieldname: string, idx: number) {
let rows = this[fieldname] as Doc[] | undefined;
if (!Array.isArray(rows)) {
return;
}
this._setDirty(true);
rows = rows.filter((row, i) => row.idx !== idx || i !== idx)!;
rows.forEach((row, i) => {
row.idx = i;
});
this[fieldname] = rows;
return await this._applyChange(fieldname);
} }
push(fieldname: string, docValueMap: Doc | DocValueMap = {}) { push(fieldname: string, docValueMap: Doc | DocValueMap = {}) {
// push child row without triggering change // push child row without triggering change
this[fieldname] ??= []; this[fieldname] ??= [];
const childDoc = this._getChildDoc(docValueMap, fieldname); const childDoc = this._getChildDoc(docValueMap, fieldname);
childDoc.parentdoc = this;
(this[fieldname] as Doc[]).push(childDoc); (this[fieldname] as Doc[]).push(childDoc);
} }
@ -389,6 +416,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
throw new NotFoundError(`Not Found: ${this.schemaName} ${this.name}`); throw new NotFoundError(`Not Found: ${this.schemaName} ${this.name}`);
} }
this._setDirty(false);
this._notInserted = false; this._notInserted = false;
this.fyo.doc.observer.trigger(`load:${this.schemaName}`, this.name); this.fyo.doc.observer.trigger(`load:${this.schemaName}`, this.name);
} }
@ -744,9 +772,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
updateMap.name = updateMap.name + ' CPY'; updateMap.name = updateMap.name + ' CPY';
} }
const doc = this.fyo.doc.getNewDoc(this.schemaName, {}, false); const doc = this.fyo.doc.getNewDoc(this.schemaName, updateMap, false);
await doc.setMultiple(updateMap);
if (shouldSync) { if (shouldSync) {
await doc.sync(); await doc.sync();
} }

View File

@ -52,7 +52,7 @@ export function getDeviceId(fyo: Fyo): UniqueId {
} }
export function getInstanceId(fyo: Fyo): UniqueId { export function getInstanceId(fyo: Fyo): UniqueId {
const files = fyo.config.get(ConfigKeys.Files) as ConfigFile[]; const files = (fyo.config.get(ConfigKeys.Files) ?? []) as ConfigFile[];
const companyName = fyo.singles.AccountingSettings?.companyName as string; const companyName = fyo.singles.AccountingSettings?.companyName as string;
if (companyName === undefined) { if (companyName === undefined) {

View File

@ -129,7 +129,7 @@ function getNumberFormatter(fyo: Fyo) {
} }
function getCurrency(field: Field, doc: Doc | null, fyo: Fyo): string { function getCurrency(field: Field, doc: Doc | null, fyo: Fyo): string {
let getCurrency = doc?.getCurrencies[field.fieldname]; let getCurrency = doc?.getCurrencies?.[field.fieldname];
if (getCurrency !== undefined) { if (getCurrency !== undefined) {
return getCurrency(); return getCurrency();
} }

View File

@ -67,7 +67,7 @@ export class Party extends Doc {
}, },
currency: async () => currency: async () =>
this.fyo.singles.AccountingSettings!.currency as string, this.fyo.singles.AccountingSettings!.currency as string,
addressDisplay: async () => { address: async () => {
const address = this.address as string | undefined; const address = this.address as string | undefined;
if (address) { if (address) {
return this.getFrom('Address', address, 'addressDisplay') as string; return this.getFrom('Address', address, 'addressDisplay') as string;

View File

@ -1 +1,6 @@
export type PartyRole = 'Both' | 'Supplier' | 'Customer'; export type PartyRole = 'Both' | 'Supplier' | 'Customer';
export enum PartyRoleEnum {
'Both' = 'Both',
'Supplier' = 'Supplier',
'Customer' = 'Customer',
}

View File

@ -1,6 +1,6 @@
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
import { Fyo } from 'fyo'; import { Fyo } from 'fyo';
import { Action, ListViewSettings } from 'fyo/model/types'; import { Action, ListViewSettings } from 'fyo/model/types';
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
import { import {
getTransactionActions, getTransactionActions,
getTransactionStatusColumn, getTransactionStatusColumn,
@ -40,13 +40,13 @@ export class PurchaseInvoice extends Invoice {
return getTransactionActions('PurchaseInvoice', fyo); return getTransactionActions('PurchaseInvoice', fyo);
} }
static getListViewSettings(fyo: Fyo): ListViewSettings { static getListViewSettings(): ListViewSettings {
return { return {
formRoute: (name) => `/edit/PurchaseInvoice/${name}`, formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
columns: [ columns: [
'party', 'party',
'name', 'name',
getTransactionStatusColumn(fyo), getTransactionStatusColumn(),
'date', 'date',
'grandTotal', 'grandTotal',
'outstandingAmount', 'outstandingAmount',

View File

@ -1,6 +1,6 @@
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
import { Fyo } from 'fyo'; import { Fyo } from 'fyo';
import { Action, ListViewSettings } from 'fyo/model/types'; import { Action, ListViewSettings } from 'fyo/model/types';
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
import { import {
getTransactionActions, getTransactionActions,
getTransactionStatusColumn, getTransactionStatusColumn,
@ -38,13 +38,13 @@ export class SalesInvoice extends Invoice {
return getTransactionActions('SalesInvoice', fyo); return getTransactionActions('SalesInvoice', fyo);
} }
static getListViewSettings(fyo: Fyo): ListViewSettings { static getListViewSettings(): ListViewSettings {
return { return {
formRoute: (name) => `/edit/SalesInvoice/${name}`, formRoute: (name) => `/edit/SalesInvoice/${name}`,
columns: [ columns: [
'party', 'party',
'name', 'name',
getTransactionStatusColumn(fyo), getTransactionStatusColumn(),
'date', 'date',
'grandTotal', 'grandTotal',
'outstandingAmount', 'outstandingAmount',

View File

@ -1,4 +1,4 @@
import { Fyo } from 'fyo'; import { Fyo, t } from 'fyo';
import { Doc } from 'fyo/model/doc'; import { Doc } from 'fyo/model/doc';
import { Action, ColumnConfig } from 'fyo/model/types'; import { Action, ColumnConfig } from 'fyo/model/types';
import { NotFoundError } from 'fyo/utils/errors'; import { NotFoundError } from 'fyo/utils/errors';
@ -75,16 +75,16 @@ export function getTransactionActions(schemaName: string, fyo: Fyo): Action[] {
]; ];
} }
export function getTransactionStatusColumn(fyo: Fyo): ColumnConfig { export function getTransactionStatusColumn(): ColumnConfig {
const statusMap = { const statusMap = {
Unpaid: fyo.t`Unpaid`, Unpaid: t`Unpaid`,
Paid: fyo.t`Paid`, Paid: t`Paid`,
Draft: fyo.t`Draft`, Draft: t`Draft`,
Cancelled: fyo.t`Cancelled`, Cancelled: t`Cancelled`,
}; };
return { return {
label: fyo.t`Status`, label: t`Status`,
fieldname: 'status', fieldname: 'status',
fieldtype: 'Select', fieldtype: 'Select',
render(doc: Doc) { render(doc: Doc) {

View File

@ -79,6 +79,13 @@
"fieldtype": "Data" "fieldtype": "Data"
} }
], ],
"quickEditFields": ["email", "role", "phone", "address", "defaultAccount", "currency", "taxId"], "quickEditFields": [
"email",
"phone",
"address",
"defaultAccount",
"currency",
"taxId"
],
"keywordFields": ["name"] "keywordFields": ["name"]
} }

View File

@ -20,7 +20,7 @@ export default {
inputClass: { inputClass: {
type: String, type: String,
default: default:
'bg-gray-100 active:bg-gray-200 focus:bg-gray-200 px-3 py-2 text-base', 'px-3 py-2 text-base',
}, },
dontReload: { dontReload: {
type: Boolean, type: Boolean,

View File

@ -21,7 +21,7 @@ export default {
const filters = await this.getFilters(keyword); const filters = await this.getFilters(keyword);
if (keyword && !filters.keywords) { if (keyword && !filters.keywords) {
filters.keywords = ['like', keyword]; filters[schema.titleField] = ['like', keyword];
} }
const fields = [ const fields = [

View File

@ -3,8 +3,10 @@
<div class="text-gray-600 text-sm mb-1" v-if="showLabel"> <div class="text-gray-600 text-sm mb-1" v-if="showLabel">
{{ df.label }} {{ df.label }}
</div> </div>
<!-- Title Row -->
<Row :ratio="ratio" class="border-b px-2 text-gray-600 w-full"> <Row :ratio="ratio" class="border-b px-2 text-gray-600 w-full">
<div class="flex items-center pl-2">No</div> <div class="flex items-center pl-2">#</div>
<div <div
:class="{ :class="{
'px-2 py-3': size === 'small', 'px-2 py-3': size === 'small',
@ -17,6 +19,8 @@
{{ df.label }} {{ df.label }}
</div> </div>
</Row> </Row>
<!-- Data Rows -->
<div class="overflow-auto" :style="{ 'max-height': rowContainerHeight }"> <div class="overflow-auto" :style="{ 'max-height': rowContainerHeight }">
<TableRow <TableRow
:class="{ 'pointer-events-none': isReadOnly }" :class="{ 'pointer-events-none': isReadOnly }"
@ -27,6 +31,8 @@
@remove="removeRow(row)" @remove="removeRow(row)"
/> />
</div> </div>
<!-- Add Row and Row Count -->
<Row <Row
:ratio="ratio" :ratio="ratio"
class="text-gray-500 cursor-pointer border-transparent px-2 w-full" class="text-gray-500 cursor-pointer border-transparent px-2 w-full"
@ -54,7 +60,7 @@
}" }"
v-if="maxRowsBeforeOverflow && value.length > maxRowsBeforeOverflow" v-if="maxRowsBeforeOverflow && value.length > maxRowsBeforeOverflow"
> >
{{ value.length }} rows {{ t`${value.length} rows` }}
</div> </div>
</Row> </Row>
</div> </div>
@ -71,9 +77,11 @@ export default {
extends: Base, extends: Base,
props: { props: {
showHeader: { showHeader: {
type: Boolean,
default: true, default: true,
}, },
maxRowsBeforeOverflow: { maxRowsBeforeOverflow: {
type: Number,
default: 0, default: 0,
}, },
}, },
@ -81,6 +89,7 @@ export default {
Row, Row,
TableRow, TableRow,
}, },
inject: ['doc'],
data: () => ({ rowContainerHeight: null }), data: () => ({ rowContainerHeight: null }),
watch: { watch: {
value: { value: {
@ -101,19 +110,28 @@ export default {
methods: { methods: {
focus() {}, focus() {},
addRow() { addRow() {
let rows = this.value || []; this.doc.append(this.df.fieldname, {}).then((s) => {
this.triggerChange([...rows, {}]); if (!s) {
return;
}
this.$nextTick(() => { this.$nextTick(() => {
this.scrollToRow(this.value.length - 1); this.scrollToRow(this.value.length - 1);
}); });
this.triggerChange(this.value);
});
}, },
removeRow(row) { removeRow(row) {
let rows = this.value || []; this.doc.remove(this.df.fieldname, row.idx).then((s) => {
rows = rows.filter((_row) => _row !== row); if (!s) {
this.triggerChange(rows); return;
}
this.triggerChange(this.value);
});
}, },
scrollToRow(index) { scrollToRow(index) {
let row = this.$refs['table-row'][index]; const row = this.$refs['table-row'][index];
row && row.$el.scrollIntoView({ block: 'nearest' }); row && row.$el.scrollIntoView({ block: 'nearest' });
}, },
}, },

View File

@ -1,5 +1,6 @@
<template> <template>
<Row :ratio="ratio" class="w-full px-2 border-b hover:bg-brand-100 group"> <Row :ratio="ratio" class="w-full px-2 border-b hover:bg-brand-100 group">
<!-- Index or Remove button -->
<div class="flex items-center pl-2 text-gray-600"> <div class="flex items-center pl-2 text-gray-600">
<span class="hidden group-hover:inline-block"> <span class="hidden group-hover:inline-block">
<feather-icon <feather-icon
@ -12,6 +13,8 @@
{{ row.idx + 1 }} {{ row.idx + 1 }}
</span> </span>
</div> </div>
<!-- Data Input Form Control -->
<FormControl <FormControl
:size="size" :size="size"
class="py-2" class="py-2"
@ -23,6 +26,8 @@
@change="(value) => onChange(df, value)" @change="(value) => onChange(df, value)"
@new-doc="(doc) => row.set(df.fieldname, doc.name)" @new-doc="(doc) => row.set(df.fieldname, doc.name)"
/> />
<!-- Error Display -->
<div <div
class="text-sm text-red-600 mb-2 pl-2 col-span-full" class="text-sm text-red-600 mb-2 pl-2 col-span-full"
v-if="Object.values(errors).length" v-if="Object.values(errors).length"
@ -32,13 +37,20 @@
</Row> </Row>
</template> </template>
<script> <script>
import { Doc } from 'fyo/model/doc';
import Row from 'src/components/Row.vue'; import Row from 'src/components/Row.vue';
import { getErrorMessage } from 'src/utils'; import { getErrorMessage } from 'src/utils';
import FormControl from './FormControl.vue'; import FormControl from './FormControl.vue';
export default { export default {
name: 'TableRow', name: 'TableRow',
props: ['row', 'tableFields', 'size', 'ratio', 'isNumeric'], props: {
row: Doc,
tableFields: Array,
size: String,
ratio: Array,
isNumeric: Function,
},
emits: ['remove'], emits: ['remove'],
components: { components: {
Row, Row,
@ -50,7 +62,7 @@ export default {
}, },
provide() { provide() {
return { return {
doctype: this.row.doctype, schemaName: this.row.schemaName,
name: this.row.name, name: this.row.name,
doc: this.row, doc: this.row,
}; };
@ -62,11 +74,6 @@ export default {
} }
this.errors[df.fieldname] = null; this.errors[df.fieldname] = null;
const oldValue = this.row.get(df.fieldname);
if (oldValue === value) {
return;
}
this.row.set(df.fieldname, value).catch((e) => { this.row.set(df.fieldname, value).catch((e) => {
this.errors[df.fieldname] = getErrorMessage(e, this.row); this.errors[df.fieldname] = getErrorMessage(e, this.row);
}); });

View File

@ -117,6 +117,7 @@ export default {
return { return {
inlineEditDoc: null, inlineEditDoc: null,
inlineEditField: null, inlineEditField: null,
formFields: [],
errors: {}, errors: {},
}; };
}, },
@ -133,8 +134,9 @@ export default {
TwoColumnForm: () => TwoColumnForm, TwoColumnForm: () => TwoColumnForm,
}, },
mounted() { mounted() {
this.setFormFields();
if (this.focusFirstInput) { if (this.focusFirstInput) {
this.$refs['controls'][0].focus(); this.$refs['controls']?.[0].focus();
} }
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
@ -176,28 +178,34 @@ export default {
return; return;
} }
const oldValue = this.doc.get(df.fieldname);
this.errors[df.fieldname] = null;
if (oldValue === value) {
return;
}
if (this.emitChange) {
this.$emit('change', df, value, oldValue);
}
// handle rename // handle rename
if (this.autosave && df.fieldname === 'name' && !this.doc.notInserted) { if (this.autosave && df.fieldname === 'name' && !this.doc.notInserted) {
return this.doc.rename(value); return this.doc.rename(value);
} }
this.onChangeCommon(df, value); const oldValue = this.doc.get(df.fieldname);
this.errors[df.fieldname] = null;
this.onChangeCommon(df, value, oldValue);
}, },
onChangeCommon(df, value) { onChangeCommon(df, value, oldValue) {
this.doc.set(df.fieldname, value).catch((e) => { this.doc
// set error message for this field .set(df.fieldname, value)
.catch((e) => {
this.errors[df.fieldname] = getErrorMessage(e, this.doc); this.errors[df.fieldname] = getErrorMessage(e, this.doc);
})
.then((success) => {
if (!success) {
return;
}
this.handlePostSet(df, value, oldValue);
}); });
},
handlePostSet(df, value, oldValue) {
this.setFormFields();
if (this.emitChange) {
this.$emit('change', df, value, oldValue);
}
if (this.autosave && this.doc.dirty) { if (this.autosave && this.doc.dirty) {
if (df.fieldtype === 'Table') { if (df.fieldtype === 'Table') {
@ -250,15 +258,14 @@ export default {
await this.stopInlineEditing(); await this.stopInlineEditing();
}, },
async stopInlineEditing() { async stopInlineEditing() {
if (this.inlineEditDoc?.dirty) { if (this.inlineEditDoc?.dirty && !this.inlineEditDoc?.notInserted) {
await this.inlineEditDoc.load(); await this.inlineEditDoc.load();
} }
this.inlineEditDoc = null; this.inlineEditDoc = null;
this.inlineEditField = null; this.inlineEditField = null;
}, },
}, setFormFields() {
computed: {
formFields() {
let fieldList = this.fields; let fieldList = this.fields;
if (fieldList.length === 0) { if (fieldList.length === 0) {
@ -269,10 +276,12 @@ export default {
fieldList = this.doc.schema.fields.filter((f) => f.required); fieldList = this.doc.schema.fields.filter((f) => f.required);
} }
return fieldList.filter( this.formFields = fieldList.filter(
(field) => field && !evaluateHidden(field, this.doc) (field) => field && !evaluateHidden(field, this.doc)
); );
}, },
},
computed: {
style() { style() {
let templateColumns = (this.columnRatio || [1, 1]) let templateColumns = (this.columnRatio || [1, 1])
.map((r) => `${r}fr`) .map((r) => `${r}fr`)

View File

@ -20,16 +20,14 @@
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span> <span>
{{ fyo.format(invoice.date, invoiceMeta.getField('date')) }} {{ fyo.format(invoice.date, getInvoiceField(invoice, 'date')) }}
</span> </span>
<div> <div>
<span <span class="font-medium text-gray-900">
class="font-medium text-gray-900"
>
{{ {{
fyo.format( fyo.format(
amountPaid(invoice), amountPaid(invoice),
invoiceMeta.getField('baseGrandTotal') getInvoiceField(invoice, 'baseGrandTotal')
) )
}} }}
</span> </span>
@ -37,7 +35,7 @@
({{ ({{
fyo.format( fyo.format(
invoice.outstandingAmount, invoice.outstandingAmount,
invoiceMeta.getField('outstandingAmount') getInvoiceField(invoice, 'outstandingAmount')
) )
}}) }})
</span> </span>
@ -49,37 +47,45 @@
</template> </template>
<script> <script>
import { Doc } from 'fyo/model/doc';
import { PartyRoleEnum } from 'models/baseModels/Party/types';
import { getTransactionStatusColumn } from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { routeTo } from 'src/utils'; import { routeTo } from 'src/utils';
// import { getStatusColumn } from '../Transaction/Transaction';
export default { export default {
name: 'PartyWidget', name: 'PartyWidget',
props: ['doc'], props: { doc: Doc },
data() { data() {
return { return {
pendingInvoices: [], pendingInvoices: [],
}; };
}, },
computed: { computed: {
invoiceDoctype() { invoiceSchemaNames() {
let isCustomer = this.doc.doctype === 'Customer'; switch (this.doc.get('role')) {
return isCustomer ? 'SalesInvoice' : 'PurchaseInvoice'; case PartyRoleEnum.Customer:
}, return [ModelNameEnum.SalesInvoice];
invoiceMeta() { case PartyRoleEnum.Supplier:
return fyo.getMeta(this.invoiceDoctype); return [ModelNameEnum.PurchaseInvoice];
case PartyRoleEnum.Both:
default:
return [ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice];
}
}, },
}, },
mounted() { mounted() {
this.fetchPendingInvoices(); this.fetchPendingInvoices();
}, },
methods: { methods: {
getInvoiceField(invoice, fieldname) {
return fyo.getField(invoice.schemaName, fieldname);
},
async fetchPendingInvoices() { async fetchPendingInvoices() {
let isCustomer = this.doc.doctype === 'Customer'; const pendingInvoices = [];
let doctype = this.invoiceDoctype; for (const schemaName of this.invoiceSchemaNames) {
let partyField = isCustomer ? 'customer' : 'supplier'; const invoices = await fyo.db.getAll(schemaName, {
this.pendingInvoices = await fyo.db.getAll({
doctype,
fields: [ fields: [
'name', 'name',
'date', 'date',
@ -88,20 +94,27 @@ export default {
'submitted', 'submitted',
], ],
filters: { filters: {
[partyField]: this.doc.name, party: this.doc.name,
}, },
limit: 3, limit: 3,
orderBy: 'created', orderBy: 'created',
}); });
window.pendingInvoices = this.pendingInvoices;
invoices.forEach((i) => {
i.schemaName = schemaName;
});
pendingInvoices.push(...invoices);
}
this.pendingInvoices = pendingInvoices;
}, },
getStatusBadge(doc) { getStatusBadge(doc) {
// let statusColumn = getStatusColumn(); const statusColumn = getTransactionStatusColumn();
// return statusColumn.render(doc); return statusColumn.render(doc);
return {}
}, },
routeToForm(doc) { routeToForm(invoice) {
routeTo(`/edit/${this.invoiceDoctype}/${doc.name}`); routeTo(`/edit/${invoice.schemaName}/${invoice.name}`);
}, },
fullyPaid(invoice) { fullyPaid(invoice) {
return invoice.outstandingAmount.isZero(); return invoice.outstandingAmount.isZero();

View File

@ -10,147 +10,121 @@
class="w-full w-600 shadow rounded-lg border relative" class="w-full w-600 shadow rounded-lg border relative"
style="height: 700px" style="height: 700px"
> >
<div class="px-6 py-8"> <!-- Welcome to Frappe Books -->
<h1 class="text-2xl font-semibold"> <div class="px-6 py-10">
<h1 class="text-2xl font-semibold select-none">
{{ t`Welcome to Frappe Books` }} {{ t`Welcome to Frappe Books` }}
</h1> </h1>
<p class="text-gray-600 text-base" v-if="!showFiles"> <p class="text-gray-600 text-base select-none">
{{ {{
t`Create a new file or select an existing one from your computer` t`Create a new file or select an existing one from your computer`
}} }}
</p> </p>
<p class="text-gray-600 text-base" v-if="showFiles">
{{ t`Select a file to load the company transactions` }}
</p>
</div> </div>
<div class="px-12 mt-6 window-no-drag" v-if="!showFiles">
<div class="flex"> <hr />
<!-- New File (Blue Icon) -->
<div <div
@click="newDatabase" @click="newDatabase"
class=" class="
w-1/2 px-6
border h-18
rounded-xl flex flex-row
flex flex-col
items-center items-center
py-8
px-5
cursor-pointer cursor-pointer
hover:shadow gap-4
p-2
hover:bg-gray-100
" "
> >
<div <div class="w-8 h-8 rounded-full bg-blue-500 relative flex-center">
class="w-14 h-14 rounded-full bg-blue-200 relative flex-center"
>
<div
class="w-12 h-12 absolute rounded-full bg-blue-500 flex-center"
>
<feather-icon name="plus" class="text-white w-5 h-5" /> <feather-icon name="plus" class="text-white w-5 h-5" />
</div> </div>
</div>
<div class="mt-5 font-medium"> <div>
<template <p class="font-medium">
v-if="loadingDatabase && fileSelectedFrom === 'New File'"
>
{{ t`Loading...` }}
</template>
<template v-else>
{{ t`New File` }} {{ t`New File` }}
</template> </p>
</div> <p class="text-sm text-gray-600">
<div class="mt-2 text-sm text-gray-600 text-center">
{{ t`Create a new file and store it in your computer.` }} {{ t`Create a new file and store it in your computer.` }}
</p>
</div> </div>
</div> </div>
<!-- Existing File (Green Icon) -->
<div <div
@click="existingDatabase" @click="existingDatabase"
class=" class="
ml-6 px-6
w-1/2 h-18
border flex flex-row
rounded-xl
flex flex-col
items-center items-center
py-8
px-5
cursor-pointer cursor-pointer
hover:shadow gap-4
p-2
hover:bg-gray-100
" "
> >
<div <div class="w-8 h-8 rounded-full bg-green-500 relative flex-center">
class="w-14 h-14 rounded-full bg-green-200 relative flex-center"
>
<div class="w-12 h-12 rounded-full bg-green-500 flex-center">
<feather-icon name="upload" class="w-4 h-4 text-white" /> <feather-icon name="upload" class="w-4 h-4 text-white" />
</div> </div>
</div> <div>
<div class="mt-5 font-medium"> <p class="font-medium">
<template
v-if="loadingDatabase && fileSelectedFrom === 'Existing File'"
>
{{ t`Loading...` }}
</template>
<template v-else>
{{ t`Existing File` }} {{ t`Existing File` }}
</template> </p>
</div> <p class="text-sm text-gray-600">
<div class="mt-2 text-sm text-gray-600 text-center">
{{ t`Load an existing .db file from your computer.` }} {{ t`Load an existing .db file from your computer.` }}
</p>
</div> </div>
</div> </div>
</div> <hr />
<a
v-if="files.length > 0"
class="text-brand text-sm mt-4 inline-block cursor-pointer"
@click="showFiles = true"
>
{{ t`Select from existing files` }}
</a>
</div>
<div v-if="showFiles"> <!-- File List -->
<div class="px-12 mt-6"> <div class="overflow-scroll" style="max-height: 340px">
<div <div
class=" class="
py-2 h-18
px-4 px-6
text-sm
flex flex
justify-between gap-4
items-center items-center
hover:bg-gray-100 hover:bg-gray-100
cursor-pointer cursor-pointer
border-b
" "
:class="{ 'border-t': i === 0 }"
v-for="(file, i) in files" v-for="(file, i) in files"
:key="file.filePath" :key="file.dbPath"
@click="selectFile(file)" @click="selectFile(file)"
> >
<div class="flex items-baseline"> <div
<span> class="
<template v-if="loadingDatabase && fileSelectedFrom === file"> w-8
{{ t`Loading...` }} h-8
</template> rounded-full
<template v-else> flex
{{ file.companyName }} justify-center
</template> items-center
</span> bg-gray-200
</div> text-gray-500
<div class="text-gray-700"> font-semibold
{{ file.modified }} text-base
</div> "
</div>
</div>
<div class="px-12 mt-4">
<a
class="text-brand text-sm cursor-pointer"
@click="showFiles = false"
> >
{{ t`Select file manually` }} {{ i + 1 }}
</a> </div>
<div>
<p class="font-medium">
{{ file.companyName }}
</p>
<p class="text-sm text-gray-600">
{{ file.modified }}
</p>
</div> </div>
</div> </div>
</div>
<hr v-if="files?.length" />
<!-- Language Selector -->
<div <div
class="w-full flex justify-end absolute px-6 py-6" class="w-full flex justify-end absolute px-6 py-6"
style="top: 100%; transform: translateY(-100%)" style="top: 100%; transform: translateY(-100%)"
@ -177,27 +151,20 @@ export default {
return { return {
loadingDatabase: false, loadingDatabase: false,
fileSelectedFrom: null, fileSelectedFrom: null,
showFiles: false,
files: [], files: [],
}; };
}, },
mounted() { mounted() {
this.setFiles(); this.setFiles();
this.showFiles = this.files.length > 0; window.ds = this;
},
watch: {
showFiles() {
this.setFiles();
},
}, },
methods: { methods: {
setFiles() { setFiles() {
this.files = cloneDeep(fyo.config.get('files', [])).filter( const files = cloneDeep(fyo.config.get('files', []));
({ filePath }) => fs.existsSync(filePath) this.files = files.filter(({ dbPath }) => fs.existsSync(dbPath));
);
for (const file of this.files) { for (const file of this.files) {
const stats = fs.statSync(file.filePath); const stats = fs.statSync(file.dbPath);
file.modified = DateTime.fromJSDate(stats.mtime).toRelative(); file.modified = DateTime.fromJSDate(stats.mtime).toRelative();
} }
}, },
@ -223,7 +190,7 @@ export default {
}, },
async selectFile(file) { async selectFile(file) {
this.fileSelectedFrom = file; this.fileSelectedFrom = file;
await this.connectToDatabase(file.filePath); await this.connectToDatabase(file.dbPath);
}, },
async connectToDatabase(filePath, isNew) { async connectToDatabase(filePath, isNew) {
if (!filePath) { if (!filePath) {

View File

@ -112,7 +112,7 @@ export default {
switch (key) { switch (key) {
case 'Opening Balances': case 'Opening Balances':
await this.updateChecks({ openingBalanceChecked: 1 }); await this.updateChecks({ openingBalanceChecked: true });
break; break;
} }
}, },
@ -124,19 +124,19 @@ export default {
switch (key) { switch (key) {
case 'Invoice': case 'Invoice':
await this.updateChecks({ invoiceSetup: 1 }); await this.updateChecks({ invoiceSetup: true });
break; break;
case 'General': case 'General':
await this.updateChecks({ companySetup: 1 }); await this.updateChecks({ companySetup: true });
break; break;
case 'System': case 'System':
await this.updateChecks({ systemSetup: 1 }); await this.updateChecks({ systemSetup: true });
break; break;
case 'Review Accounts': case 'Review Accounts':
await this.updateChecks({ chartOfAccountsReviewed: 1 }); await this.updateChecks({ chartOfAccountsReviewed: true });
break; break;
case 'Add Taxes': case 'Add Taxes':
await this.updateChecks({ taxesAdded: 1 }); await this.updateChecks({ taxesAdded: true });
break; break;
} }
}, },
@ -154,7 +154,7 @@ export default {
if (onboardingComplete) { if (onboardingComplete) {
await this.updateChecks({ onboardingComplete }); await this.updateChecks({ onboardingComplete });
const systemSettings = await fyo.doc.getSingle('SystemSettings'); const systemSettings = await fyo.doc.getSingle('SystemSettings');
await systemSettings.set({ hideGetStarted: 1 }); await systemSettings.set('hideGetStarted', true);
await systemSettings.sync(); await systemSettings.sync();
} }
@ -207,9 +207,8 @@ export default {
await this.updateChecks(toUpdate); await this.updateChecks(toUpdate);
}, },
async updateChecks(toUpdate) { async updateChecks(toUpdate) {
await fyo.singles.GetStarted.setMultiple(toUpdate); await fyo.singles.GetStarted.setAndSync(toUpdate);
await fyo.singles.GetStarted.sync(); await fyo.doc.getSingle('GetStarted');
fyo.singles.GetStarted = await fyo.doc.getSingle('GetStarted');
}, },
isCompleted(item) { isCompleted(item) {
return fyo.singles.GetStarted.get(item.fieldname) || 0; return fyo.singles.GetStarted.get(item.fieldname) || 0;

View File

@ -2,9 +2,7 @@
<div class="mx-4 pb-16 text-base flex flex-col overflow-y-hidden"> <div class="mx-4 pb-16 text-base flex flex-col overflow-y-hidden">
<!-- Title Row --> <!-- Title Row -->
<div class="flex"> <div class="flex">
<div class="py-4 mr-3 w-7 border-b" v-if="hasImage"> <div class="py-4 mr-3 w-7" v-if="hasImage" />
<p class="text-gray-700">Img</p>
</div>
<Row <Row
class="flex-1 text-gray-700" class="flex-1 text-gray-700"
:columnCount="columns.length" :columnCount="columns.length"
@ -25,11 +23,7 @@
<!-- Data Rows --> <!-- Data Rows -->
<div class="overflow-y-auto" v-if="data.length !== 0"> <div class="overflow-y-auto" v-if="data.length !== 0">
<div <div class="flex hover:bg-gray-100" v-for="doc in data" :key="doc.name">
class="px-3 flex hover:bg-gray-100 rounded-md"
v-for="doc in data"
:key="doc.name"
>
<div class="w-7 py-4 mr-3" v-if="hasImage"> <div class="w-7 py-4 mr-3" v-if="hasImage">
<Avatar :imageURL="doc.image" :label="doc.name" /> <Avatar :imageURL="doc.image" :label="doc.name" />
</div> </div>

View File

@ -1,6 +1,8 @@
<template> <template>
<div class="border-l h-full"> <div class="border-l h-full">
<!-- Quick edit Tool bar -->
<div class="flex items-center justify-between px-4 pt-4"> <div class="flex items-center justify-between px-4 pt-4">
<!-- Close Button and Status Text -->
<div class="flex items-center"> <div class="flex items-center">
<Button :icon="true" @click="routeToPrevious"> <Button :icon="true" @click="routeToPrevious">
<feather-icon name="x" class="w-4 h-4" /> <feather-icon name="x" class="w-4 h-4" />
@ -9,6 +11,8 @@
statusText statusText
}}</span> }}</span>
</div> </div>
<!-- Actions, Badge and Status Change Buttons -->
<div class="flex items-stretch"> <div class="flex items-stretch">
<DropdownWithActions :actions="actions" /> <DropdownWithActions :actions="actions" />
<StatusBadge :status="status" /> <StatusBadge :status="status" />
@ -16,7 +20,7 @@
:icon="true" :icon="true"
@click="sync" @click="sync"
type="primary" type="primary"
v-if="doc && doc.notInserted" v-if="doc?.dirty || doc?.notInserted"
class="ml-2 text-white text-xs" class="ml-2 text-white text-xs"
> >
{{ t`Save` }} {{ t`Save` }}
@ -27,10 +31,9 @@
type="primary" type="primary"
v-if=" v-if="
schema?.isSubmittable && schema?.isSubmittable &&
doc && !doc?.submitted &&
!doc.submitted && !doc?.notInserted &&
!doc.notInserted && !(doc?.cancelled || false)
!(doc.cancelled || false)
" "
class="ml-2 text-white text-xs" class="ml-2 text-white text-xs"
> >
@ -38,6 +41,8 @@
</Button> </Button>
</div> </div>
</div> </div>
<!-- Name and image -->
<div class="px-4 pt-2 pb-4 flex-center" v-if="doc"> <div class="px-4 pt-2 pb-4 flex-center" v-if="doc">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<FormControl <FormControl
@ -63,15 +68,19 @@
/> />
</div> </div>
</div> </div>
<!-- Rest of the form -->
<TwoColumnForm <TwoColumnForm
ref="form" ref="form"
v-if="doc" v-if="doc"
:doc="doc" :doc="doc"
:fields="fields" :fields="fields"
:autosave="true" :autosave="false"
:column-ratio="[1.1, 2]" :column-ratio="[1.1, 2]"
/> />
<component v-if="doc && quickEditWidget" :is="quickEditWidget" />
<!-- QuickEdit Widgets -->
<component v-if="quickEditWidget" :is="quickEditWidget" />
</div> </div>
</template> </template>
@ -167,6 +176,10 @@ export default {
return getActionsForDocument(this.doc); return getActionsForDocument(this.doc);
}, },
quickEditWidget() { quickEditWidget() {
if (this.doc?.notInserted ?? true) {
return null;
}
const widget = getQuickEditWidget(this.schemaName); const widget = getQuickEditWidget(this.schemaName);
if (widget === null) { if (widget === null) {
return null; return null;
@ -212,7 +225,7 @@ export default {
this.doc.set(fieldname, ''); this.doc.set(fieldname, '');
setTimeout(() => { setTimeout(() => {
this.$refs.titleControl.focus(); this.$refs.titleControl?.focus();
}, 300); }, 300);
}, },
async fetchDoc() { async fetchDoc() {
@ -225,9 +238,11 @@ export default {
name: this.doc.name, name: this.doc.name,
}); });
}); });
this.doc.on('beforeSync', () => { this.doc.on('beforeSync', () => {
this.statusText = t`Saving...`; this.statusText = t`Saving...`;
}); });
this.doc.on('afterSync', () => { this.doc.on('afterSync', () => {
setTimeout(() => { setTimeout(() => {
this.statusText = null; this.statusText = null;
@ -252,6 +267,9 @@ export default {
} }
}, },
routeToPrevious() { routeToPrevious() {
if (this.doc.dirty && !this.doc.notInserted) {
this.doc.load();
}
this.$router.back(); this.$router.back();
}, },
setTitleSize() { setTitleSize() {
@ -261,13 +279,8 @@ export default {
const input = this.$refs.titleControl.getInput(); const input = this.$refs.titleControl.getInput();
const value = input.value; const value = input.value;
let valueLength = (value || '').length + 1; const valueLength = (value || '').length + 1;
input.size = Math.max(valueLength, 15);
if (valueLength < 7) {
valueLength = 7;
}
input.size = valueLength;
}, },
}, },
}; };