mirror of
https://github.com/frappe/books.git
synced 2025-01-22 22:58:28 +00:00
fix: from account not found
- add a bunch of Payment validations - add inline form error message display
This commit is contained in:
parent
ef4e934bfd
commit
7c03b98d67
@ -70,7 +70,7 @@ export class DocHandler {
|
||||
return doc;
|
||||
}
|
||||
|
||||
doc = this.getNewDoc(schemaName, { name });
|
||||
doc = this.getNewDoc(schemaName, { name }, false);
|
||||
await doc.load();
|
||||
this.#addToCache(doc);
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { ConflictError, MandatoryError, NotFoundError } from 'fyo/utils/errors';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import { Money } from 'pesa';
|
||||
import {
|
||||
DynamicLinkField,
|
||||
Field,
|
||||
FieldTypeEnum,
|
||||
OptionField,
|
||||
@ -60,7 +61,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
parentFieldname?: string;
|
||||
parentSchemaName?: string;
|
||||
|
||||
_links?: Record<string, Doc>;
|
||||
links?: Record<string, Doc>;
|
||||
_dirty: boolean = true;
|
||||
_notInserted: boolean = true;
|
||||
|
||||
@ -512,41 +513,67 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
async loadLinks() {
|
||||
this._links = {};
|
||||
this.links ??= {};
|
||||
const linkFields = this.schema.fields.filter(
|
||||
(f) => f.fieldtype === FieldTypeEnum.Link || f.inline
|
||||
({ fieldtype }) =>
|
||||
fieldtype === FieldTypeEnum.Link ||
|
||||
fieldtype === FieldTypeEnum.DynamicLink
|
||||
);
|
||||
|
||||
for (const f of linkFields) {
|
||||
await this.loadLink(f.fieldname);
|
||||
for (const field of linkFields) {
|
||||
await this._loadLink(field);
|
||||
}
|
||||
}
|
||||
|
||||
async loadLink(fieldname: string) {
|
||||
this._links ??= {};
|
||||
const field = this.fieldMap[fieldname] as TargetField;
|
||||
if (field === undefined) {
|
||||
async _loadLink(field: Field) {
|
||||
if (field.fieldtype === FieldTypeEnum.Link) {
|
||||
return await this._loadLinkField(field as TargetField);
|
||||
}
|
||||
|
||||
if (field.fieldtype === FieldTypeEnum.DynamicLink) {
|
||||
return await this._loadDynamicLinkField(field as DynamicLinkField);
|
||||
}
|
||||
}
|
||||
|
||||
async _loadLinkField(field: TargetField) {
|
||||
const { fieldname, target } = field;
|
||||
const value = this.get(fieldname) as string | undefined;
|
||||
if (!value || !target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = this.get(fieldname);
|
||||
if (getIsNullOrUndef(value) || field.target === undefined) {
|
||||
await this._loadLinkDoc(fieldname, target, value);
|
||||
}
|
||||
|
||||
async _loadDynamicLinkField(field: DynamicLinkField) {
|
||||
const { fieldname, references } = field;
|
||||
const value = this.get(fieldname) as string | undefined;
|
||||
const reference = this.get(references) as string | undefined;
|
||||
if (!value || !reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._links[fieldname] = await this.fyo.doc.getDoc(
|
||||
field.target,
|
||||
value as string
|
||||
);
|
||||
await this._loadLinkDoc(fieldname, reference, value);
|
||||
}
|
||||
|
||||
async _loadLinkDoc(fieldname: string, schemaName: string, name: string) {
|
||||
this.links![fieldname] = await this.fyo.doc.getDoc(schemaName, name);
|
||||
}
|
||||
|
||||
getLink(fieldname: string): Doc | null {
|
||||
const link = this._links?.[fieldname];
|
||||
if (link === undefined) {
|
||||
return this.links?.[fieldname] ?? null;
|
||||
}
|
||||
|
||||
async loadAndGetLink(fieldname: string): Promise<Doc | null> {
|
||||
if (!this?.[fieldname]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return link;
|
||||
if (this.links?.[fieldname]?.name !== this[fieldname]) {
|
||||
await this.loadLinks();
|
||||
}
|
||||
|
||||
return this.links?.[fieldname] ?? null;
|
||||
}
|
||||
|
||||
async _syncValues(data: DocValueMap) {
|
||||
@ -672,8 +699,8 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
async _applyFormulaForFields(doc: Doc, fieldname?: string) {
|
||||
const formulaFields = Object.keys(this.formulas).map(
|
||||
(fn) => this.fieldMap[fn]
|
||||
const formulaFields = this.schema.fields.filter(
|
||||
({ fieldname }) => this.formulas?.[fieldname]
|
||||
);
|
||||
|
||||
let changed = false;
|
||||
|
@ -15,6 +15,8 @@ import { Money } from 'pesa';
|
||||
import { PartyRole } from './types';
|
||||
|
||||
export class Party extends Doc {
|
||||
role?: PartyRole;
|
||||
defaultAccount?: string;
|
||||
outstandingAmount?: Money;
|
||||
async updateOutstandingAmount() {
|
||||
/**
|
||||
|
@ -23,16 +23,22 @@ import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||
import { Transactional } from 'models/Transactional/Transactional';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { AccountTypeEnum } from '../Account/types';
|
||||
import { Invoice } from '../Invoice/Invoice';
|
||||
import { Party } from '../Party/Party';
|
||||
import { PaymentFor } from '../PaymentFor/PaymentFor';
|
||||
import { PaymentMethod, PaymentType } from './types';
|
||||
|
||||
type AccountTypeMap = Record<AccountTypeEnum, string[] | undefined>;
|
||||
|
||||
export class Payment extends Transactional {
|
||||
party?: string;
|
||||
amount?: Money;
|
||||
writeoff?: Money;
|
||||
paymentType?: PaymentType;
|
||||
for?: PaymentFor[];
|
||||
_accountsMap?: AccountTypeMap;
|
||||
|
||||
async change({ changed }: ChangeArg) {
|
||||
if (changed === 'for') {
|
||||
@ -100,12 +106,44 @@ export class Payment extends Transactional {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.validateFor();
|
||||
this.validateAccounts();
|
||||
this.validateTotalReferenceAmount();
|
||||
this.validateWriteOffAccount();
|
||||
await this.validateReferences();
|
||||
}
|
||||
|
||||
async validateFor() {
|
||||
for (const childDoc of this.for ?? []) {
|
||||
const referenceName = childDoc.referenceName;
|
||||
const referenceType = childDoc.referenceType;
|
||||
|
||||
const refDoc = (await this.fyo.doc.getDoc(
|
||||
childDoc.referenceType!,
|
||||
childDoc.referenceName
|
||||
)) as Invoice;
|
||||
|
||||
if (referenceName && referenceType && !refDoc) {
|
||||
throw new ValidationError(
|
||||
t`${referenceType} of type ${this.fyo.schemaMap?.[referenceType]
|
||||
?.label!} does not exist`
|
||||
);
|
||||
}
|
||||
console.log(refDoc);
|
||||
|
||||
if (!refDoc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (refDoc?.party !== this.party) {
|
||||
throw new ValidationError(
|
||||
t`${refDoc.name!} party ${refDoc.party!} is different from ${this
|
||||
.party!}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateAccounts() {
|
||||
if (this.paymentAccount !== this.account || !this.account) {
|
||||
return;
|
||||
@ -319,46 +357,118 @@ export class Payment extends Transactional {
|
||||
|
||||
static defaults: DefaultMap = { date: () => new Date().toISOString() };
|
||||
|
||||
async _getAccountsMap(): Promise<AccountTypeMap> {
|
||||
if (this._accountsMap) {
|
||||
return this._accountsMap;
|
||||
}
|
||||
|
||||
const accounts = (await this.fyo.db.getAll(ModelNameEnum.Account, {
|
||||
fields: ['name', 'accountType'],
|
||||
filters: {
|
||||
accountType: [
|
||||
'in',
|
||||
[
|
||||
AccountTypeEnum.Bank,
|
||||
AccountTypeEnum.Cash,
|
||||
AccountTypeEnum.Payable,
|
||||
AccountTypeEnum.Receivable,
|
||||
],
|
||||
],
|
||||
},
|
||||
})) as { name: string; accountType: AccountTypeEnum }[];
|
||||
|
||||
return (this._accountsMap = accounts.reduce((acc, ac) => {
|
||||
acc[ac.accountType] ??= [];
|
||||
acc[ac.accountType]!.push(ac.name);
|
||||
return acc;
|
||||
}, {} as AccountTypeMap));
|
||||
}
|
||||
|
||||
async _getReferenceAccount() {
|
||||
const account = await this._getAccountFromParty();
|
||||
if (!account) {
|
||||
return await this._getAccountFromFor();
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
async _getAccountFromParty() {
|
||||
const party = (await this.loadAndGetLink('party')) as Party | null;
|
||||
if (!party || party.role === 'Both') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return party.defaultAccount ?? null;
|
||||
}
|
||||
|
||||
async _getAccountFromFor() {
|
||||
const reference = this?.for?.[0];
|
||||
if (!reference) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const refDoc = (await reference.loadAndGetLink(
|
||||
'referenceName'
|
||||
)) as Invoice | null;
|
||||
|
||||
return (refDoc?.account ?? null) as string | null;
|
||||
}
|
||||
|
||||
formulas: FormulaMap = {
|
||||
account: {
|
||||
formula: async () => {
|
||||
const hasCash = await this.fyo.db.exists(ModelNameEnum.Account, 'Cash');
|
||||
if (
|
||||
this.paymentMethod === 'Cash' &&
|
||||
this.paymentType === 'Pay' &&
|
||||
hasCash
|
||||
) {
|
||||
return 'Cash';
|
||||
const accountsMap = await this._getAccountsMap();
|
||||
if (this.paymentType === 'Receive') {
|
||||
return (
|
||||
(await this._getReferenceAccount()) ??
|
||||
accountsMap[AccountTypeEnum.Receivable]?.[0] ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if (this.paymentMethod === 'Cash') {
|
||||
return accountsMap[AccountTypeEnum.Cash]?.[0] ?? null;
|
||||
}
|
||||
|
||||
if (this.paymentMethod !== 'Cash') {
|
||||
return accountsMap[AccountTypeEnum.Bank]?.[0] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
dependsOn: ['paymentMethod', 'paymentType'],
|
||||
dependsOn: ['paymentMethod', 'paymentType', 'party'],
|
||||
},
|
||||
paymentAccount: {
|
||||
formula: async () => {
|
||||
const hasCash = await this.fyo.db.exists(ModelNameEnum.Account, 'Cash');
|
||||
if (
|
||||
this.paymentMethod === 'Cash' &&
|
||||
this.paymentType === 'Receive' &&
|
||||
hasCash
|
||||
) {
|
||||
return 'Cash';
|
||||
const accountsMap = await this._getAccountsMap();
|
||||
if (this.paymentType === 'Pay') {
|
||||
return (
|
||||
(await this._getReferenceAccount()) ??
|
||||
accountsMap[AccountTypeEnum.Payable]?.[0] ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if (this.paymentMethod === 'Cash') {
|
||||
return accountsMap[AccountTypeEnum.Cash]?.[0] ?? null;
|
||||
}
|
||||
|
||||
if (this.paymentMethod !== 'Cash') {
|
||||
return accountsMap[AccountTypeEnum.Bank]?.[0] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
dependsOn: ['paymentMethod', 'paymentType'],
|
||||
dependsOn: ['paymentMethod', 'paymentType', 'party'],
|
||||
},
|
||||
paymentType: {
|
||||
formula: async () => {
|
||||
if (!this.party) {
|
||||
return;
|
||||
}
|
||||
const partyDoc = await this.fyo.doc.getDoc(
|
||||
ModelNameEnum.Party,
|
||||
this.party
|
||||
);
|
||||
|
||||
const partyDoc = (await this.loadAndGetLink('party')) as Party;
|
||||
if (partyDoc.role === 'Supplier') {
|
||||
return 'Pay';
|
||||
} else if (partyDoc.role === 'Customer') {
|
||||
@ -426,6 +536,18 @@ export class Payment extends Transactional {
|
||||
};
|
||||
|
||||
static filters: FiltersMap = {
|
||||
party: (doc: Doc) => {
|
||||
const paymentType = (doc as Payment).paymentType;
|
||||
if (paymentType === 'Pay') {
|
||||
return { role: ['in', ['Supplier', 'Both']] } as QueryFilter;
|
||||
}
|
||||
|
||||
if (paymentType === 'Receive') {
|
||||
return { role: ['in', ['Customer', 'Both']] } as QueryFilter;
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
numberSeries: () => {
|
||||
return { referenceType: 'Payment' };
|
||||
},
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { t } from 'fyo';
|
||||
import { DocValue } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { FiltersMap, FormulaMap } from 'fyo/model/types';
|
||||
import { FiltersMap, FormulaMap, ValidationMap } from 'fyo/model/types';
|
||||
import { NotFoundError } from 'fyo/utils/errors';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { PartyRoleEnum } from '../Party/types';
|
||||
@ -18,24 +21,37 @@ export class PaymentFor extends Doc {
|
||||
return;
|
||||
}
|
||||
|
||||
const party = this.parentdoc!.party;
|
||||
if (party === undefined) {
|
||||
const party = await this.parentdoc?.loadAndGetLink('party');
|
||||
if (!party) {
|
||||
return ModelNameEnum.SalesInvoice;
|
||||
}
|
||||
|
||||
const role = await this.fyo.getValue(
|
||||
ModelNameEnum.Party,
|
||||
party,
|
||||
'role'
|
||||
);
|
||||
|
||||
if (role === PartyRoleEnum.Supplier) {
|
||||
if (party.role === PartyRoleEnum.Supplier) {
|
||||
return ModelNameEnum.PurchaseInvoice;
|
||||
}
|
||||
|
||||
return ModelNameEnum.SalesInvoice;
|
||||
},
|
||||
},
|
||||
referenceName: {
|
||||
formula: async () => {
|
||||
if (!this.referenceName || !this.referenceType) {
|
||||
return this.referenceName;
|
||||
}
|
||||
|
||||
const exists = await this.fyo.db.exists(
|
||||
this.referenceType,
|
||||
this.referenceName
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.referenceName;
|
||||
},
|
||||
dependsOn: ['referenceType'],
|
||||
},
|
||||
amount: {
|
||||
formula: async () => {
|
||||
if (!this.referenceName) {
|
||||
@ -74,4 +90,24 @@ export class PaymentFor extends Doc {
|
||||
return { ...baseFilters, party };
|
||||
},
|
||||
};
|
||||
|
||||
validations: ValidationMap = {
|
||||
referenceName: async (value: DocValue) => {
|
||||
console.log(value);
|
||||
const exists = await this.fyo.db.exists(
|
||||
this.referenceType!,
|
||||
value as string
|
||||
);
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotFoundError(
|
||||
t`${this.fyo.schemaMap[this.referenceType!]?.label!} ${
|
||||
value as string
|
||||
} does not exist`,
|
||||
false
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export function getInvoiceActions(
|
||||
await openQuickEdit({
|
||||
schemaName: 'Payment',
|
||||
name: payment.name as string,
|
||||
hideFields: ['party', 'date', hideAccountField, 'paymentType', 'for'],
|
||||
hideFields: ['party', 'date', 'paymentType', 'for'],
|
||||
defaults: {
|
||||
party,
|
||||
[hideAccountField]: doc.account,
|
||||
|
@ -27,14 +27,6 @@
|
||||
"fieldtype": "Date",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "From Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentType",
|
||||
"label": "Payment Type",
|
||||
@ -52,15 +44,6 @@
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentAccount",
|
||||
"label": "To Account",
|
||||
"placeholder": "To Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
@ -92,6 +75,23 @@
|
||||
"default": "Cash",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "From Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentAccount",
|
||||
"label": "To Account",
|
||||
"placeholder": "To Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceId",
|
||||
"label": "Ref. / Cheque No.",
|
||||
|
@ -135,7 +135,7 @@ export default {
|
||||
doc.once('afterSync', () => {
|
||||
this.$emit('new-doc', doc);
|
||||
this.$router.back();
|
||||
this.results = []
|
||||
this.results = [];
|
||||
});
|
||||
},
|
||||
async getCreateFilters() {
|
||||
|
@ -4,10 +4,10 @@ export default {
|
||||
props: { doc: Object, printSettings: Object },
|
||||
data: () => ({ party: null, companyAddress: null, partyAddress: null }),
|
||||
async mounted() {
|
||||
await this.printSettings.loadLink('address');
|
||||
await this.printSettings.loadLinks();
|
||||
this.companyAddress = this.printSettings.getLink('address');
|
||||
|
||||
await this.doc.loadLink('party');
|
||||
await this.doc.loadLinks();
|
||||
this.party = this.doc.getLink('party');
|
||||
this.partyAddress = this.party.getLink('address')?.addressDisplay ?? null;
|
||||
|
||||
|
@ -265,7 +265,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.inlineEditDoc.sync();
|
||||
try {
|
||||
await this.inlineEditDoc.sync();
|
||||
} catch (error) {
|
||||
return await handleErrorWithDialog(error, this.inlineEditDoc)
|
||||
}
|
||||
|
||||
await this.onChangeCommon(df, this.inlineEditDoc.name);
|
||||
await this.doc.loadLinks();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user