mirror of
https://github.com/frappe/books.git
synced 2024-11-08 23:00:56 +00:00
incr: type Transaction and Tax
This commit is contained in:
parent
1cc05d218f
commit
91bf6e03fa
@ -1,25 +1,45 @@
|
||||
import frappe from 'frappe';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export async function getExchangeRate({ fromCurrency, toCurrency, date }) {
|
||||
export async function getExchangeRate({
|
||||
fromCurrency,
|
||||
toCurrency,
|
||||
date,
|
||||
}: {
|
||||
fromCurrency: string;
|
||||
toCurrency: string;
|
||||
date?: string;
|
||||
}) {
|
||||
if (!date) {
|
||||
date = DateTime.local().toISODate();
|
||||
}
|
||||
|
||||
if (!fromCurrency || !toCurrency) {
|
||||
throw new frappe.errors.NotFoundError(
|
||||
'Please provide `fromCurrency` and `toCurrency` to get exchange rate.'
|
||||
);
|
||||
}
|
||||
let cacheKey = `currencyExchangeRate:${date}:${fromCurrency}:${toCurrency}`;
|
||||
let exchangeRate = parseFloat(localStorage.getItem(cacheKey));
|
||||
|
||||
const cacheKey = `currencyExchangeRate:${date}:${fromCurrency}:${toCurrency}`;
|
||||
|
||||
let exchangeRate = 0;
|
||||
if (localStorage) {
|
||||
exchangeRate = parseFloat(
|
||||
localStorage.getItem(cacheKey as string) as string
|
||||
);
|
||||
}
|
||||
|
||||
if (!exchangeRate) {
|
||||
try {
|
||||
let res = await fetch(
|
||||
const res = await fetch(
|
||||
` https://api.vatcomply.com/rates?date=${date}&base=${fromCurrency}&symbols=${toCurrency}`
|
||||
);
|
||||
let data = await res.json();
|
||||
const data = await res.json();
|
||||
exchangeRate = data.rates[toCurrency];
|
||||
localStorage.setItem(cacheKey, exchangeRate);
|
||||
|
||||
if (localStorage) {
|
||||
localStorage.setItem(cacheKey, String(exchangeRate));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(
|
||||
@ -27,5 +47,6 @@ export async function getExchangeRate({ fromCurrency, toCurrency, date }) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return exchangeRate;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { t } from 'frappe';
|
||||
export default {
|
||||
name: 'Tax',
|
||||
label: t`Tax`,
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
keywordFields: ['name'],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: t`Name`,
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
},
|
||||
{
|
||||
fieldname: 'details',
|
||||
label: t`Details`,
|
||||
fieldtype: 'Table',
|
||||
childtype: 'TaxDetail',
|
||||
required: 1,
|
||||
},
|
||||
],
|
||||
quickEditFields: ['details'],
|
||||
};
|
6
models/baseModels/Tax/Tax.ts
Normal file
6
models/baseModels/Tax/Tax.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { ListViewSettings } from 'frappe/model/types';
|
||||
|
||||
export class Tax extends Doc {
|
||||
static listSettings: ListViewSettings = { columns: ['name'] };
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { t } from 'frappe';
|
||||
|
||||
export default {
|
||||
doctype: 'Tax',
|
||||
title: t`Taxes`,
|
||||
columns: ['name'],
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import Badge from '@/components/Badge';
|
||||
import { getInvoiceStatus, openQuickEdit, routeTo } from '@/utils';
|
||||
import frappe, { t } from 'frappe';
|
||||
import utils from '../../../accounting/utils';
|
||||
import { statusColor } from '../../../src/colors';
|
||||
|
||||
export function getStatusColumn() {
|
||||
const statusMap = {
|
||||
Unpaid: t`Unpaid`,
|
||||
Paid: t`Paid`,
|
||||
Draft: t`Draft`,
|
||||
Cancelled: t`Cancelled`,
|
||||
};
|
||||
return {
|
||||
label: t`Status`,
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
render(doc) {
|
||||
const status = getInvoiceStatus(doc);
|
||||
const color = statusColor[status];
|
||||
const label = statusMap[status];
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||
components: { Badge },
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getActions(doctype) {
|
||||
return [
|
||||
{
|
||||
label: t`Make Payment`,
|
||||
condition: (doc) => doc.submitted && doc.outstandingAmount > 0,
|
||||
action: async function makePayment(doc) {
|
||||
let payment = await frappe.getEmptyDoc('Payment');
|
||||
payment.once('afterInsert', async () => {
|
||||
await payment.submit();
|
||||
});
|
||||
let isSales = doctype === 'SalesInvoice';
|
||||
let party = isSales ? doc.customer : doc.supplier;
|
||||
let paymentType = isSales ? 'Receive' : 'Pay';
|
||||
let hideAccountField = isSales ? 'account' : 'paymentAccount';
|
||||
openQuickEdit({
|
||||
doctype: 'Payment',
|
||||
name: payment.name,
|
||||
hideFields: ['party', 'date', hideAccountField, 'paymentType', 'for'],
|
||||
defaults: {
|
||||
party,
|
||||
[hideAccountField]: doc.account,
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
paymentType,
|
||||
for: [
|
||||
{
|
||||
referenceType: doc.doctype,
|
||||
referenceName: doc.name,
|
||||
amount: doc.outstandingAmount,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t`Print`,
|
||||
condition: (doc) => doc.submitted,
|
||||
action(doc) {
|
||||
routeTo(`/print/${doc.doctype}/${doc.name}`);
|
||||
},
|
||||
},
|
||||
utils.ledgerLink,
|
||||
];
|
||||
}
|
||||
|
||||
export default {
|
||||
getStatusColumn,
|
||||
getActions,
|
||||
};
|
144
models/baseModels/Transaction/Transaction.ts
Normal file
144
models/baseModels/Transaction/Transaction.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { LedgerPosting } from 'accounting/ledgerPosting';
|
||||
import frappe from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { getExchangeRate } from '../../../accounting/exchangeRate';
|
||||
import { Party } from '../Party/Party';
|
||||
import { Payment } from '../Payment/Payment';
|
||||
import { Tax } from '../Tax/Tax';
|
||||
|
||||
export abstract class Transaction extends Doc {
|
||||
_taxes: Record<string, Tax> = {};
|
||||
|
||||
abstract getPosting(): LedgerPosting;
|
||||
|
||||
async getPayments() {
|
||||
const payments = await frappe.db.getAll('PaymentFor', {
|
||||
fields: ['parent'],
|
||||
filters: { referenceName: this.name as string },
|
||||
orderBy: 'name',
|
||||
});
|
||||
|
||||
if (payments.length != 0) {
|
||||
return payments;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async beforeUpdate() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
}
|
||||
|
||||
async beforeInsert() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
// post ledger entries
|
||||
const entries = await this.getPosting();
|
||||
await entries.post();
|
||||
|
||||
// update outstanding amounts
|
||||
await frappe.db.update(this.schemaName, {
|
||||
name: this.name as string,
|
||||
outstandingAmount: this.baseGrandTotal as Money,
|
||||
});
|
||||
|
||||
const party = (await frappe.doc.getDoc(
|
||||
'Party',
|
||||
this.party as string
|
||||
)) as Party;
|
||||
await party.updateOutstandingAmount();
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
const paymentRefList = await this.getPayments();
|
||||
for (const paymentFor of paymentRefList) {
|
||||
const paymentReference = paymentFor.parent;
|
||||
const payment = (await frappe.doc.getDoc(
|
||||
'Payment',
|
||||
paymentReference as string
|
||||
)) as Payment;
|
||||
|
||||
const paymentEntries = await payment.getPosting();
|
||||
for (const entry of paymentEntries) {
|
||||
await entry.postReverse();
|
||||
}
|
||||
|
||||
// To set the payment status as unsubmitted.
|
||||
await frappe.db.update('Payment', {
|
||||
name: paymentReference,
|
||||
submitted: false,
|
||||
cancelled: true,
|
||||
});
|
||||
}
|
||||
const entries = await this.getPosting();
|
||||
await entries.postReverse();
|
||||
}
|
||||
|
||||
async getExchangeRate() {
|
||||
if (!this.currency) return 1.0;
|
||||
|
||||
const accountingSettings = await frappe.doc.getSingle('AccountingSettings');
|
||||
const companyCurrency = accountingSettings.currency;
|
||||
if (this.currency === companyCurrency) {
|
||||
return 1.0;
|
||||
}
|
||||
return await getExchangeRate({
|
||||
fromCurrency: this.currency as string,
|
||||
toCurrency: companyCurrency as string,
|
||||
});
|
||||
}
|
||||
|
||||
async getTaxSummary() {
|
||||
const taxes: Record<
|
||||
string,
|
||||
{ account: string; rate: number; amount: Money; baseAmount?: Money }
|
||||
> = {};
|
||||
|
||||
for (const row of this.items as Doc[]) {
|
||||
if (!row.tax) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tax = await this.getTax(row.tax as string);
|
||||
for (const d of tax.details as Doc[]) {
|
||||
const account = d.account as string;
|
||||
const rate = d.rate as number;
|
||||
|
||||
taxes[account] = taxes[account] || {
|
||||
account,
|
||||
rate,
|
||||
amount: frappe.pesa(0),
|
||||
};
|
||||
|
||||
const amount = (row.amount as Money).mul(rate).div(100);
|
||||
taxes[account].amount = taxes[account].amount.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(taxes)
|
||||
.map((account) => {
|
||||
const tax = taxes[account];
|
||||
tax.baseAmount = tax.amount.mul(this.exchangeRate as number);
|
||||
return tax;
|
||||
})
|
||||
.filter((tax) => !tax.amount.isZero());
|
||||
}
|
||||
|
||||
async getTax(tax: string) {
|
||||
if (!this._taxes![tax]) {
|
||||
this._taxes[tax] = await frappe.doc.getDoc('Tax', tax);
|
||||
}
|
||||
|
||||
return this._taxes[tax];
|
||||
}
|
||||
|
||||
async getGrandTotal() {
|
||||
return ((this.taxes ?? []) as Doc[])
|
||||
.map((doc) => doc.amount as Money)
|
||||
.reduce((a, b) => a.add(b), this.netTotal as Money);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
import frappe from 'frappe';
|
||||
import Document from 'frappe/model/document';
|
||||
import { getExchangeRate } from '../../../accounting/exchangeRate';
|
||||
|
||||
export default class TransactionDocument extends Document {
|
||||
async getExchangeRate() {
|
||||
if (!this.currency) return 1.0;
|
||||
|
||||
let accountingSettings = await frappe.getSingle('AccountingSettings');
|
||||
const companyCurrency = accountingSettings.currency;
|
||||
if (this.currency === companyCurrency) {
|
||||
return 1.0;
|
||||
}
|
||||
return await getExchangeRate({
|
||||
fromCurrency: this.currency,
|
||||
toCurrency: companyCurrency,
|
||||
});
|
||||
}
|
||||
|
||||
async getTaxSummary() {
|
||||
let taxes = {};
|
||||
|
||||
for (let row of this.items) {
|
||||
if (!row.tax) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tax = await this.getTax(row.tax);
|
||||
for (let d of tax.details) {
|
||||
taxes[d.account] = taxes[d.account] || {
|
||||
account: d.account,
|
||||
rate: d.rate,
|
||||
amount: frappe.pesa(0),
|
||||
};
|
||||
|
||||
const amount = row.amount.mul(d.rate).div(100);
|
||||
taxes[d.account].amount = taxes[d.account].amount.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(taxes)
|
||||
.map((account) => {
|
||||
const tax = taxes[account];
|
||||
tax.baseAmount = tax.amount.mul(this.exchangeRate);
|
||||
return tax;
|
||||
})
|
||||
.filter((tax) => !tax.amount.isZero());
|
||||
}
|
||||
|
||||
async getTax(tax) {
|
||||
if (!this._taxes) this._taxes = {};
|
||||
if (!this._taxes[tax])
|
||||
this._taxes[tax] = await frappe.doc.getDoc('Tax', tax);
|
||||
return this._taxes[tax];
|
||||
}
|
||||
|
||||
async getGrandTotal() {
|
||||
return (this.taxes || [])
|
||||
.map(({ amount }) => amount)
|
||||
.reduce((a, b) => a.add(b), this.netTotal);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
import frappe from 'frappe';
|
||||
|
||||
export default {
|
||||
async getPayments() {
|
||||
let payments = await frappe.db.getAll({
|
||||
doctype: 'PaymentFor',
|
||||
fields: ['parent'],
|
||||
filters: { referenceName: this.name },
|
||||
orderBy: 'name',
|
||||
});
|
||||
if (payments.length != 0) {
|
||||
return payments;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
async beforeUpdate() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
},
|
||||
|
||||
async beforeInsert() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
},
|
||||
|
||||
async afterSubmit() {
|
||||
// post ledger entries
|
||||
const entries = await this.getPosting();
|
||||
await entries.post();
|
||||
|
||||
// update outstanding amounts
|
||||
await frappe.db.update(this.doctype, {
|
||||
name: this.name,
|
||||
outstandingAmount: this.baseGrandTotal,
|
||||
});
|
||||
|
||||
let party = await frappe.doc.getDoc(
|
||||
'Party',
|
||||
this.customer || this.supplier
|
||||
);
|
||||
await party.updateOutstandingAmount();
|
||||
},
|
||||
|
||||
async afterRevert() {
|
||||
let paymentRefList = await this.getPayments();
|
||||
for (let paymentFor of paymentRefList) {
|
||||
const paymentReference = paymentFor.parent;
|
||||
const payment = await frappe.doc.getDoc('Payment', paymentReference);
|
||||
const paymentEntries = await payment.getPosting();
|
||||
await paymentEntries.postReverse();
|
||||
// To set the payment status as unsubmitted.
|
||||
await frappe.db.update('Payment', {
|
||||
name: paymentReference,
|
||||
submitted: 0,
|
||||
cancelled: 1,
|
||||
});
|
||||
}
|
||||
const entries = await this.getPosting();
|
||||
await entries.postReverse();
|
||||
},
|
||||
};
|
@ -1,7 +1,10 @@
|
||||
import { openQuickEdit } from '@/utils';
|
||||
import frappe from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { Action } from 'frappe/model/types';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { Router } from 'vue-router';
|
||||
import { InvoiceStatus } from './types';
|
||||
|
||||
export function getLedgerLinkAction(): Action {
|
||||
return {
|
||||
@ -22,3 +25,96 @@ export function getLedgerLinkAction(): Action {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getTransactionActions(schemaName: string) {
|
||||
return [
|
||||
{
|
||||
label: frappe.t`Make Payment`,
|
||||
condition: (doc: Doc) =>
|
||||
doc.submitted && (doc.outstandingAmount as Money).gt(0),
|
||||
action: async function makePayment(doc: Doc) {
|
||||
const payment = await frappe.doc.getEmptyDoc('Payment');
|
||||
payment.once('afterInsert', async () => {
|
||||
await payment.submit();
|
||||
});
|
||||
const isSales = schemaName === 'SalesInvoice';
|
||||
const party = isSales ? doc.customer : doc.supplier;
|
||||
const paymentType = isSales ? 'Receive' : 'Pay';
|
||||
const hideAccountField = isSales ? 'account' : 'paymentAccount';
|
||||
|
||||
await openQuickEdit({
|
||||
schemaName: 'Payment',
|
||||
name: payment.name as string,
|
||||
hideFields: ['party', 'date', hideAccountField, 'paymentType', 'for'],
|
||||
defaults: {
|
||||
party,
|
||||
[hideAccountField]: doc.account,
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
paymentType,
|
||||
for: [
|
||||
{
|
||||
referenceType: doc.doctype,
|
||||
referenceName: doc.name,
|
||||
amount: doc.outstandingAmount,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: frappe.t`Print`,
|
||||
condition: (doc: Doc) => doc.submitted,
|
||||
action(doc: Doc, router: Router) {
|
||||
router.push({ path: `/print/${doc.doctype}/${doc.name}` });
|
||||
},
|
||||
},
|
||||
getLedgerLinkAction(),
|
||||
];
|
||||
}
|
||||
|
||||
export function getTransactionStatusColumn() {
|
||||
const statusMap = {
|
||||
Unpaid: frappe.t`Unpaid`,
|
||||
Paid: frappe.t`Paid`,
|
||||
Draft: frappe.t`Draft`,
|
||||
Cancelled: frappe.t`Cancelled`,
|
||||
};
|
||||
|
||||
return {
|
||||
label: frappe.t`Status`,
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
render(doc: Doc) {
|
||||
const status = getInvoiceStatus(doc) as InvoiceStatus;
|
||||
const color = statusColor[status];
|
||||
const label = statusMap[status];
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const statusColor = {
|
||||
Draft: 'gray',
|
||||
Unpaid: 'orange',
|
||||
Paid: 'green',
|
||||
Cancelled: 'red',
|
||||
};
|
||||
|
||||
export function getInvoiceStatus(doc: Doc) {
|
||||
let status = `Unpaid`;
|
||||
if (!doc.submitted) {
|
||||
status = 'Draft';
|
||||
}
|
||||
|
||||
if (doc.submitted && (doc.outstandingAmount as Money).isZero()) {
|
||||
status = 'Paid';
|
||||
}
|
||||
|
||||
if (doc.cancelled) {
|
||||
status = 'Cancelled';
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
@ -50,3 +50,5 @@ export enum DoctypeName {
|
||||
PrintSettings = 'PrintSettings',
|
||||
GetStarted = 'GetStarted',
|
||||
}
|
||||
|
||||
export type InvoiceStatus = 'Draft' | 'Unpaid' | 'Cancelled' | 'Paid';
|
||||
|
@ -1,6 +1,8 @@
|
||||
import frappe from 'frappe';
|
||||
|
||||
export default async function generateTaxes(country) {
|
||||
export type TaxType = 'GST' | 'IGST' | 'Exempt-GST' | 'Exempt-IGST';
|
||||
|
||||
export default async function generateTaxes(country: string) {
|
||||
if (country === 'India') {
|
||||
const GSTs = {
|
||||
GST: [28, 18, 12, 6, 5, 3, 0.25, 0],
|
||||
@ -8,16 +10,16 @@ export default async function generateTaxes(country) {
|
||||
'Exempt-GST': [0],
|
||||
'Exempt-IGST': [0],
|
||||
};
|
||||
let newTax = await frappe.getEmptyDoc('Tax');
|
||||
const newTax = await frappe.doc.getEmptyDoc('Tax');
|
||||
|
||||
for (const type of Object.keys(GSTs)) {
|
||||
for (const percent of GSTs[type]) {
|
||||
for (const percent of GSTs[type as TaxType]) {
|
||||
const name = `${type}-${percent}`;
|
||||
|
||||
// Not cross checking cause hardcoded values.
|
||||
await frappe.db.delete('Tax', name);
|
||||
|
||||
const details = getTaxDetails(type, percent);
|
||||
const details = getTaxDetails(type as TaxType, percent);
|
||||
await newTax.set({ name, details });
|
||||
await newTax.insert();
|
||||
}
|
||||
@ -25,7 +27,7 @@ export default async function generateTaxes(country) {
|
||||
}
|
||||
}
|
||||
|
||||
function getTaxDetails(type, percent) {
|
||||
function getTaxDetails(type: TaxType, percent: number) {
|
||||
if (type === 'GST') {
|
||||
return [
|
||||
{
|
99
src/utils.ts
Normal file
99
src/utils.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import Doc from 'frappe/model/doc';
|
||||
|
||||
export interface QuickEditOptions {
|
||||
schemaName: string;
|
||||
name: string;
|
||||
hideFields?: string[];
|
||||
showFields?: string[];
|
||||
defaults?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function openQuickEdit({
|
||||
schemaName,
|
||||
name,
|
||||
hideFields,
|
||||
showFields,
|
||||
defaults = {},
|
||||
}: QuickEditOptions) {
|
||||
const router = (await import('./router')).default;
|
||||
|
||||
const currentRoute = router.currentRoute.value;
|
||||
const query = currentRoute.query;
|
||||
let method: 'push' | 'replace' = 'push';
|
||||
|
||||
if (query.edit && query.doctype === schemaName) {
|
||||
// replace the current route if we are
|
||||
// editing another document of the same doctype
|
||||
method = 'replace';
|
||||
}
|
||||
if (query.name === name) return;
|
||||
|
||||
const forWhat = (defaults?.for ?? []) as string[];
|
||||
if (forWhat[0] === 'not in') {
|
||||
const purpose = forWhat[1]?.[0];
|
||||
|
||||
defaults = Object.assign({
|
||||
for:
|
||||
purpose === 'sales'
|
||||
? 'purchases'
|
||||
: purpose === 'purchases'
|
||||
? 'sales'
|
||||
: 'both',
|
||||
});
|
||||
}
|
||||
|
||||
if (forWhat[0] === 'not in' && forWhat[1] === 'sales') {
|
||||
defaults = Object.assign({ for: 'purchases' });
|
||||
}
|
||||
|
||||
router[method]({
|
||||
query: {
|
||||
edit: 1,
|
||||
doctype: schemaName,
|
||||
name,
|
||||
showFields: showFields ?? getShowFields(schemaName),
|
||||
hideFields,
|
||||
valueJSON: stringifyCircular(defaults),
|
||||
// @ts-ignore
|
||||
lastRoute: currentRoute,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getShowFields(schemaName: string) {
|
||||
if (schemaName === 'Party') {
|
||||
return ['customer'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function stringifyCircular(
|
||||
obj: Record<string, unknown>,
|
||||
ignoreCircular = false,
|
||||
convertDocument = false
|
||||
) {
|
||||
const cacheKey: string[] = [];
|
||||
const cacheValue: unknown[] = [];
|
||||
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
cacheKey.push(key);
|
||||
cacheValue.push(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (cacheValue.includes(value)) {
|
||||
const circularKey = cacheKey[cacheValue.indexOf(value)] || '{self}';
|
||||
return ignoreCircular ? undefined : `[Circular:${circularKey}]`;
|
||||
}
|
||||
|
||||
cacheKey.push(key);
|
||||
cacheValue.push(value);
|
||||
|
||||
if (convertDocument && value instanceof Doc) {
|
||||
return value.getValidDict();
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user