mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
fix: db updation, invoice submit, number series
This commit is contained in:
parent
185110276d
commit
8cc46c584c
@ -460,9 +460,8 @@ export default class DatabaseCore extends DatabaseBase {
|
||||
let comparisonValue = value as string | number | (string | number)[];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
operator = value[0] as string;
|
||||
operator = (value[0] as string).toLowerCase();
|
||||
comparisonValue = value[1] as string | number | (string | number)[];
|
||||
operator = operator.toLowerCase();
|
||||
|
||||
if (operator === 'includes') {
|
||||
operator = 'like';
|
||||
@ -671,12 +670,12 @@ export default class DatabaseCore extends DatabaseBase {
|
||||
idx: number
|
||||
) {
|
||||
if (!child.name) {
|
||||
child.name = getRandomString();
|
||||
child.name ??= getRandomString();
|
||||
}
|
||||
child.parent = parentName;
|
||||
child.parentSchemaName = parentSchemaName;
|
||||
child.parentFieldname = field.fieldname;
|
||||
child.idx = idx;
|
||||
child.idx ??= idx;
|
||||
}
|
||||
|
||||
async #addForeignKeys(schemaName: string, newForeignKeys: Field[]) {
|
||||
@ -878,14 +877,19 @@ export default class DatabaseCore extends DatabaseBase {
|
||||
fieldValueMap: FieldValueMap,
|
||||
isUpdate: boolean
|
||||
) {
|
||||
const parentName = fieldValueMap.name as string;
|
||||
const tableFields = this.#getTableFields(schemaName);
|
||||
|
||||
const parentName = fieldValueMap.name as string;
|
||||
for (const field of tableFields) {
|
||||
const added: string[] = [];
|
||||
|
||||
const tableFieldValue = (fieldValueMap[field.fieldname] ??
|
||||
[]) as FieldValueMap[];
|
||||
const tableFieldValue = fieldValueMap[field.fieldname] as
|
||||
| FieldValueMap[]
|
||||
| undefined;
|
||||
if (tableFieldValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const child of tableFieldValue) {
|
||||
this.#prepareChild(schemaName, parentName, child, field, added.length);
|
||||
|
||||
|
@ -24,6 +24,12 @@ const Customer = {
|
||||
fieldtype: 'Data',
|
||||
placeholder: 'john@thoe.com',
|
||||
},
|
||||
{
|
||||
fieldname: 'phone',
|
||||
label: 'Phone',
|
||||
fieldtype: 'Data',
|
||||
placeholder: '9999999999',
|
||||
},
|
||||
],
|
||||
quickEditFields: ['email'],
|
||||
keywordFields: ['name'],
|
||||
|
@ -351,6 +351,26 @@ describe('DatabaseCore: CRUD', function () {
|
||||
`email check update ${firstRow.email}`
|
||||
);
|
||||
|
||||
const phone = '8149133530';
|
||||
await sleep(1);
|
||||
await db.update(schemaName, {
|
||||
name,
|
||||
phone,
|
||||
modified: new Date().toISOString(),
|
||||
});
|
||||
rows = await db.knex!(schemaName);
|
||||
firstRow = rows?.[0];
|
||||
assert.strictEqual(
|
||||
firstRow.email,
|
||||
email,
|
||||
`email check update ${firstRow.email}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
firstRow.phone,
|
||||
phone,
|
||||
`email check update ${firstRow.phone}`
|
||||
);
|
||||
|
||||
for (const key in metaValues) {
|
||||
const val = firstRow[key];
|
||||
const expected = metaValues[key as BaseMetaKey];
|
||||
@ -586,6 +606,17 @@ describe('DatabaseCore: CRUD', function () {
|
||||
}
|
||||
}
|
||||
|
||||
invoice.date = '2022-04-01';
|
||||
invoice.modified = new Date().toISOString();
|
||||
await db.update('SalesInvoice', {
|
||||
name: invoice.name,
|
||||
date: invoice.date,
|
||||
modified: invoice.modified,
|
||||
});
|
||||
|
||||
rows = await db.knex!(SalesInvoiceItem);
|
||||
assert.strictEqual(rows.length, 2, `postupdate ct empty ${rows.length}`);
|
||||
|
||||
await db.delete(SalesInvoice, invoice.name as string);
|
||||
rows = await db.getAll(SalesInvoiceItem);
|
||||
assert.strictEqual(rows.length, 0, `ct length delete ${rows.length}`);
|
||||
|
@ -87,20 +87,6 @@ export class DocHandler {
|
||||
return docMap?.[name];
|
||||
}
|
||||
|
||||
getCachedValue(
|
||||
schemaName: string,
|
||||
name: string,
|
||||
fieldname: string
|
||||
): DocValue | Doc[] | undefined {
|
||||
const docMap = this.docs[schemaName] as DocMap;
|
||||
const doc = docMap[name];
|
||||
if (doc === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return doc.get(fieldname);
|
||||
}
|
||||
|
||||
isDirty(schemaName: string, name: string): boolean {
|
||||
const doc = (this.docs?.[schemaName] as DocMap)?.[name];
|
||||
if (doc === undefined) {
|
||||
|
25
fyo/index.ts
25
fyo/index.ts
@ -1,5 +1,6 @@
|
||||
import { getMoneyMaker, MoneyMaker } from 'pesa';
|
||||
import { Field } from 'schemas/types';
|
||||
import { getIsNullOrUndef } from 'utils';
|
||||
import { markRaw } from 'vue';
|
||||
import { AuthHandler } from './core/authHandler';
|
||||
import { DatabaseHandler } from './core/dbHandler';
|
||||
@ -166,6 +167,30 @@ export class Fyo {
|
||||
return schema?.fields.find((f) => f.fieldname === fieldname);
|
||||
}
|
||||
|
||||
async getValue(
|
||||
schemaName: string,
|
||||
name: string,
|
||||
fieldname?: string
|
||||
): Promise<DocValue | Doc[]> {
|
||||
if (fieldname === undefined && this.schemaMap[schemaName]?.isSingle) {
|
||||
fieldname = name;
|
||||
name = schemaName;
|
||||
}
|
||||
|
||||
if (getIsNullOrUndef(name) || getIsNullOrUndef(fieldname)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let doc: Doc;
|
||||
try {
|
||||
doc = await this.doc.getDoc(schemaName, name);
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return doc.get(fieldname!);
|
||||
}
|
||||
|
||||
purgeCache() {
|
||||
this.pesa = getMoneyMaker({
|
||||
currency: DEFAULT_CURRENCY,
|
||||
|
113
fyo/model/doc.ts
113
fyo/model/doc.ts
@ -2,12 +2,7 @@ import { Fyo } from 'fyo';
|
||||
import { DocValue, DocValueMap } from 'fyo/core/types';
|
||||
import { Verb } from 'fyo/telemetry/types';
|
||||
import { DEFAULT_USER } from 'fyo/utils/consts';
|
||||
import {
|
||||
ConflictError,
|
||||
MandatoryError,
|
||||
NotFoundError,
|
||||
ValidationError
|
||||
} from 'fyo/utils/errors';
|
||||
import { ConflictError, MandatoryError, NotFoundError } from 'fyo/utils/errors';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import {
|
||||
@ -25,7 +20,7 @@ import {
|
||||
getMissingMandatoryMessage,
|
||||
getPreDefaultValues,
|
||||
setChildDocIdx,
|
||||
shouldApplyFormula,
|
||||
shouldApplyFormula
|
||||
} from './helpers';
|
||||
import { setName } from './naming';
|
||||
import {
|
||||
@ -42,7 +37,7 @@ import {
|
||||
ReadOnlyMap,
|
||||
RequiredMap,
|
||||
TreeViewSettings,
|
||||
ValidationMap,
|
||||
ValidationMap
|
||||
} from './types';
|
||||
import { validateOptions, validateRequired } from './validationFunction';
|
||||
|
||||
@ -65,11 +60,6 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
_dirty: boolean = true;
|
||||
_notInserted: boolean = true;
|
||||
|
||||
flags = {
|
||||
submitAction: false,
|
||||
revertAction: false,
|
||||
};
|
||||
|
||||
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
||||
super();
|
||||
this.fyo = markRaw(fyo);
|
||||
@ -469,21 +459,18 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
this._notInserted = true;
|
||||
}
|
||||
|
||||
_setChildIdx() {
|
||||
_setChildDocsIdx() {
|
||||
const childFields = this.schema.fields.filter(
|
||||
(f) => f.fieldtype === FieldTypeEnum.Table
|
||||
) as TargetField[];
|
||||
|
||||
for (const field of childFields) {
|
||||
const childDocs = (this.get(field.fieldname) as Doc[]) ?? [];
|
||||
|
||||
for (let i = 0; i < childDocs.length; i++) {
|
||||
childDocs[i].idx = i;
|
||||
}
|
||||
setChildDocIdx(childDocs);
|
||||
}
|
||||
}
|
||||
|
||||
async _compareWithCurrentDoc() {
|
||||
async _validateDbNotModified() {
|
||||
if (this.notInserted || !this.name || this.schema.isSingle) {
|
||||
return;
|
||||
}
|
||||
@ -499,21 +486,6 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
` ${dbModified}, ${docModified}`
|
||||
);
|
||||
}
|
||||
|
||||
if (this.submitted && !this.schema.isSubmittable) {
|
||||
throw new ValidationError(
|
||||
this.fyo.t`Document type ${this.schemaName} is not submittable`
|
||||
);
|
||||
}
|
||||
|
||||
// set submit action flag
|
||||
if (this.submitted && !dbValues.submitted) {
|
||||
this.flags.submitAction = true;
|
||||
}
|
||||
|
||||
if (dbValues.submitted && !this.submitted) {
|
||||
this.flags.revertAction = true;
|
||||
}
|
||||
}
|
||||
|
||||
async _applyFormula(fieldname?: string): Promise<boolean> {
|
||||
@ -587,17 +559,16 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
return value;
|
||||
}
|
||||
|
||||
async _commit() {
|
||||
// re-run triggers
|
||||
this._setChildIdx();
|
||||
async _preSync() {
|
||||
this._setChildDocsIdx();
|
||||
await this._applyFormula();
|
||||
await this.trigger('validate', null);
|
||||
await this.trigger('validate');
|
||||
}
|
||||
|
||||
async _insert() {
|
||||
await setName(this, this.fyo);
|
||||
this._setBaseMetaValues();
|
||||
await this._commit();
|
||||
await this._preSync();
|
||||
await this._validateInsert();
|
||||
|
||||
const oldName = this.name!;
|
||||
@ -613,24 +584,14 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
async _update() {
|
||||
await this._compareWithCurrentDoc();
|
||||
await this._commit();
|
||||
|
||||
// before submit
|
||||
if (this.flags.submitAction) await this.trigger('beforeSubmit');
|
||||
if (this.flags.revertAction) await this.trigger('beforeRevert');
|
||||
|
||||
// update modifiedBy and modified
|
||||
await this._validateDbNotModified();
|
||||
await this._preSync();
|
||||
this._updateModifiedMetaValues();
|
||||
|
||||
const data = this.getValidDict();
|
||||
await this.fyo.db.update(this.schemaName, data);
|
||||
this._syncValues(data);
|
||||
|
||||
// after submit
|
||||
if (this.flags.submitAction) await this.trigger('afterSubmit');
|
||||
if (this.flags.revertAction) await this.trigger('afterRevert');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -657,29 +618,35 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
this.fyo.doc.observer.trigger(`delete:${this.schemaName}`, this.name);
|
||||
}
|
||||
|
||||
async _submitOrRevert(isSubmit: boolean) {
|
||||
const wasSubmitted = this.submitted;
|
||||
this.submitted = isSubmit;
|
||||
try {
|
||||
await this.sync();
|
||||
} catch (e) {
|
||||
this.submitted = wasSubmitted;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.cancelled = false;
|
||||
await this._submitOrRevert(true);
|
||||
if (!this.schema.isSubmittable || this.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.trigger('beforeSubmit');
|
||||
await this.setAndSync('submitted', true);
|
||||
await this.trigger('afterSubmit');
|
||||
|
||||
this.fyo.doc.observer.trigger(`submit:${this.schemaName}`, this.name);
|
||||
}
|
||||
|
||||
async revert() {
|
||||
await this._submitOrRevert(false);
|
||||
this.fyo.doc.observer.trigger(`revert:${this.schemaName}`, this.name);
|
||||
async cancel() {
|
||||
if (!this.schema.isSubmittable || !this.submitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.trigger('beforeCancel');
|
||||
await this.setAndSync('cancelled', true);
|
||||
await this.trigger('afterCancel');
|
||||
|
||||
this.fyo.doc.observer.trigger(`cancel:${this.schemaName}`, this.name);
|
||||
}
|
||||
|
||||
async rename(newName: string) {
|
||||
if (this.submitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.trigger('beforeRename');
|
||||
await this.fyo.db.rename(this.schemaName, this.name!, newName);
|
||||
this.name = newName;
|
||||
@ -720,14 +687,6 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
return sum;
|
||||
}
|
||||
|
||||
getFrom(schemaName: string, name: string, fieldname: string) {
|
||||
if (name === undefined || fieldname === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.fyo.doc.getCachedValue(schemaName, name, fieldname);
|
||||
}
|
||||
|
||||
async setAndSync(fieldname: string | DocValueMap, value?: DocValue | Doc[]) {
|
||||
await this.set(fieldname, value);
|
||||
return await this.sync();
|
||||
@ -774,8 +733,8 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
async afterDelete() {}
|
||||
async beforeSubmit() {}
|
||||
async afterSubmit() {}
|
||||
async beforeRevert() {}
|
||||
async afterRevert() {}
|
||||
async beforeCancel() {}
|
||||
async afterCancel() {}
|
||||
|
||||
formulas: FormulaMap = {};
|
||||
validations: ValidationMap = {};
|
||||
|
@ -5,15 +5,17 @@ function getPaddedName(prefix: string, next: number, padZeros: number): string {
|
||||
}
|
||||
|
||||
export default class NumberSeries extends Doc {
|
||||
validate() {
|
||||
const current = this.get('current') as number | null;
|
||||
if (current) {
|
||||
this.current = this.get('start');
|
||||
setCurrent() {
|
||||
let current = this.get('current') as number | null;
|
||||
if (!current) {
|
||||
current = this.get('start') as number;
|
||||
}
|
||||
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
async next(schemaName: string) {
|
||||
this.validate();
|
||||
this.setCurrent();
|
||||
|
||||
const exists = await this.checkIfCurrentExists(schemaName);
|
||||
if (!exists) {
|
||||
|
@ -3,7 +3,9 @@ import { Doc } from 'fyo/model/doc';
|
||||
import { DefaultMap, FiltersMap, FormulaMap } from 'fyo/model/types';
|
||||
import { getExchangeRate } from 'models/helpers';
|
||||
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { getIsNullOrUndef } from 'utils';
|
||||
import { Party } from '../Party/Party';
|
||||
import { Payment } from '../Payment/Payment';
|
||||
import { Tax } from '../Tax/Tax';
|
||||
@ -63,7 +65,7 @@ export abstract class Invoice extends Doc {
|
||||
await party.updateOutstandingAmount();
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
async afterCancel() {
|
||||
const paymentRefList = await this.getPayments();
|
||||
for (const paymentFor of paymentRefList) {
|
||||
const paymentReference = paymentFor.parent;
|
||||
@ -89,18 +91,20 @@ export abstract class Invoice extends Doc {
|
||||
}
|
||||
|
||||
async getExchangeRate() {
|
||||
if (!this.currency) return 1.0;
|
||||
if (!this.currency) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
const accountingSettings = await this.fyo.doc.getSingle(
|
||||
'AccountingSettings'
|
||||
const currency = await this.fyo.getValue(
|
||||
ModelNameEnum.SystemSettings,
|
||||
'currency'
|
||||
);
|
||||
const companyCurrency = accountingSettings.currency;
|
||||
if (this.currency === companyCurrency) {
|
||||
if (this.currency === currency) {
|
||||
return 1.0;
|
||||
}
|
||||
return await getExchangeRate({
|
||||
fromCurrency: this.currency!,
|
||||
toCurrency: companyCurrency as string,
|
||||
toCurrency: currency as string,
|
||||
});
|
||||
}
|
||||
|
||||
@ -163,14 +167,28 @@ export abstract class Invoice extends Doc {
|
||||
|
||||
formulas: FormulaMap = {
|
||||
account: {
|
||||
formula: async () =>
|
||||
this.getFrom('Party', this.party!, 'defaultAccount') as string,
|
||||
formula: async () => {
|
||||
return (await this.fyo.getValue(
|
||||
'Party',
|
||||
this.party!,
|
||||
'defaultAccount'
|
||||
)) as string;
|
||||
},
|
||||
dependsOn: ['party'],
|
||||
},
|
||||
currency: {
|
||||
formula: async () =>
|
||||
(this.getFrom('Party', this.party!, 'currency') as string) ||
|
||||
(this.fyo.singles.AccountingSettings!.currency as string),
|
||||
formula: async () => {
|
||||
const currency = (await this.fyo.getValue(
|
||||
'Party',
|
||||
this.party!,
|
||||
'currency'
|
||||
)) as string;
|
||||
|
||||
if (!getIsNullOrUndef(currency)) {
|
||||
return currency;
|
||||
}
|
||||
return this.fyo.singles.SystemSettings!.currency as string;
|
||||
},
|
||||
dependsOn: ['party'],
|
||||
},
|
||||
exchangeRate: { formula: async () => await this.getExchangeRate() },
|
||||
@ -199,6 +217,9 @@ export abstract class Invoice extends Doc {
|
||||
};
|
||||
|
||||
static filters: FiltersMap = {
|
||||
party: (doc: Doc) => ({
|
||||
role: ['in', [doc.isSales ? 'Customer' : 'Supplier', 'Both']],
|
||||
}),
|
||||
account: (doc: Doc) => ({
|
||||
isGroup: false,
|
||||
accountType: doc.isSales ? 'Receivable' : 'Payable',
|
||||
|
@ -17,23 +17,23 @@ export abstract class InvoiceItem extends Doc {
|
||||
|
||||
formulas: FormulaMap = {
|
||||
description: {
|
||||
formula: () =>
|
||||
this.parentdoc!.getFrom(
|
||||
formula: async () =>
|
||||
(await this.fyo.getValue(
|
||||
'Item',
|
||||
this.item as string,
|
||||
'description'
|
||||
) as string,
|
||||
)) as string,
|
||||
dependsOn: ['item'],
|
||||
},
|
||||
rate: {
|
||||
formula: async () => {
|
||||
const baseRate = ((await this.parentdoc!.getFrom(
|
||||
const rate = (await this.fyo.getValue(
|
||||
'Item',
|
||||
this.item as string,
|
||||
'rate'
|
||||
)) || this.fyo.pesa(0)) as Money;
|
||||
)) as undefined | Money;
|
||||
|
||||
return baseRate.div(this.exchangeRate!);
|
||||
return rate ?? this.fyo.pesa(0);
|
||||
},
|
||||
dependsOn: ['item'],
|
||||
},
|
||||
@ -48,25 +48,17 @@ export abstract class InvoiceItem extends Doc {
|
||||
if (this.isSales) {
|
||||
accountType = 'incomeAccount';
|
||||
}
|
||||
return this.parentdoc!.getFrom(
|
||||
'Item',
|
||||
this.item as string,
|
||||
accountType
|
||||
);
|
||||
return this.fyo.getValue('Item', this.item as string, accountType);
|
||||
},
|
||||
dependsOn: ['item'],
|
||||
},
|
||||
tax: {
|
||||
formula: () => {
|
||||
if (this.tax) {
|
||||
return this.tax as string;
|
||||
}
|
||||
|
||||
return this.parentdoc!.getFrom(
|
||||
formula: async () => {
|
||||
return (await this.fyo.getValue(
|
||||
'Item',
|
||||
this.item as string,
|
||||
'tax'
|
||||
) as string;
|
||||
)) as string;
|
||||
},
|
||||
dependsOn: ['item'],
|
||||
},
|
||||
@ -80,8 +72,8 @@ export abstract class InvoiceItem extends Doc {
|
||||
dependsOn: ['item', 'amount', 'rate', 'quantity'],
|
||||
},
|
||||
hsnCode: {
|
||||
formula: () =>
|
||||
this.parentdoc!.getFrom('Item', this.item as string, 'hsnCode'),
|
||||
formula: async () =>
|
||||
await this.fyo.getValue('Item', this.item as string, 'hsnCode'),
|
||||
dependsOn: ['item'],
|
||||
},
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ export class JournalEntry extends Doc {
|
||||
await this.getPosting().post();
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
async afterCancel() {
|
||||
await this.getPosting().postReverse();
|
||||
}
|
||||
|
||||
|
@ -69,14 +69,17 @@ export class Party extends Doc {
|
||||
dependsOn: ['role'],
|
||||
},
|
||||
currency: {
|
||||
formula: async () =>
|
||||
this.fyo.singles.AccountingSettings!.currency as string,
|
||||
formula: async () => this.fyo.singles.SystemSettings!.currency as string,
|
||||
},
|
||||
address: {
|
||||
formula: async () => {
|
||||
const address = this.address as string | undefined;
|
||||
if (address) {
|
||||
return this.getFrom('Address', address, 'addressDisplay') as string;
|
||||
return (await this.fyo.getValue(
|
||||
'Address',
|
||||
address,
|
||||
'addressDisplay'
|
||||
)) as string;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
@ -256,7 +256,7 @@ export class Payment extends Doc {
|
||||
}
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
async afterCancel() {
|
||||
this.updateReferenceOutstandingAmount();
|
||||
const entryList = await this.getPosting();
|
||||
for (const entry of entryList) {
|
||||
@ -391,11 +391,11 @@ export class Payment extends Doc {
|
||||
render(doc) {
|
||||
let status = 'Draft';
|
||||
let color = 'gray';
|
||||
if (doc.submitted === 1) {
|
||||
if (doc.submitted) {
|
||||
color = 'green';
|
||||
status = 'Submitted';
|
||||
}
|
||||
if (doc.cancelled === 1) {
|
||||
if (doc.cancelled) {
|
||||
color = 'red';
|
||||
status = 'Cancelled';
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ export class PaymentFor extends Doc {
|
||||
return this.fyo.pesa(0);
|
||||
}
|
||||
|
||||
const outstandingAmount = this.parentdoc!.getFrom(
|
||||
const outstandingAmount = (await this.fyo.getValue(
|
||||
this.referenceType as string,
|
||||
this.referenceName as string,
|
||||
'outstandingAmount'
|
||||
) as Money;
|
||||
)) as Money;
|
||||
|
||||
if (outstandingAmount) {
|
||||
return outstandingAmount;
|
||||
|
@ -15,6 +15,28 @@
|
||||
"label": "Image",
|
||||
"fieldtype": "AttachImage"
|
||||
},
|
||||
{
|
||||
"fieldname": "role",
|
||||
"label": "Role",
|
||||
"fieldtype": "Select",
|
||||
"default": "Both",
|
||||
"options": [
|
||||
{
|
||||
"value": "Both",
|
||||
"label": "Both"
|
||||
},
|
||||
{
|
||||
"value": "Supplier",
|
||||
"label": "Supplier"
|
||||
},
|
||||
{
|
||||
"value": "Customer",
|
||||
"label": "Customer"
|
||||
}
|
||||
],
|
||||
"readOnly": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "defaultAccount",
|
||||
"label": "Default Account",
|
||||
|
@ -31,14 +31,14 @@ describe('Schema Builder', function () {
|
||||
assert.strictEqual(appSchemaMap.Account.fields?.length, 6);
|
||||
assert.strictEqual(appSchemaMap.JournalEntry.fields?.length, 8);
|
||||
assert.strictEqual(appSchemaMap.JournalEntryAccount.fields?.length, 3);
|
||||
assert.strictEqual(appSchemaMap.Party.fields?.length, 8);
|
||||
assert.strictEqual(appSchemaMap.Party.fields?.length, 9);
|
||||
assert.strictEqual(appSchemaMap.Customer.fields?.length, undefined);
|
||||
assert.strictEqual(regionalSchemaMap.Party.fields?.length, 2);
|
||||
});
|
||||
|
||||
specify('Quick Edit Field Counts', function () {
|
||||
assert.strictEqual(appSchemaMap.Party.quickEditFields?.length, 5);
|
||||
assert.strictEqual(regionalSchemaMap.Party.quickEditFields?.length, 7);
|
||||
assert.strictEqual(regionalSchemaMap.Party.quickEditFields?.length, 8);
|
||||
});
|
||||
});
|
||||
|
||||
@ -48,11 +48,11 @@ describe('Schema Builder', function () {
|
||||
);
|
||||
describe('Regional Combined Schemas', function () {
|
||||
specify('Field Counts', function () {
|
||||
assert.strictEqual(regionalCombined.Party.fields?.length, 10);
|
||||
assert.strictEqual(regionalCombined.Party.fields?.length, 11);
|
||||
});
|
||||
|
||||
specify('Quick Edit Field Counts', function () {
|
||||
assert.strictEqual(regionalSchemaMap.Party.quickEditFields?.length, 7);
|
||||
assert.strictEqual(regionalSchemaMap.Party.quickEditFields?.length, 8);
|
||||
});
|
||||
|
||||
specify('Schema Equality with App Schemas', function () {
|
||||
@ -95,11 +95,11 @@ describe('Schema Builder', function () {
|
||||
});
|
||||
|
||||
specify('Field Counts', function () {
|
||||
assert.strictEqual(abstractCombined.Customer!.fields?.length, 10);
|
||||
assert.strictEqual(abstractCombined.Customer!.fields?.length, 11);
|
||||
});
|
||||
|
||||
specify('Quick Edit Field Counts', function () {
|
||||
assert.strictEqual(abstractCombined.Customer!.quickEditFields?.length, 7);
|
||||
assert.strictEqual(abstractCombined.Customer!.quickEditFields?.length, 8);
|
||||
});
|
||||
|
||||
specify('Schema Equality with App Schemas', function () {
|
||||
|
@ -23,6 +23,7 @@
|
||||
>
|
||||
<input
|
||||
ref="input"
|
||||
spellcheck="false"
|
||||
:class="inputClasses"
|
||||
type="text"
|
||||
:value="linkValue"
|
||||
|
@ -4,6 +4,7 @@
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<input
|
||||
spellcheck="false"
|
||||
ref="input"
|
||||
:class="inputClasses"
|
||||
:type="inputType"
|
||||
|
@ -144,7 +144,7 @@ export default {
|
||||
return (this.maxHeight = '');
|
||||
}
|
||||
|
||||
const rowHeight = this.$refs?.['table-row']?.[0].$el.offsetHeight;
|
||||
const rowHeight = this.$refs?.['table-row']?.[0]?.$el.offsetHeight;
|
||||
if (rowHeight === undefined) {
|
||||
return (this.maxHeight = '');
|
||||
}
|
||||
|
@ -1,46 +1,52 @@
|
||||
<template>
|
||||
<div class="py-4" v-if="pendingInvoices.length">
|
||||
<div class="px-4 text-sm text-gray-600 mb-1">
|
||||
<div class="px-4 text-sm text-gray-600">
|
||||
{{ t`Recent Invoices` }}
|
||||
</div>
|
||||
|
||||
<!-- Invoice List -->
|
||||
<div
|
||||
class="px-4 py-3 border-b hover:bg-gray-100 cursor-pointer"
|
||||
class="px-4 py-3 border-b hover:bg-gray-100 cursor-pointer text-base"
|
||||
v-for="invoice in pendingInvoices"
|
||||
:key="invoice.name"
|
||||
@click="routeToForm(invoice)"
|
||||
>
|
||||
<div class="text-base">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<span class="font-medium">
|
||||
{{ invoice.name }}
|
||||
<!-- Invoice Name & Status -->
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<span class="font-medium">
|
||||
{{ invoice.name }}
|
||||
</span>
|
||||
<span>
|
||||
<component :is="getStatusBadge(invoice)" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Invoice Date & Amount -->
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
{{ fyo.format(invoice.date, getInvoiceField(invoice, 'date')) }}
|
||||
</span>
|
||||
<div>
|
||||
<!-- Paid Amount -->
|
||||
<span class="font-medium text-gray-900">
|
||||
{{
|
||||
fyo.format(
|
||||
amountPaid(invoice),
|
||||
getInvoiceField(invoice, 'baseGrandTotal')
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span>
|
||||
<component :is="getStatusBadge(invoice)" />
|
||||
|
||||
<!-- Outstanding Amount -->
|
||||
<span class="text-gray-600" v-if="!fullyPaid(invoice)">
|
||||
({{
|
||||
fyo.format(
|
||||
invoice.outstandingAmount,
|
||||
getInvoiceField(invoice, 'outstandingAmount')
|
||||
)
|
||||
}})
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
{{ fyo.format(invoice.date, getInvoiceField(invoice, 'date')) }}
|
||||
</span>
|
||||
<div>
|
||||
<span class="font-medium text-gray-900">
|
||||
{{
|
||||
fyo.format(
|
||||
amountPaid(invoice),
|
||||
getInvoiceField(invoice, 'baseGrandTotal')
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span class="text-gray-600" v-if="!fullyPaid(invoice)">
|
||||
({{
|
||||
fyo.format(
|
||||
invoice.outstandingAmount,
|
||||
getInvoiceField(invoice, 'outstandingAmount')
|
||||
)
|
||||
}})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -52,7 +58,7 @@ 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 { routeTo } from 'src/utils/ui';
|
||||
|
||||
export default {
|
||||
name: 'PartyWidget',
|
||||
|
@ -13,18 +13,18 @@
|
||||
</Button>
|
||||
<DropdownWithActions :actions="actions" />
|
||||
<Button
|
||||
v-if="showSave"
|
||||
v-if="doc?.notInserted || doc?.dirty"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="onSaveClick"
|
||||
@click="sync"
|
||||
>
|
||||
{{ t`Save` }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!doc.dirty && !doc.notInserted && !doc?.submitted"
|
||||
v-if="!doc?.dirty && !doc?.notInserted && !doc?.submitted"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="onSubmitClick"
|
||||
@click="submit"
|
||||
>{{ t`Submit` }}</Button
|
||||
>
|
||||
</PageHeader>
|
||||
@ -155,7 +155,7 @@
|
||||
/>
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end">
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end ml-auto">
|
||||
<!-- Subtotal -->
|
||||
<div class="flex justify-between">
|
||||
<div>{{ t`Subtotal` }}</div>
|
||||
@ -267,9 +267,6 @@ export default {
|
||||
address() {
|
||||
return this.printSettings && this.printSettings.getLink('address');
|
||||
},
|
||||
showSave() {
|
||||
return this.doc && (this.doc.notInserted || this.doc.dirty);
|
||||
},
|
||||
actions() {
|
||||
return getActionsForDocument(this.doc);
|
||||
},
|
||||
@ -307,15 +304,11 @@ export default {
|
||||
getField(fieldname) {
|
||||
return fyo.getField(this.schemaName, fieldname);
|
||||
},
|
||||
async onSaveClick() {
|
||||
await this.doc.set(
|
||||
'items',
|
||||
this.doc.items.filter((row) => row.item)
|
||||
);
|
||||
async sync() {
|
||||
return this.doc.sync().catch(this.handleError);
|
||||
},
|
||||
onSubmitClick() {
|
||||
let message =
|
||||
submit() {
|
||||
const message =
|
||||
this.schemaName === ModelNameEnum.SalesInvoice
|
||||
? this.t`Are you sure you want to submit this Sales Invoice?`
|
||||
: this.t`Are you sure you want to submit this Purchase Invoice?`;
|
||||
|
@ -6,7 +6,7 @@
|
||||
<StatusBadge :status="status" />
|
||||
<DropdownWithActions :actions="actions" />
|
||||
<Button
|
||||
v-if="doc.notInserted || doc.dirty"
|
||||
v-if="doc?.notInserted || doc?.dirty"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="sync"
|
||||
@ -120,6 +120,7 @@
|
||||
<div class="flex justify-between text-base m-6 gap-12">
|
||||
<!-- User Remark -->
|
||||
<FormControl
|
||||
v-if="!doc.submitted || doc.userRemark"
|
||||
class="w-1/2 self-end"
|
||||
input-class="bg-gray-100"
|
||||
:df="getField('userRemark')"
|
||||
@ -130,7 +131,9 @@
|
||||
/>
|
||||
|
||||
<!-- Debit and Credit -->
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end font-semibold">
|
||||
<div
|
||||
class="w-1/2 gap-2 flex flex-col self-end font-semibold ml-auto"
|
||||
>
|
||||
<div class="flex justify-between text-green-600">
|
||||
<div>{{ t`Total Debit` }}</div>
|
||||
<div>{{ totalDebit }}</div>
|
||||
@ -158,9 +161,9 @@ import PageHeader from 'src/components/PageHeader';
|
||||
import StatusBadge from 'src/components/StatusBadge';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import {
|
||||
getActionsForDocument,
|
||||
routeTo,
|
||||
showMessageDialog
|
||||
getActionsForDocument,
|
||||
routeTo,
|
||||
showMessageDialog,
|
||||
} from 'src/utils/ui';
|
||||
import { handleErrorWithDialog } from '../errorHandling';
|
||||
|
||||
@ -173,8 +176,8 @@ export default {
|
||||
DropdownWithActions,
|
||||
StatusBadge,
|
||||
FormControl,
|
||||
Table
|
||||
},
|
||||
Table,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
|
@ -48,10 +48,10 @@
|
||||
<script>
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { Verb } from 'fyo/telemetry/types';
|
||||
import Button from 'src/components/Button';
|
||||
import PageHeader from 'src/components/PageHeader';
|
||||
import SearchBar from 'src/components/SearchBar';
|
||||
import TwoColumnForm from 'src/components/TwoColumnForm';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import InvoiceTemplate from 'src/components/SalesInvoice/InvoiceTemplate.vue';
|
||||
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
||||
import { makePDF } from 'src/utils';
|
||||
import { IPC_ACTIONS } from 'utils/messages';
|
||||
|
||||
@ -60,7 +60,6 @@ export default {
|
||||
props: { schemaName: String, name: String },
|
||||
components: {
|
||||
PageHeader,
|
||||
SearchBar,
|
||||
Button,
|
||||
TwoColumnForm,
|
||||
},
|
||||
@ -73,14 +72,11 @@ export default {
|
||||
},
|
||||
async mounted() {
|
||||
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
|
||||
this.printSettings = await fyo.getSingle('PrintSettings');
|
||||
this.printSettings = await fyo.doc.getSingle('PrintSettings');
|
||||
},
|
||||
computed: {
|
||||
meta() {
|
||||
return fyo.getMeta(this.schemaName);
|
||||
},
|
||||
printTemplate() {
|
||||
return this.meta.printTemplate;
|
||||
return InvoiceTemplate
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -130,7 +130,7 @@ function getReportList(): SearchItem[] {
|
||||
}
|
||||
|
||||
function getListViewList(): SearchItem[] {
|
||||
const standardLists = [
|
||||
let schemaNames = [
|
||||
ModelNameEnum.Account,
|
||||
ModelNameEnum.Party,
|
||||
ModelNameEnum.Payment,
|
||||
@ -138,7 +138,12 @@ function getListViewList(): SearchItem[] {
|
||||
ModelNameEnum.PurchaseInvoice,
|
||||
ModelNameEnum.SalesInvoice,
|
||||
ModelNameEnum.Tax,
|
||||
]
|
||||
];
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
schemaNames = Object.keys(fyo.schemaMap) as ModelNameEnum[];
|
||||
}
|
||||
const standardLists = schemaNames
|
||||
.map((s) => fyo.schemaMap[s])
|
||||
.filter((s) => s && !s.isChild && !s.isSingle)
|
||||
.map(
|
||||
|
@ -214,14 +214,10 @@ export async function cancelDocWithPrompt(doc: Doc) {
|
||||
label: t`Yes`,
|
||||
async action() {
|
||||
const entryDoc = await fyo.doc.getDoc(doc.schemaName, doc.name!);
|
||||
entryDoc.cancelled = 1;
|
||||
await entryDoc.sync();
|
||||
entryDoc
|
||||
.revert()
|
||||
.cancel()
|
||||
.then(() => resolve(true))
|
||||
.catch((e) => {
|
||||
handleErrorWithDialog(e, doc);
|
||||
});
|
||||
.catch((e) => handleErrorWithDialog(e, doc));
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -262,7 +258,7 @@ function getCancelAction(doc: Doc): Action {
|
||||
component: {
|
||||
template: '<span class="text-red-700">{{ t`Cancel` }}</span>',
|
||||
},
|
||||
condition: (doc: Doc) => !!(doc.submitted && !doc.cancelled),
|
||||
condition: (doc: Doc) => !doc.cancelled,
|
||||
action: () => {
|
||||
cancelDocWithPrompt(doc).then((res) => {
|
||||
if (res) {
|
||||
|
Loading…
Reference in New Issue
Block a user