2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 03:29:00 +00:00
books/models/helpers.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

526 lines
12 KiB
TypeScript
Raw Normal View History

import { Fyo, t } from 'fyo';
import { Doc } from 'fyo/model/doc';
2022-06-14 09:10:46 +00:00
import { Action, ColumnConfig, DocStatus, RenderData } from 'fyo/model/types';
import { DateTime } from 'luxon';
2022-05-23 05:30:54 +00:00
import { Money } from 'pesa';
import { safeParseFloat } from 'utils/index';
import { Router } from 'vue-router';
import {
AccountRootType,
AccountRootTypeEnum,
} from './baseModels/Account/types';
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
2022-11-22 09:12:49 +00:00
import { Invoice } from './baseModels/Invoice/Invoice';
import { StockMovement } from './inventory/StockMovement';
import { StockTransfer } from './inventory/StockTransfer';
2023-05-04 10:45:12 +00:00
import { InvoiceStatus, ModelNameEnum } from './types';
2022-12-01 08:31:23 +00:00
export function getInvoiceActions(
fyo: Fyo,
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
): Action[] {
2022-04-14 08:01:33 +00:00
return [
2022-11-22 09:12:49 +00:00
getMakePaymentAction(fyo),
2022-12-01 08:31:23 +00:00
getMakeStockTransferAction(fyo, schemaName),
2022-11-22 09:12:49 +00:00
getLedgerLinkAction(fyo),
];
}
export function getStockTransferActions(
fyo: Fyo,
schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
): Action[] {
return [
getMakeInvoiceAction(fyo, schemaName),
getLedgerLinkAction(fyo, false),
getLedgerLinkAction(fyo, true),
2023-07-01 07:31:00 +00:00
getMakeReturnDocAction(fyo),
];
}
2022-12-01 08:31:23 +00:00
export function getMakeStockTransferAction(
fyo: Fyo,
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
): Action {
let label = fyo.t`Shipment`;
if (schemaName === ModelNameEnum.PurchaseInvoice) {
label = fyo.t`Purchase Receipt`;
}
2022-11-22 09:12:49 +00:00
return {
2022-12-01 08:31:23 +00:00
label,
2022-11-30 06:59:52 +00:00
group: fyo.t`Create`,
2022-11-22 09:12:49 +00:00
condition: (doc: Doc) => doc.isSubmitted && !!doc.stockNotTransferred,
action: async (doc: Doc) => {
const transfer = await (doc as Invoice).getStockTransfer();
if (!transfer || !transfer.name) {
2022-11-22 09:12:49 +00:00
return;
}
2022-04-14 08:01:33 +00:00
2022-11-22 09:12:49 +00:00
const { routeTo } = await import('src/utils/ui');
const path = `/edit/${transfer.schemaName}/${transfer.name}`;
await routeTo(path);
2022-04-14 08:01:33 +00:00
},
2022-11-22 09:12:49 +00:00
};
}
export function getMakeInvoiceAction(
fyo: Fyo,
schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
): Action {
let label = fyo.t`Sales Invoice`;
if (schemaName === ModelNameEnum.PurchaseReceipt) {
label = fyo.t`Purchase Invoice`;
}
return {
label,
group: fyo.t`Create`,
condition: (doc: Doc) => doc.isSubmitted && !doc.backReference,
action: async (doc: Doc) => {
const invoice = await (doc as StockTransfer).getInvoice();
if (!invoice || !invoice.name) {
return;
}
const { routeTo } = await import('src/utils/ui');
const path = `/edit/${invoice.schemaName}/${invoice.name}`;
await routeTo(path);
},
};
}
2022-11-22 09:12:49 +00:00
export function getMakePaymentAction(fyo: Fyo): Action {
return {
2022-11-30 06:59:52 +00:00
label: fyo.t`Payment`,
group: fyo.t`Create`,
2022-11-22 09:12:49 +00:00
condition: (doc: Doc) =>
doc.isSubmitted && !(doc.outstandingAmount as Money).isZero(),
2023-04-19 07:43:46 +00:00
action: async (doc, router) => {
2022-11-22 09:12:49 +00:00
const payment = (doc as Invoice).getPayment();
if (!payment) {
return;
}
2023-04-19 07:43:46 +00:00
const currentRoute = router.currentRoute.value.fullPath;
2022-11-22 09:12:49 +00:00
payment.once('afterSync', async () => {
await payment.submit();
2023-04-19 07:43:46 +00:00
await doc.load();
await router.push(currentRoute);
2022-11-22 09:12:49 +00:00
});
2023-04-19 07:43:46 +00:00
const hideFields = ['party', 'paymentType', 'for'];
if (doc.schemaName === ModelNameEnum.SalesInvoice) {
hideFields.push('account');
} else {
hideFields.push('paymentAccount');
}
await payment.runFormulas();
2022-11-22 09:12:49 +00:00
const { openQuickEdit } = await import('src/utils/ui');
await openQuickEdit({
doc: payment,
2023-04-19 07:43:46 +00:00
hideFields,
2022-11-22 09:12:49 +00:00
});
},
};
2022-04-14 08:01:33 +00:00
}
export function getLedgerLinkAction(fyo: Fyo, isStock = false): Action {
2022-12-01 08:31:23 +00:00
let label = fyo.t`Accounting Entries`;
let reportClassName: 'GeneralLedger' | 'StockLedger' = 'GeneralLedger';
if (isStock) {
label = fyo.t`Stock Entries`;
reportClassName = 'StockLedger';
}
return {
label,
2022-11-30 06:59:52 +00:00
group: fyo.t`View`,
condition: (doc: Doc) => doc.isSubmitted,
action: async (doc: Doc, router: Router) => {
const route = getLedgerLink(doc, reportClassName);
await router.push(route);
},
};
}
export function getLedgerLink(
doc: Doc,
reportClassName: 'GeneralLedger' | 'StockLedger'
) {
return {
name: 'Report',
params: {
reportClassName,
defaultFilters: JSON.stringify({
referenceType: doc.schemaName,
referenceName: doc.name,
}),
},
};
}
2023-07-01 07:31:00 +00:00
export function getMakeReturnDocAction(fyo: Fyo): Action {
return {
label: fyo.t`Return`,
group: fyo.t`Create`,
condition: (doc: Doc) =>
!!fyo.singles.InventorySettings?.enableStockReturns &&
2023-07-01 07:31:00 +00:00
doc.isSubmitted &&
!doc.isReturn,
action: async (doc: Doc) => {
const returnDoc = await (doc as StockTransfer).getReturnDoc();
if (!returnDoc || !returnDoc.name) {
return;
}
const { routeTo } = await import('src/utils/ui');
const path = `/edit/${doc.schemaName}/${returnDoc.name}`;
await routeTo(path);
},
};
}
export function getTransactionStatusColumn(): ColumnConfig {
2022-04-14 08:01:33 +00:00
return {
label: t`Status`,
2022-04-14 08:01:33 +00:00
fieldname: 'status',
fieldtype: 'Select',
2022-06-14 09:10:46 +00:00
render(doc) {
const status = getDocStatus(doc) as InvoiceStatus;
const color = statusColor[status] ?? 'gray';
const label = getStatusText(status);
2022-06-14 09:10:46 +00:00
2022-04-14 08:01:33 +00:00
return {
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
};
},
};
}
2022-06-14 09:10:46 +00:00
export const statusColor: Record<
DocStatus | InvoiceStatus,
string | undefined
> = {
'': 'gray',
2022-04-14 08:01:33 +00:00
Draft: 'gray',
Unpaid: 'orange',
Paid: 'green',
2022-06-14 09:10:46 +00:00
Saved: 'gray',
NotSaved: 'gray',
Submitted: 'green',
2022-04-14 08:01:33 +00:00
Cancelled: 'red',
Return: 'green',
ReturnIssued: 'green',
2022-04-14 08:01:33 +00:00
};
export function getStatusText(status: DocStatus | InvoiceStatus): string {
switch (status) {
case 'Draft':
return t`Draft`;
case 'Saved':
return t`Saved`;
case 'NotSaved':
return t`Not Saved`;
case 'Submitted':
return t`Submitted`;
case 'Cancelled':
return t`Cancelled`;
case 'Paid':
return t`Paid`;
case 'Unpaid':
return t`Unpaid`;
2023-07-01 07:31:00 +00:00
case 'Return':
return t`Return`;
case 'ReturnIssued':
return t`Return Issued`;
default:
return '';
}
2022-06-14 09:10:46 +00:00
}
export function getDocStatus(
doc?: RenderData | Doc
): DocStatus | InvoiceStatus {
if (!doc) {
return '';
}
if (doc.notInserted) {
return 'Draft';
2022-04-14 08:01:33 +00:00
}
2022-06-14 09:10:46 +00:00
if (doc.dirty) {
return 'NotSaved';
}
if (!doc.schema?.isSubmittable) {
return 'Saved';
}
return getSubmittableDocStatus(doc);
}
function getSubmittableDocStatus(doc: RenderData | Doc) {
if (
[ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice].includes(
doc.schema.name as ModelNameEnum
)
) {
return getInvoiceStatus(doc);
}
2023-07-01 07:31:00 +00:00
if (
[ModelNameEnum.Shipment, ModelNameEnum.PurchaseReceipt].includes(
2023-08-01 05:42:13 +00:00
doc.schema.name as ModelNameEnum
2023-07-01 07:31:00 +00:00
)
) {
2023-08-01 05:42:13 +00:00
if (!!doc.returnAgainst && doc.submitted && !doc.cancelled) {
2023-07-01 07:31:00 +00:00
return 'Return';
}
2023-08-01 05:42:13 +00:00
if (doc.isReturned && doc.submitted && !doc.cancelled) {
2023-07-01 07:31:00 +00:00
return 'ReturnIssued';
}
}
2022-06-14 09:10:46 +00:00
if (!!doc.submitted && !doc.cancelled) {
return 'Submitted';
}
if (!!doc.submitted && !!doc.cancelled) {
return 'Cancelled';
}
return 'Saved';
}
export function getInvoiceStatus(doc: RenderData | Doc): InvoiceStatus {
if (
doc.submitted &&
!doc.cancelled &&
(doc.outstandingAmount as Money).isZero()
) {
return 'Paid';
}
if (
doc.submitted &&
!doc.cancelled &&
(doc.outstandingAmount as Money).isPositive()
) {
return 'Unpaid';
2022-04-14 08:01:33 +00:00
}
if (doc.cancelled) {
2022-06-14 09:10:46 +00:00
return 'Cancelled';
2022-04-14 08:01:33 +00:00
}
2022-06-14 09:10:46 +00:00
return 'Saved';
2022-04-14 08:01:33 +00:00
}
2023-05-04 10:45:12 +00:00
export function getSerialNumberStatusColumn(): ColumnConfig {
2023-04-25 07:07:29 +00:00
return {
label: t`Status`,
fieldname: 'status',
fieldtype: 'Select',
render(doc) {
2023-05-04 10:45:12 +00:00
let status = doc.status;
if (typeof status !== 'string') {
status = 'Inactive';
}
const color = serialNumberStatusColor[status] ?? 'gray';
const label = getSerialNumberStatusText(status);
2023-04-25 07:07:29 +00:00
return {
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
};
},
};
}
2023-05-04 10:45:12 +00:00
export const serialNumberStatusColor: Record<string, string | undefined> = {
2023-04-25 07:07:29 +00:00
Inactive: 'gray',
Active: 'green',
2023-05-04 10:45:12 +00:00
Delivered: 'blue',
2023-04-25 07:07:29 +00:00
};
2023-05-04 10:45:12 +00:00
export function getSerialNumberStatusText(status: string): string {
2023-04-25 07:07:29 +00:00
switch (status) {
case 'Inactive':
return t`Inactive`;
case 'Active':
return t`Active`;
case 'Delivered':
return t`Delivered`;
default:
return t`Inactive`;
}
}
2023-06-06 08:59:08 +00:00
export function getPriceListStatusColumn(): ColumnConfig {
return {
label: t`Enabled For`,
fieldname: 'enabledFor',
fieldtype: 'Select',
2023-06-07 04:45:26 +00:00
render({ isSales, isPurchase }) {
2023-06-06 22:39:46 +00:00
let status = t`None`;
2023-06-06 08:59:08 +00:00
2023-06-07 04:45:26 +00:00
if (isSales && isPurchase) {
status = t`Sales and Purchase`;
} else if (isSales) {
status = t`Sales`;
} else if (isPurchase) {
status = t`Purchase`;
2023-06-06 08:59:08 +00:00
}
return {
template: `<Badge class="text-xs" color="gray">${status}</Badge>`,
};
},
};
}
2023-06-06 22:42:38 +00:00
export function getPriceListEnabledColumn(): ColumnConfig {
return {
label: t`Enabled`,
fieldname: 'enabled',
fieldtype: 'Data',
render(doc) {
2023-06-07 04:45:26 +00:00
let status = t`Disabled`;
2023-06-06 22:42:38 +00:00
let color = 'orange';
2023-06-07 04:45:26 +00:00
if (doc.isEnabled) {
2023-06-06 22:42:38 +00:00
status = t`Enabled`;
color = 'green';
}
return {
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
};
},
};
}
export async function getExchangeRate({
fromCurrency,
toCurrency,
date,
}: {
fromCurrency: string;
toCurrency: string;
date?: string;
}) {
if (!fetch) {
return 1;
}
if (!date) {
date = DateTime.local().toISODate();
}
const cacheKey = `currencyExchangeRate:${date}:${fromCurrency}:${toCurrency}`;
let exchangeRate = 0;
if (localStorage) {
exchangeRate = safeParseFloat(localStorage.getItem(cacheKey) as string);
}
if (exchangeRate && exchangeRate !== 1) {
return exchangeRate;
}
try {
const res = await fetch(
`https://api.vatcomply.com/rates?date=${date}&base=${fromCurrency}&symbols=${toCurrency}`
);
const data = (await res.json()) as {
base: string;
data: string;
rates: Record<string, number>;
};
exchangeRate = data.rates[toCurrency];
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
exchangeRate ??= 1;
}
if (localStorage) {
localStorage.setItem(cacheKey, String(exchangeRate));
}
return exchangeRate;
}
export function isCredit(rootType: AccountRootType) {
switch (rootType) {
case AccountRootTypeEnum.Asset:
return false;
case AccountRootTypeEnum.Liability:
return true;
case AccountRootTypeEnum.Equity:
return true;
case AccountRootTypeEnum.Expense:
return false;
case AccountRootTypeEnum.Income:
return true;
default:
return true;
}
}
2022-10-12 09:29:43 +00:00
export function getNumberSeries(schemaName: string, fyo: Fyo) {
const numberSeriesKey = numberSeriesDefaultsMap[schemaName];
if (!numberSeriesKey) {
return undefined;
}
const defaults = fyo.singles.Defaults;
2022-10-12 09:29:43 +00:00
const field = fyo.getField(schemaName, 'numberSeries');
const value = defaults?.[numberSeriesKey] as string | undefined;
return value ?? (field?.default as string | undefined);
}
export function getDocStatusListColumn(): ColumnConfig {
return {
label: t`Status`,
fieldname: 'status',
fieldtype: 'Select',
render(doc) {
const status = getDocStatus(doc);
const color = statusColor[status] ?? 'gray';
const label = getStatusText(status);
return {
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
};
},
};
}
type ModelsWithItems = Invoice | StockTransfer | StockMovement;
export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
if (!doc.canEdit) {
return;
}
const items = (doc.items ?? []) as NonNullable<M['items']>[number][];
let item = items.find((i) => i.item === name);
if (item) {
const q = item.quantity ?? 0;
await item.set('quantity', q + 1);
return;
}
await doc.append('items');
item = doc.items?.at(-1);
if (!item) {
return;
}
await item.set('item', name);
}