mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
incr: fix a bunch of vue files
- no modes in the first pane
This commit is contained in:
parent
1bcd0f0afb
commit
a2cd1bac21
@ -348,7 +348,9 @@ export default class DatabaseCore extends DatabaseBase {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -88,6 +88,8 @@ export class Converter {
|
||||
return toRawFloat(value, field);
|
||||
case FieldTypeEnum.Check:
|
||||
return toRawCheck(value, field);
|
||||
case FieldTypeEnum.Link:
|
||||
return toRawLink(value, field);
|
||||
default:
|
||||
return toRawString(value, field);
|
||||
}
|
||||
@ -335,6 +337,18 @@ function toRawString(value: DocValue, field: Field): string | null {
|
||||
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 {
|
||||
throw new ValueError(
|
||||
`invalid ${type} conversion '${value}' of type ${typeof value} found, field: ${JSON.stringify(
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
ConflictError,
|
||||
MandatoryError,
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
ValidationError
|
||||
} from 'fyo/utils/errors';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
@ -15,7 +15,7 @@ import {
|
||||
FieldTypeEnum,
|
||||
OptionField,
|
||||
Schema,
|
||||
TargetField,
|
||||
TargetField
|
||||
} from 'schemas/types';
|
||||
import { getIsNullOrUndef, getMapFromList, getRandomString } from 'utils';
|
||||
import { markRaw } from 'vue';
|
||||
@ -24,7 +24,7 @@ import {
|
||||
areDocValuesEqual,
|
||||
getMissingMandatoryMessage,
|
||||
getPreDefaultValues,
|
||||
shouldApplyFormula,
|
||||
shouldApplyFormula
|
||||
} from './helpers';
|
||||
import { setName } from './naming';
|
||||
import {
|
||||
@ -42,7 +42,7 @@ import {
|
||||
ReadOnlyMap,
|
||||
RequiredMap,
|
||||
TreeViewSettings,
|
||||
ValidationMap,
|
||||
ValidationMap
|
||||
} from './types';
|
||||
import { validateOptions, validateRequired } from './validationFunction';
|
||||
|
||||
@ -139,14 +139,16 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
// 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') {
|
||||
await this.setMultiple(fieldname as DocValueMap);
|
||||
return;
|
||||
return await this.setMultiple(fieldname as DocValueMap);
|
||||
}
|
||||
|
||||
if (!this._canSet(fieldname, value)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this._setDirty(true);
|
||||
@ -168,12 +170,19 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
} else {
|
||||
await this._applyChange(fieldname);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setMultiple(docValueMap: DocValueMap) {
|
||||
async setMultiple(docValueMap: DocValueMap): Promise<boolean> {
|
||||
let hasSet = false;
|
||||
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 {
|
||||
@ -197,12 +206,14 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
return !areDocValuesEqual(currentValue as DocValue, value as DocValue);
|
||||
}
|
||||
|
||||
async _applyChange(fieldname: string) {
|
||||
async _applyChange(fieldname: string): Promise<boolean> {
|
||||
await this._applyFormula(fieldname);
|
||||
await this.trigger('change', {
|
||||
doc: this,
|
||||
changed: fieldname,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_setDefaults() {
|
||||
@ -232,13 +243,29 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
// push child row and trigger change
|
||||
this.push(fieldname, docValueMap);
|
||||
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 child row without triggering change
|
||||
this[fieldname] ??= [];
|
||||
const childDoc = this._getChildDoc(docValueMap, fieldname);
|
||||
childDoc.parentdoc = this;
|
||||
(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}`);
|
||||
}
|
||||
|
||||
this._setDirty(false);
|
||||
this._notInserted = false;
|
||||
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';
|
||||
}
|
||||
|
||||
const doc = this.fyo.doc.getNewDoc(this.schemaName, {}, false);
|
||||
await doc.setMultiple(updateMap);
|
||||
|
||||
const doc = this.fyo.doc.getNewDoc(this.schemaName, updateMap, false);
|
||||
if (shouldSync) {
|
||||
await doc.sync();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export function getDeviceId(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;
|
||||
if (companyName === undefined) {
|
||||
|
@ -129,7 +129,7 @@ function getNumberFormatter(fyo: Fyo) {
|
||||
}
|
||||
|
||||
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) {
|
||||
return getCurrency();
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export class Party extends Doc {
|
||||
},
|
||||
currency: async () =>
|
||||
this.fyo.singles.AccountingSettings!.currency as string,
|
||||
addressDisplay: async () => {
|
||||
address: async () => {
|
||||
const address = this.address as string | undefined;
|
||||
if (address) {
|
||||
return this.getFrom('Address', address, 'addressDisplay') as string;
|
||||
|
@ -1 +1,6 @@
|
||||
export type PartyRole = 'Both' | 'Supplier' | 'Customer';
|
||||
export enum PartyRoleEnum {
|
||||
'Both' = 'Both',
|
||||
'Supplier' = 'Supplier',
|
||||
'Customer' = 'Customer',
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
|
||||
import { Fyo } from 'fyo';
|
||||
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
|
||||
import {
|
||||
getTransactionActions,
|
||||
getTransactionStatusColumn,
|
||||
@ -40,13 +40,13 @@ export class PurchaseInvoice extends Invoice {
|
||||
return getTransactionActions('PurchaseInvoice', fyo);
|
||||
}
|
||||
|
||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
|
||||
columns: [
|
||||
'party',
|
||||
'name',
|
||||
getTransactionStatusColumn(fyo),
|
||||
getTransactionStatusColumn(),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
|
||||
import { Fyo } from 'fyo';
|
||||
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
|
||||
import {
|
||||
getTransactionActions,
|
||||
getTransactionStatusColumn,
|
||||
@ -38,13 +38,13 @@ export class SalesInvoice extends Invoice {
|
||||
return getTransactionActions('SalesInvoice', fyo);
|
||||
}
|
||||
|
||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/SalesInvoice/${name}`,
|
||||
columns: [
|
||||
'party',
|
||||
'name',
|
||||
getTransactionStatusColumn(fyo),
|
||||
getTransactionStatusColumn(),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Action, ColumnConfig } from 'fyo/model/types';
|
||||
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 = {
|
||||
Unpaid: fyo.t`Unpaid`,
|
||||
Paid: fyo.t`Paid`,
|
||||
Draft: fyo.t`Draft`,
|
||||
Cancelled: fyo.t`Cancelled`,
|
||||
Unpaid: t`Unpaid`,
|
||||
Paid: t`Paid`,
|
||||
Draft: t`Draft`,
|
||||
Cancelled: t`Cancelled`,
|
||||
};
|
||||
|
||||
return {
|
||||
label: fyo.t`Status`,
|
||||
label: t`Status`,
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
render(doc: Doc) {
|
||||
|
@ -79,6 +79,13 @@
|
||||
"fieldtype": "Data"
|
||||
}
|
||||
],
|
||||
"quickEditFields": ["email", "role", "phone", "address", "defaultAccount", "currency", "taxId"],
|
||||
"quickEditFields": [
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"defaultAccount",
|
||||
"currency",
|
||||
"taxId"
|
||||
],
|
||||
"keywordFields": ["name"]
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export default {
|
||||
inputClass: {
|
||||
type: String,
|
||||
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: {
|
||||
type: Boolean,
|
||||
|
@ -21,7 +21,7 @@ export default {
|
||||
const filters = await this.getFilters(keyword);
|
||||
|
||||
if (keyword && !filters.keywords) {
|
||||
filters.keywords = ['like', keyword];
|
||||
filters[schema.titleField] = ['like', keyword];
|
||||
}
|
||||
|
||||
const fields = [
|
||||
|
@ -3,8 +3,10 @@
|
||||
<div class="text-gray-600 text-sm mb-1" v-if="showLabel">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
|
||||
<!-- Title Row -->
|
||||
<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
|
||||
:class="{
|
||||
'px-2 py-3': size === 'small',
|
||||
@ -17,6 +19,8 @@
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<!-- Data Rows -->
|
||||
<div class="overflow-auto" :style="{ 'max-height': rowContainerHeight }">
|
||||
<TableRow
|
||||
:class="{ 'pointer-events-none': isReadOnly }"
|
||||
@ -27,6 +31,8 @@
|
||||
@remove="removeRow(row)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Add Row and Row Count -->
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="text-gray-500 cursor-pointer border-transparent px-2 w-full"
|
||||
@ -54,7 +60,7 @@
|
||||
}"
|
||||
v-if="maxRowsBeforeOverflow && value.length > maxRowsBeforeOverflow"
|
||||
>
|
||||
{{ value.length }} rows
|
||||
{{ t`${value.length} rows` }}
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
@ -71,9 +77,11 @@ export default {
|
||||
extends: Base,
|
||||
props: {
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
maxRowsBeforeOverflow: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
@ -81,6 +89,7 @@ export default {
|
||||
Row,
|
||||
TableRow,
|
||||
},
|
||||
inject: ['doc'],
|
||||
data: () => ({ rowContainerHeight: null }),
|
||||
watch: {
|
||||
value: {
|
||||
@ -101,19 +110,28 @@ export default {
|
||||
methods: {
|
||||
focus() {},
|
||||
addRow() {
|
||||
let rows = this.value || [];
|
||||
this.triggerChange([...rows, {}]);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToRow(this.value.length - 1);
|
||||
this.doc.append(this.df.fieldname, {}).then((s) => {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollToRow(this.value.length - 1);
|
||||
});
|
||||
this.triggerChange(this.value);
|
||||
});
|
||||
},
|
||||
removeRow(row) {
|
||||
let rows = this.value || [];
|
||||
rows = rows.filter((_row) => _row !== row);
|
||||
this.triggerChange(rows);
|
||||
this.doc.remove(this.df.fieldname, row.idx).then((s) => {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerChange(this.value);
|
||||
});
|
||||
},
|
||||
scrollToRow(index) {
|
||||
let row = this.$refs['table-row'][index];
|
||||
const row = this.$refs['table-row'][index];
|
||||
row && row.$el.scrollIntoView({ block: 'nearest' });
|
||||
},
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<span class="hidden group-hover:inline-block">
|
||||
<feather-icon
|
||||
@ -12,6 +13,8 @@
|
||||
{{ row.idx + 1 }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Data Input Form Control -->
|
||||
<FormControl
|
||||
:size="size"
|
||||
class="py-2"
|
||||
@ -23,6 +26,8 @@
|
||||
@change="(value) => onChange(df, value)"
|
||||
@new-doc="(doc) => row.set(df.fieldname, doc.name)"
|
||||
/>
|
||||
|
||||
<!-- Error Display -->
|
||||
<div
|
||||
class="text-sm text-red-600 mb-2 pl-2 col-span-full"
|
||||
v-if="Object.values(errors).length"
|
||||
@ -32,13 +37,20 @@
|
||||
</Row>
|
||||
</template>
|
||||
<script>
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import Row from 'src/components/Row.vue';
|
||||
import { getErrorMessage } from 'src/utils';
|
||||
import FormControl from './FormControl.vue';
|
||||
|
||||
export default {
|
||||
name: 'TableRow',
|
||||
props: ['row', 'tableFields', 'size', 'ratio', 'isNumeric'],
|
||||
props: {
|
||||
row: Doc,
|
||||
tableFields: Array,
|
||||
size: String,
|
||||
ratio: Array,
|
||||
isNumeric: Function,
|
||||
},
|
||||
emits: ['remove'],
|
||||
components: {
|
||||
Row,
|
||||
@ -50,7 +62,7 @@ export default {
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
doctype: this.row.doctype,
|
||||
schemaName: this.row.schemaName,
|
||||
name: this.row.name,
|
||||
doc: this.row,
|
||||
};
|
||||
@ -62,11 +74,6 @@ export default {
|
||||
}
|
||||
|
||||
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.errors[df.fieldname] = getErrorMessage(e, this.row);
|
||||
});
|
||||
|
@ -117,6 +117,7 @@ export default {
|
||||
return {
|
||||
inlineEditDoc: null,
|
||||
inlineEditField: null,
|
||||
formFields: [],
|
||||
errors: {},
|
||||
};
|
||||
},
|
||||
@ -133,8 +134,9 @@ export default {
|
||||
TwoColumnForm: () => TwoColumnForm,
|
||||
},
|
||||
mounted() {
|
||||
this.setFormFields();
|
||||
if (this.focusFirstInput) {
|
||||
this.$refs['controls'][0].focus();
|
||||
this.$refs['controls']?.[0].focus();
|
||||
}
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
@ -176,28 +178,34 @@ export default {
|
||||
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
|
||||
if (this.autosave && df.fieldname === 'name' && !this.doc.notInserted) {
|
||||
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) {
|
||||
this.doc.set(df.fieldname, value).catch((e) => {
|
||||
// set error message for this field
|
||||
this.errors[df.fieldname] = getErrorMessage(e, this.doc);
|
||||
});
|
||||
onChangeCommon(df, value, oldValue) {
|
||||
this.doc
|
||||
.set(df.fieldname, value)
|
||||
.catch((e) => {
|
||||
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 (df.fieldtype === 'Table') {
|
||||
@ -250,15 +258,14 @@ export default {
|
||||
await this.stopInlineEditing();
|
||||
},
|
||||
async stopInlineEditing() {
|
||||
if (this.inlineEditDoc?.dirty) {
|
||||
if (this.inlineEditDoc?.dirty && !this.inlineEditDoc?.notInserted) {
|
||||
await this.inlineEditDoc.load();
|
||||
}
|
||||
|
||||
this.inlineEditDoc = null;
|
||||
this.inlineEditField = null;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
formFields() {
|
||||
setFormFields() {
|
||||
let fieldList = this.fields;
|
||||
|
||||
if (fieldList.length === 0) {
|
||||
@ -269,10 +276,12 @@ export default {
|
||||
fieldList = this.doc.schema.fields.filter((f) => f.required);
|
||||
}
|
||||
|
||||
return fieldList.filter(
|
||||
this.formFields = fieldList.filter(
|
||||
(field) => field && !evaluateHidden(field, this.doc)
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
let templateColumns = (this.columnRatio || [1, 1])
|
||||
.map((r) => `${r}fr`)
|
||||
|
@ -20,16 +20,14 @@
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
{{ fyo.format(invoice.date, invoiceMeta.getField('date')) }}
|
||||
{{ fyo.format(invoice.date, getInvoiceField(invoice, 'date')) }}
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
class="font-medium text-gray-900"
|
||||
>
|
||||
<span class="font-medium text-gray-900">
|
||||
{{
|
||||
fyo.format(
|
||||
amountPaid(invoice),
|
||||
invoiceMeta.getField('baseGrandTotal')
|
||||
getInvoiceField(invoice, 'baseGrandTotal')
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
@ -37,7 +35,7 @@
|
||||
({{
|
||||
fyo.format(
|
||||
invoice.outstandingAmount,
|
||||
invoiceMeta.getField('outstandingAmount')
|
||||
getInvoiceField(invoice, 'outstandingAmount')
|
||||
)
|
||||
}})
|
||||
</span>
|
||||
@ -49,59 +47,74 @@
|
||||
</template>
|
||||
|
||||
<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 { routeTo } from 'src/utils';
|
||||
// import { getStatusColumn } from '../Transaction/Transaction';
|
||||
|
||||
export default {
|
||||
name: 'PartyWidget',
|
||||
props: ['doc'],
|
||||
props: { doc: Doc },
|
||||
data() {
|
||||
return {
|
||||
pendingInvoices: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
invoiceDoctype() {
|
||||
let isCustomer = this.doc.doctype === 'Customer';
|
||||
return isCustomer ? 'SalesInvoice' : 'PurchaseInvoice';
|
||||
},
|
||||
invoiceMeta() {
|
||||
return fyo.getMeta(this.invoiceDoctype);
|
||||
invoiceSchemaNames() {
|
||||
switch (this.doc.get('role')) {
|
||||
case PartyRoleEnum.Customer:
|
||||
return [ModelNameEnum.SalesInvoice];
|
||||
case PartyRoleEnum.Supplier:
|
||||
return [ModelNameEnum.PurchaseInvoice];
|
||||
case PartyRoleEnum.Both:
|
||||
default:
|
||||
return [ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice];
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchPendingInvoices();
|
||||
},
|
||||
methods: {
|
||||
getInvoiceField(invoice, fieldname) {
|
||||
return fyo.getField(invoice.schemaName, fieldname);
|
||||
},
|
||||
async fetchPendingInvoices() {
|
||||
let isCustomer = this.doc.doctype === 'Customer';
|
||||
let doctype = this.invoiceDoctype;
|
||||
let partyField = isCustomer ? 'customer' : 'supplier';
|
||||
this.pendingInvoices = await fyo.db.getAll({
|
||||
doctype,
|
||||
fields: [
|
||||
'name',
|
||||
'date',
|
||||
'outstandingAmount',
|
||||
'baseGrandTotal',
|
||||
'submitted',
|
||||
],
|
||||
filters: {
|
||||
[partyField]: this.doc.name,
|
||||
},
|
||||
limit: 3,
|
||||
orderBy: 'created',
|
||||
});
|
||||
window.pendingInvoices = this.pendingInvoices;
|
||||
const pendingInvoices = [];
|
||||
for (const schemaName of this.invoiceSchemaNames) {
|
||||
const invoices = await fyo.db.getAll(schemaName, {
|
||||
fields: [
|
||||
'name',
|
||||
'date',
|
||||
'outstandingAmount',
|
||||
'baseGrandTotal',
|
||||
'submitted',
|
||||
],
|
||||
filters: {
|
||||
party: this.doc.name,
|
||||
},
|
||||
limit: 3,
|
||||
orderBy: 'created',
|
||||
});
|
||||
|
||||
invoices.forEach((i) => {
|
||||
i.schemaName = schemaName;
|
||||
});
|
||||
|
||||
pendingInvoices.push(...invoices);
|
||||
}
|
||||
|
||||
this.pendingInvoices = pendingInvoices;
|
||||
},
|
||||
getStatusBadge(doc) {
|
||||
// let statusColumn = getStatusColumn();
|
||||
// return statusColumn.render(doc);
|
||||
return {}
|
||||
const statusColumn = getTransactionStatusColumn();
|
||||
return statusColumn.render(doc);
|
||||
},
|
||||
routeToForm(doc) {
|
||||
routeTo(`/edit/${this.invoiceDoctype}/${doc.name}`);
|
||||
routeToForm(invoice) {
|
||||
routeTo(`/edit/${invoice.schemaName}/${invoice.name}`);
|
||||
},
|
||||
fullyPaid(invoice) {
|
||||
return invoice.outstandingAmount.isZero();
|
||||
|
@ -10,147 +10,121 @@
|
||||
class="w-full w-600 shadow rounded-lg border relative"
|
||||
style="height: 700px"
|
||||
>
|
||||
<div class="px-6 py-8">
|
||||
<h1 class="text-2xl font-semibold">
|
||||
<!-- Welcome to Frappe Books -->
|
||||
<div class="px-6 py-10">
|
||||
<h1 class="text-2xl font-semibold select-none">
|
||||
{{ t`Welcome to Frappe Books` }}
|
||||
</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`
|
||||
}}
|
||||
</p>
|
||||
<p class="text-gray-600 text-base" v-if="showFiles">
|
||||
{{ t`Select a file to load the company transactions` }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="px-12 mt-6 window-no-drag" v-if="!showFiles">
|
||||
<div class="flex">
|
||||
<div
|
||||
@click="newDatabase"
|
||||
class="
|
||||
w-1/2
|
||||
border
|
||||
rounded-xl
|
||||
flex flex-col
|
||||
items-center
|
||||
py-8
|
||||
px-5
|
||||
cursor-pointer
|
||||
hover:shadow
|
||||
"
|
||||
>
|
||||
<div
|
||||
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" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 font-medium">
|
||||
<template
|
||||
v-if="loadingDatabase && fileSelectedFrom === 'New File'"
|
||||
>
|
||||
{{ t`Loading...` }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t`New File` }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-600 text-center">
|
||||
{{ t`Create a new file and store it in your computer.` }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@click="existingDatabase"
|
||||
class="
|
||||
ml-6
|
||||
w-1/2
|
||||
border
|
||||
rounded-xl
|
||||
flex flex-col
|
||||
items-center
|
||||
py-8
|
||||
px-5
|
||||
cursor-pointer
|
||||
hover:shadow
|
||||
"
|
||||
>
|
||||
<div
|
||||
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" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 font-medium">
|
||||
<template
|
||||
v-if="loadingDatabase && fileSelectedFrom === 'Existing File'"
|
||||
>
|
||||
{{ t`Loading...` }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t`Existing File` }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-600 text-center">
|
||||
{{ t`Load an existing .db file from your computer.` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div class="px-12 mt-6">
|
||||
<div
|
||||
class="
|
||||
py-2
|
||||
px-4
|
||||
text-sm
|
||||
flex
|
||||
justify-between
|
||||
items-center
|
||||
hover:bg-gray-100
|
||||
cursor-pointer
|
||||
border-b
|
||||
"
|
||||
:class="{ 'border-t': i === 0 }"
|
||||
v-for="(file, i) in files"
|
||||
:key="file.filePath"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<div class="flex items-baseline">
|
||||
<span>
|
||||
<template v-if="loadingDatabase && fileSelectedFrom === file">
|
||||
{{ t`Loading...` }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ file.companyName }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-gray-700">
|
||||
{{ file.modified }}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- New File (Blue Icon) -->
|
||||
<div
|
||||
@click="newDatabase"
|
||||
class="
|
||||
px-6
|
||||
h-18
|
||||
flex flex-row
|
||||
items-center
|
||||
cursor-pointer
|
||||
gap-4
|
||||
p-2
|
||||
hover:bg-gray-100
|
||||
"
|
||||
>
|
||||
<div class="w-8 h-8 rounded-full bg-blue-500 relative flex-center">
|
||||
<feather-icon name="plus" class="text-white w-5 h-5" />
|
||||
</div>
|
||||
<div class="px-12 mt-4">
|
||||
<a
|
||||
class="text-brand text-sm cursor-pointer"
|
||||
@click="showFiles = false"
|
||||
>
|
||||
{{ t`Select file manually` }}
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<p class="font-medium">
|
||||
{{ t`New File` }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ t`Create a new file and store it in your computer.` }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing File (Green Icon) -->
|
||||
<div
|
||||
@click="existingDatabase"
|
||||
class="
|
||||
px-6
|
||||
h-18
|
||||
flex flex-row
|
||||
items-center
|
||||
cursor-pointer
|
||||
gap-4
|
||||
p-2
|
||||
hover:bg-gray-100
|
||||
"
|
||||
>
|
||||
<div class="w-8 h-8 rounded-full bg-green-500 relative flex-center">
|
||||
<feather-icon name="upload" class="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium">
|
||||
{{ t`Existing File` }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ t`Load an existing .db file from your computer.` }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- File List -->
|
||||
<div class="overflow-scroll" style="max-height: 340px">
|
||||
<div
|
||||
class="
|
||||
h-18
|
||||
px-6
|
||||
flex
|
||||
gap-4
|
||||
items-center
|
||||
hover:bg-gray-100
|
||||
cursor-pointer
|
||||
"
|
||||
v-for="(file, i) in files"
|
||||
:key="file.dbPath"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
w-8
|
||||
h-8
|
||||
rounded-full
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
bg-gray-200
|
||||
text-gray-500
|
||||
font-semibold
|
||||
text-base
|
||||
"
|
||||
>
|
||||
{{ i + 1 }}
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium">
|
||||
{{ file.companyName }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ file.modified }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr v-if="files?.length" />
|
||||
|
||||
<!-- Language Selector -->
|
||||
<div
|
||||
class="w-full flex justify-end absolute px-6 py-6"
|
||||
style="top: 100%; transform: translateY(-100%)"
|
||||
@ -177,27 +151,20 @@ export default {
|
||||
return {
|
||||
loadingDatabase: false,
|
||||
fileSelectedFrom: null,
|
||||
showFiles: false,
|
||||
files: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setFiles();
|
||||
this.showFiles = this.files.length > 0;
|
||||
},
|
||||
watch: {
|
||||
showFiles() {
|
||||
this.setFiles();
|
||||
},
|
||||
window.ds = this;
|
||||
},
|
||||
methods: {
|
||||
setFiles() {
|
||||
this.files = cloneDeep(fyo.config.get('files', [])).filter(
|
||||
({ filePath }) => fs.existsSync(filePath)
|
||||
);
|
||||
const files = cloneDeep(fyo.config.get('files', []));
|
||||
this.files = files.filter(({ dbPath }) => fs.existsSync(dbPath));
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
@ -223,7 +190,7 @@ export default {
|
||||
},
|
||||
async selectFile(file) {
|
||||
this.fileSelectedFrom = file;
|
||||
await this.connectToDatabase(file.filePath);
|
||||
await this.connectToDatabase(file.dbPath);
|
||||
},
|
||||
async connectToDatabase(filePath, isNew) {
|
||||
if (!filePath) {
|
||||
|
@ -112,7 +112,7 @@ export default {
|
||||
|
||||
switch (key) {
|
||||
case 'Opening Balances':
|
||||
await this.updateChecks({ openingBalanceChecked: 1 });
|
||||
await this.updateChecks({ openingBalanceChecked: true });
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -124,19 +124,19 @@ export default {
|
||||
|
||||
switch (key) {
|
||||
case 'Invoice':
|
||||
await this.updateChecks({ invoiceSetup: 1 });
|
||||
await this.updateChecks({ invoiceSetup: true });
|
||||
break;
|
||||
case 'General':
|
||||
await this.updateChecks({ companySetup: 1 });
|
||||
await this.updateChecks({ companySetup: true });
|
||||
break;
|
||||
case 'System':
|
||||
await this.updateChecks({ systemSetup: 1 });
|
||||
await this.updateChecks({ systemSetup: true });
|
||||
break;
|
||||
case 'Review Accounts':
|
||||
await this.updateChecks({ chartOfAccountsReviewed: 1 });
|
||||
await this.updateChecks({ chartOfAccountsReviewed: true });
|
||||
break;
|
||||
case 'Add Taxes':
|
||||
await this.updateChecks({ taxesAdded: 1 });
|
||||
await this.updateChecks({ taxesAdded: true });
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -154,7 +154,7 @@ export default {
|
||||
if (onboardingComplete) {
|
||||
await this.updateChecks({ onboardingComplete });
|
||||
const systemSettings = await fyo.doc.getSingle('SystemSettings');
|
||||
await systemSettings.set({ hideGetStarted: 1 });
|
||||
await systemSettings.set('hideGetStarted', true);
|
||||
await systemSettings.sync();
|
||||
}
|
||||
|
||||
@ -207,9 +207,8 @@ export default {
|
||||
await this.updateChecks(toUpdate);
|
||||
},
|
||||
async updateChecks(toUpdate) {
|
||||
await fyo.singles.GetStarted.setMultiple(toUpdate);
|
||||
await fyo.singles.GetStarted.sync();
|
||||
fyo.singles.GetStarted = await fyo.doc.getSingle('GetStarted');
|
||||
await fyo.singles.GetStarted.setAndSync(toUpdate);
|
||||
await fyo.doc.getSingle('GetStarted');
|
||||
},
|
||||
isCompleted(item) {
|
||||
return fyo.singles.GetStarted.get(item.fieldname) || 0;
|
||||
|
@ -2,9 +2,7 @@
|
||||
<div class="mx-4 pb-16 text-base flex flex-col overflow-y-hidden">
|
||||
<!-- Title Row -->
|
||||
<div class="flex">
|
||||
<div class="py-4 mr-3 w-7 border-b" v-if="hasImage">
|
||||
<p class="text-gray-700">Img</p>
|
||||
</div>
|
||||
<div class="py-4 mr-3 w-7" v-if="hasImage" />
|
||||
<Row
|
||||
class="flex-1 text-gray-700"
|
||||
:columnCount="columns.length"
|
||||
@ -25,11 +23,7 @@
|
||||
|
||||
<!-- Data Rows -->
|
||||
<div class="overflow-y-auto" v-if="data.length !== 0">
|
||||
<div
|
||||
class="px-3 flex hover:bg-gray-100 rounded-md"
|
||||
v-for="doc in data"
|
||||
:key="doc.name"
|
||||
>
|
||||
<div class="flex hover:bg-gray-100" v-for="doc in data" :key="doc.name">
|
||||
<div class="w-7 py-4 mr-3" v-if="hasImage">
|
||||
<Avatar :imageURL="doc.image" :label="doc.name" />
|
||||
</div>
|
||||
|
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="border-l h-full">
|
||||
<!-- Quick edit Tool bar -->
|
||||
<div class="flex items-center justify-between px-4 pt-4">
|
||||
<!-- Close Button and Status Text -->
|
||||
<div class="flex items-center">
|
||||
<Button :icon="true" @click="routeToPrevious">
|
||||
<feather-icon name="x" class="w-4 h-4" />
|
||||
@ -9,6 +11,8 @@
|
||||
statusText
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Actions, Badge and Status Change Buttons -->
|
||||
<div class="flex items-stretch">
|
||||
<DropdownWithActions :actions="actions" />
|
||||
<StatusBadge :status="status" />
|
||||
@ -16,7 +20,7 @@
|
||||
:icon="true"
|
||||
@click="sync"
|
||||
type="primary"
|
||||
v-if="doc && doc.notInserted"
|
||||
v-if="doc?.dirty || doc?.notInserted"
|
||||
class="ml-2 text-white text-xs"
|
||||
>
|
||||
{{ t`Save` }}
|
||||
@ -27,10 +31,9 @@
|
||||
type="primary"
|
||||
v-if="
|
||||
schema?.isSubmittable &&
|
||||
doc &&
|
||||
!doc.submitted &&
|
||||
!doc.notInserted &&
|
||||
!(doc.cancelled || false)
|
||||
!doc?.submitted &&
|
||||
!doc?.notInserted &&
|
||||
!(doc?.cancelled || false)
|
||||
"
|
||||
class="ml-2 text-white text-xs"
|
||||
>
|
||||
@ -38,6 +41,8 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Name and image -->
|
||||
<div class="px-4 pt-2 pb-4 flex-center" v-if="doc">
|
||||
<div class="flex flex-col items-center">
|
||||
<FormControl
|
||||
@ -63,15 +68,19 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rest of the form -->
|
||||
<TwoColumnForm
|
||||
ref="form"
|
||||
v-if="doc"
|
||||
:doc="doc"
|
||||
:fields="fields"
|
||||
:autosave="true"
|
||||
:autosave="false"
|
||||
:column-ratio="[1.1, 2]"
|
||||
/>
|
||||
<component v-if="doc && quickEditWidget" :is="quickEditWidget" />
|
||||
|
||||
<!-- QuickEdit Widgets -->
|
||||
<component v-if="quickEditWidget" :is="quickEditWidget" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -167,6 +176,10 @@ export default {
|
||||
return getActionsForDocument(this.doc);
|
||||
},
|
||||
quickEditWidget() {
|
||||
if (this.doc?.notInserted ?? true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const widget = getQuickEditWidget(this.schemaName);
|
||||
if (widget === null) {
|
||||
return null;
|
||||
@ -212,7 +225,7 @@ export default {
|
||||
this.doc.set(fieldname, '');
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.titleControl.focus();
|
||||
this.$refs.titleControl?.focus();
|
||||
}, 300);
|
||||
},
|
||||
async fetchDoc() {
|
||||
@ -225,9 +238,11 @@ export default {
|
||||
name: this.doc.name,
|
||||
});
|
||||
});
|
||||
|
||||
this.doc.on('beforeSync', () => {
|
||||
this.statusText = t`Saving...`;
|
||||
});
|
||||
|
||||
this.doc.on('afterSync', () => {
|
||||
setTimeout(() => {
|
||||
this.statusText = null;
|
||||
@ -252,6 +267,9 @@ export default {
|
||||
}
|
||||
},
|
||||
routeToPrevious() {
|
||||
if (this.doc.dirty && !this.doc.notInserted) {
|
||||
this.doc.load();
|
||||
}
|
||||
this.$router.back();
|
||||
},
|
||||
setTitleSize() {
|
||||
@ -261,13 +279,8 @@ export default {
|
||||
|
||||
const input = this.$refs.titleControl.getInput();
|
||||
const value = input.value;
|
||||
let valueLength = (value || '').length + 1;
|
||||
|
||||
if (valueLength < 7) {
|
||||
valueLength = 7;
|
||||
}
|
||||
|
||||
input.size = valueLength;
|
||||
const valueLength = (value || '').length + 1;
|
||||
input.size = Math.max(valueLength, 15);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user