mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
Merge branch 'feature/viewDetail'
This commit is contained in:
@@ -11,7 +11,7 @@ const CheckboxEditableCell = ({
|
||||
const [value, setValue] = React.useState(initialValue);
|
||||
|
||||
const onChange = (e) => {
|
||||
setValue(e.target.value);
|
||||
setValue(e.target.checked);
|
||||
};
|
||||
const onBlur = () => {
|
||||
payload.updateData(index, id, value);
|
||||
@@ -24,12 +24,13 @@ const CheckboxEditableCell = ({
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
// intent={error ? Intent.DANGER : null}
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames(Classes.FILL)}
|
||||
>
|
||||
<Checkbox
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
checked={initialValue}
|
||||
onBlur={onBlur}
|
||||
minimal={true}
|
||||
className="ml2"
|
||||
|
||||
@@ -7,6 +7,10 @@ import AccountDrawer from 'containers/Drawers/AccountDrawer';
|
||||
import ManualJournalDrawer from 'containers/Drawers/ManualJournalDrawer';
|
||||
import ExpenseDrawer from 'containers/Drawers/ExpenseDrawer';
|
||||
import BillDrawer from 'containers/Drawers/BillDrawer';
|
||||
import InvoiceDetailDrawer from 'containers/Drawers/InvoiceDetailDrawer';
|
||||
import ReceiptDetailDrawer from 'containers/Drawers/ReceiptDetailDrawer';
|
||||
import PaymentReceiveDetailDrawer from 'containers/Drawers/PaymentReceiveDetailDrawer';
|
||||
import PaymentMadeDetailDrawer from 'containers/Drawers/PaymentMadeDetailDrawer';
|
||||
|
||||
export default function DrawersContainer() {
|
||||
return (
|
||||
@@ -19,6 +23,10 @@ export default function DrawersContainer() {
|
||||
<ManualJournalDrawer name={'journal-drawer'} />
|
||||
<ExpenseDrawer name={'expense-drawer'} />
|
||||
<BillDrawer name={'bill-drawer'} />
|
||||
<InvoiceDetailDrawer name={'invoice-detail-drawer'} />
|
||||
<ReceiptDetailDrawer name={'receipt-detail-drawer'} />
|
||||
<PaymentReceiveDetailDrawer name={'payment-receive-detail-drawer'} />
|
||||
<PaymentMadeDetailDrawer name={'payment-made-detail-drawer'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/BillDrawer.scss';
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import { BillDrawerProvider } from './BillDrawerProvider';
|
||||
import BillDrawerDetails from './BillDrawerDetails';
|
||||
|
||||
@@ -3,15 +3,24 @@ import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import LocatedLandedCostTable from './LocatedLandedCostTable';
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
|
||||
/**
|
||||
* Bill view details.
|
||||
*/
|
||||
export default function BillDrawerDetails() {
|
||||
const { data } = useBillDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="bill-drawer">
|
||||
<Tabs animate={true} large={true} selectedTabId="landed_cost">
|
||||
<div className="view-detail-drawer">
|
||||
<Tabs animate={true} large={true} defaultSelectedTabId="landed_cost">
|
||||
<Tab title={intl.get('details')} id={'details'} disabled={true} />
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable transactions={data} />}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('located_landed_cost')}
|
||||
id={'landed_cost'}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||
import { useBillLocatedLandedCost } from 'hooks/query';
|
||||
import { useTransactionsByReference } from 'hooks/query';
|
||||
|
||||
const BillDrawerContext = React.createContext();
|
||||
|
||||
@@ -9,6 +10,16 @@ const BillDrawerContext = React.createContext();
|
||||
* Bill drawer provider.
|
||||
*/
|
||||
function BillDrawerProvider({ billId, ...props }) {
|
||||
|
||||
// Handle fetch transaction by reference.
|
||||
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
|
||||
{
|
||||
reference_id: billId,
|
||||
reference_type: 'Bill',
|
||||
},
|
||||
{ enabled: !!billId },
|
||||
);
|
||||
|
||||
// Handle fetch bill located landed cost transaction.
|
||||
const { isLoading: isLandedCostLoading, data: transactions } =
|
||||
useBillLocatedLandedCost(billId, {
|
||||
@@ -19,10 +30,11 @@ function BillDrawerProvider({ billId, ...props }) {
|
||||
const provider = {
|
||||
transactions,
|
||||
billId,
|
||||
data,
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider loading={isLandedCostLoading}>
|
||||
<DashboardInsider loading={isLandedCostLoading || isTransactionLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="bill-drawer"
|
||||
title={intl.get('bill_details')}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Invoice view detail.
|
||||
*/
|
||||
export default function InvoiceDetail() {
|
||||
const { data } = useInvoiceDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="view-detail-drawer">
|
||||
<Tabs
|
||||
animate={true}
|
||||
large={true}
|
||||
defaultSelectedTabId="journal_entries"
|
||||
renderActiveTabPanelOnly={false}
|
||||
>
|
||||
<Tab title={intl.get('details')} disabled={true} />
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable transactions={data} />}
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import InvoiceDetail from './InvoiceDetail';
|
||||
import { InvoiceDetailDrawerProvider } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Invoice detail drawer content.
|
||||
*/
|
||||
export default function InvoiceDetailDrawerContent({
|
||||
// #ownProp
|
||||
invoice,
|
||||
}) {
|
||||
return (
|
||||
<InvoiceDetailDrawerProvider invoiceId={invoice}>
|
||||
<InvoiceDetail />
|
||||
</InvoiceDetailDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||
import { useTransactionsByReference } from 'hooks/query';
|
||||
|
||||
const InvoiceDetailDrawerContext = React.createContext();
|
||||
/**
|
||||
* Invoice detail provider.
|
||||
*/
|
||||
function InvoiceDetailDrawerProvider({ invoiceId, ...props }) {
|
||||
|
||||
// Handle fetch transaction by reference.
|
||||
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
|
||||
{
|
||||
reference_id: invoiceId,
|
||||
reference_type: 'SaleInvoice',
|
||||
},
|
||||
{ enabled: !!invoiceId },
|
||||
);
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
data,
|
||||
};
|
||||
return (
|
||||
<DashboardInsider loading={isTransactionLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="invoice-detail-drawer"
|
||||
title={intl.get('invoice_details')}
|
||||
/>
|
||||
<InvoiceDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const useInvoiceDetailDrawerContext = () =>
|
||||
React.useContext(InvoiceDetailDrawerContext);
|
||||
|
||||
export { InvoiceDetailDrawerProvider, useInvoiceDetailDrawerContext };
|
||||
28
client/src/containers/Drawers/InvoiceDetailDrawer/index.js
Normal file
28
client/src/containers/Drawers/InvoiceDetailDrawer/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const InvoiceDetailDrawerContent = React.lazy(() =>
|
||||
import('./InvoiceDetailDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Invoice Detail drawer.
|
||||
*/
|
||||
function InvoiceDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { invoiceId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<InvoiceDetailDrawerContent invoice={invoiceId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
export default compose(withDrawers())(InvoiceDetailDrawer);
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import PaymentMadeDetails from './PaymentMadeDetails';
|
||||
import { PaymentMadeDetailProvider } from './PaymentMadeDetailProvider';
|
||||
|
||||
/**
|
||||
* Payment made detail content.
|
||||
*/
|
||||
export default function PaymentMadeDetailContent({
|
||||
// #ownProp
|
||||
paymentMade,
|
||||
}) {
|
||||
return (
|
||||
<PaymentMadeDetailProvider paymentMadeId={paymentMade}>
|
||||
<PaymentMadeDetails />
|
||||
</PaymentMadeDetailProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||
import { useTransactionsByReference } from 'hooks/query';
|
||||
|
||||
const PaymentMadeDetailContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Payment made detail provider.
|
||||
*/
|
||||
function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
|
||||
|
||||
// Handle fetch transaction by reference.
|
||||
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
|
||||
{
|
||||
reference_id: paymentMadeId,
|
||||
reference_type: 'paymentMade',
|
||||
},
|
||||
{ enabled: !!paymentMadeId },
|
||||
);
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
data,
|
||||
};
|
||||
return (
|
||||
<DashboardInsider loading={isTransactionLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="payment-made-detail-drawer"
|
||||
title={intl.get('payment_made_details')}
|
||||
/>
|
||||
<PaymentMadeDetailContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const usePaymentMadeDetailContext = () =>
|
||||
React.useContext(PaymentMadeDetailContext);
|
||||
export { PaymentMadeDetailProvider, usePaymentMadeDetailContext };
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
|
||||
/**
|
||||
* payment made view detail.
|
||||
*/
|
||||
export default function PaymentMadeDetails() {
|
||||
return (
|
||||
<div className="view-detail-drawer">
|
||||
<Tabs animate={true} large={true} defaultSelectedTabId="journal_entries">
|
||||
<Tab title={intl.get('details')} id={'details'} disabled={true} />
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable journal={[]} />}
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const PaymentMadeDetailContent = React.lazy(() =>
|
||||
import('./PaymentMadeDetailContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Payment made detail drawer.
|
||||
*/
|
||||
function PaymentMadeDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { paymentMadeId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<PaymentMadeDetailContent paymentMade={paymentMadeId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(PaymentMadeDetailDrawer);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import { usePaymentReceiveDetailContext } from './PaymentReceiveDetailProvider';
|
||||
|
||||
/**
|
||||
* payment receive view detail.
|
||||
*/
|
||||
export default function PaymentReceiveDetail() {
|
||||
const { data } = usePaymentReceiveDetailContext();
|
||||
|
||||
return (
|
||||
<div className="view-detail-drawer">
|
||||
<Tabs animate={true} large={true} defaultSelectedTabId="journal_entries">
|
||||
<Tab title={intl.get('details')} id={'details'} disabled={true} />
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable transactions={data} />}
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import PaymentReceiveDetail from './PaymentReceiveDetail';
|
||||
import { PaymentReceiveDetailProvider } from './PaymentReceiveDetailProvider';
|
||||
|
||||
/**
|
||||
* Payment receive detail content.
|
||||
*/
|
||||
export default function PaymentReceiveDetailContent({
|
||||
// #ownProp
|
||||
paymentReceive,
|
||||
}) {
|
||||
return (
|
||||
<PaymentReceiveDetailProvider paymentReceiveId={paymentReceive}>
|
||||
<PaymentReceiveDetail />
|
||||
</PaymentReceiveDetailProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||
import { useTransactionsByReference } from 'hooks/query';
|
||||
|
||||
const PaymentReceiveDetailContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Payment receive detail provider.
|
||||
*/
|
||||
function PaymentReceiveDetailProvider({ paymentReceiveId, ...props }) {
|
||||
|
||||
// Handle fetch transaction by reference.
|
||||
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
|
||||
{
|
||||
reference_id: paymentReceiveId,
|
||||
reference_type: 'paymentReceive',
|
||||
},
|
||||
{ enabled: !!paymentReceiveId },
|
||||
);
|
||||
|
||||
//provider.
|
||||
const provider = { data };
|
||||
|
||||
return (
|
||||
<DashboardInsider loading={isTransactionLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="payment-receive-detail-drawer"
|
||||
title={intl.get('payment_receive_details')}
|
||||
/>
|
||||
<PaymentReceiveDetailContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const usePaymentReceiveDetailContext = () =>
|
||||
React.useContext(PaymentReceiveDetailContext);
|
||||
|
||||
export { PaymentReceiveDetailProvider, usePaymentReceiveDetailContext };
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const PaymentReceiveDetailContent = React.lazy(() =>
|
||||
import('./PaymentReceiveDetailContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Payment receive detail drawer
|
||||
*/
|
||||
function PaymentReceiveDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { paymentReceiveId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<PaymentReceiveDetailContent paymentReceive={paymentReceiveId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(PaymentReceiveDetailDrawer);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import { useReceiptDetailDrawerContext } from './ReceiptDetailDrawerProvider';
|
||||
|
||||
|
||||
/**
|
||||
* Receipt view detail.
|
||||
*/
|
||||
export default function ReceiptDetail() {
|
||||
const { data } = useReceiptDetailDrawerContext();
|
||||
return (
|
||||
<div className="view-detail-drawer">
|
||||
<Tabs animate={true} large={true} defaultSelectedTabId="journal_entries">
|
||||
<Tab title={intl.get('details')} id={'details'} disabled={true} />
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable transactions={data} />}
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import ReceiptDetail from './ReceiptDetail';
|
||||
import { ReceiptDetailDrawerProvider } from './ReceiptDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Receipt detail drawer content.
|
||||
*/
|
||||
export default function ReceiptDetailDrawerContent({
|
||||
// #ownProp
|
||||
receipt,
|
||||
}) {
|
||||
return (
|
||||
<ReceiptDetailDrawerProvider receiptId={receipt}>
|
||||
<ReceiptDetail />
|
||||
</ReceiptDetailDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||
import { useTransactionsByReference } from 'hooks/query';
|
||||
|
||||
// useTransactionsByReference
|
||||
const ReceiptDetailDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Receipt detail provider.
|
||||
*/
|
||||
function ReceiptDetailDrawerProvider({ receiptId, ...props }) {
|
||||
// Handle fetch transaction by reference.
|
||||
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
|
||||
{
|
||||
reference_id: receiptId,
|
||||
reference_type: 'SaleReceipt',
|
||||
},
|
||||
{ enabled: !!receiptId },
|
||||
);
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
data,
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider loading={isTransactionLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="receipt-detail-drawer"
|
||||
title={intl.get('receipt_details')}
|
||||
/>
|
||||
<ReceiptDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
const useReceiptDetailDrawerContext = () =>
|
||||
React.useContext(ReceiptDetailDrawerContext);
|
||||
|
||||
export { ReceiptDetailDrawerProvider, useReceiptDetailDrawerContext };
|
||||
29
client/src/containers/Drawers/ReceiptDetailDrawer/index.js
Normal file
29
client/src/containers/Drawers/ReceiptDetailDrawer/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ReceiptDetailDrawerContent = React.lazy(() =>
|
||||
import('./ReceiptDetailDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Receipt Detail drawer.
|
||||
*/
|
||||
function ReceiptDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { receiptId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<ReceiptDetailDrawerContent receipt={receiptId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(ReceiptDetailDrawer);
|
||||
@@ -79,10 +79,7 @@ function ExpenseForm({
|
||||
}
|
||||
const categories = values.categories.filter(
|
||||
(category) =>
|
||||
category.amount &&
|
||||
category.index &&
|
||||
category.expense_account_id &&
|
||||
category.landed_cost,
|
||||
category.amount && category.index && category.expense_account_id,
|
||||
);
|
||||
|
||||
const form = {
|
||||
|
||||
@@ -31,7 +31,7 @@ export const defaultExpenseEntry = {
|
||||
amount: '',
|
||||
expense_account_id: '',
|
||||
description: '',
|
||||
landed_cost: false,
|
||||
landed_cost: 0,
|
||||
};
|
||||
|
||||
export const defaultExpense = {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { DataTable, Card } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Journal entries table.
|
||||
*/
|
||||
export default function JournalEntriesTable({ transactions }) {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: 'date',
|
||||
// accessor: (r) => moment(new Date()).format('YYYY MMM DD'),
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: intl.get('account_name'),
|
||||
accessor: 'accountName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: intl.get('contact'),
|
||||
accessor: 'contactTypeFormatted',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: ({ credit }) => credit.formattedAmount,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: ({ debit }) => debit.formattedAmount,
|
||||
width: 100,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={transactions}
|
||||
className={'datatable--journal-entries'}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
import withPaymentMade from './withPaymentMade';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
import { usePaymentMadesTableColumns, ActionsMenu } from './components';
|
||||
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
|
||||
|
||||
@@ -27,6 +28,9 @@ function PaymentMadesTable({
|
||||
|
||||
// #withAlerts
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
}) {
|
||||
// Payment mades table columns.
|
||||
const columns = usePaymentMadesTableColumns();
|
||||
@@ -53,6 +57,11 @@ function PaymentMadesTable({
|
||||
openAlert('payment-made-delete', { paymentMadeId: paymentMade.id });
|
||||
};
|
||||
|
||||
// Handle view detail payment made.
|
||||
const handleViewDetailPaymentMade = ({ id }) => {
|
||||
openDrawer('payment-receive-detail-drawer', { paymentMadeId: id });
|
||||
};
|
||||
|
||||
// Handle datatable fetch data once the table state change.
|
||||
const handleDataTableFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
@@ -89,6 +98,7 @@ function PaymentMadesTable({
|
||||
payload={{
|
||||
onEdit: handleEditPaymentMade,
|
||||
onDelete: handleDeletePaymentMade,
|
||||
onViewDetails: handleViewDetailPaymentMade,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -98,6 +108,7 @@ export default compose(
|
||||
withPaymentMadeActions,
|
||||
withPaymentMade(({ paymentMadesTableState }) => ({ paymentMadesTableState })),
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
withSettings(({ organizationSettings }) => ({
|
||||
baseCurrency: organizationSettings?.baseCurrency,
|
||||
})),
|
||||
|
||||
@@ -26,15 +26,14 @@ export function AmountAccessor(row) {
|
||||
*/
|
||||
export function ActionsMenu({
|
||||
row: { original },
|
||||
payload: { onEdit, onDelete },
|
||||
payload: { onEdit, onDelete, onViewDetails },
|
||||
}) {
|
||||
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
@@ -70,8 +69,6 @@ export function ActionsCell(props) {
|
||||
* Retrieve payment mades table columns.
|
||||
*/
|
||||
export function usePaymentMadesTableColumns() {
|
||||
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
|
||||
@@ -79,6 +79,12 @@ function InvoicesDataTable({
|
||||
const handleQuickPaymentReceive = ({ id }) => {
|
||||
openDialog('quick-payment-receive', { invoiceId: id });
|
||||
};
|
||||
|
||||
// Handle view detail invoice.
|
||||
const handleViewDetailInvoice = ({ id }) => {
|
||||
openDrawer('invoice-detail-drawer', { invoiceId: id });
|
||||
};
|
||||
|
||||
// Handles fetch data once the table state change.
|
||||
const handleDataTableFetchData = useCallback(
|
||||
({ pageSize, pageIndex, sortBy }) => {
|
||||
@@ -123,6 +129,7 @@ function InvoicesDataTable({
|
||||
onEdit: handleEditInvoice,
|
||||
onDrawer: handleDrawerInvoice,
|
||||
onQuick: handleQuickPaymentReceive,
|
||||
onViewDetails: handleViewDetailInvoice,
|
||||
baseCurrency,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -95,7 +95,7 @@ export const handleDeleteErrors = (errors) => {
|
||||
};
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onEdit, onDeliver, onDelete, onDrawer, onQuick },
|
||||
payload: { onEdit, onDeliver, onDelete, onDrawer, onQuick, onViewDetails },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
@@ -103,6 +103,7 @@ export function ActionsMenu({
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
|
||||
@@ -62,6 +62,11 @@ function PaymentReceivesDataTable({
|
||||
openDrawer('payment-receive-drawer', { paymentReceiveId: id });
|
||||
};
|
||||
|
||||
// Handle view detail payment receive..
|
||||
const handleViewDetailPaymentReceive = ({ id }) => {
|
||||
openDrawer('payment-receive-detail-drawer', { paymentReceiveId: id });
|
||||
};
|
||||
|
||||
// Handle datatable fetch once the table's state changing.
|
||||
const handleDataTableFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
@@ -103,6 +108,7 @@ function PaymentReceivesDataTable({
|
||||
onDelete: handleDeletePaymentReceive,
|
||||
onEdit: handleEditPaymentReceive,
|
||||
onDrawer: handleDrawerPaymentReceive,
|
||||
onViewDetails: handleViewDetailPaymentReceive,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,15 +19,14 @@ import { safeCallback } from 'utils';
|
||||
*/
|
||||
export function ActionsMenu({
|
||||
row: { original: paymentReceive },
|
||||
payload: { onEdit, onDelete, onDrawer },
|
||||
payload: { onEdit, onDelete, onDrawer, onViewDetails },
|
||||
}) {
|
||||
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, paymentReceive)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
@@ -82,8 +81,6 @@ export function ActionsCell(props) {
|
||||
* Retrieve payment receives columns.
|
||||
*/
|
||||
export function usePaymentReceivesColumns() {
|
||||
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -128,7 +125,7 @@ export function usePaymentReceivesColumns() {
|
||||
accessor: 'reference_no',
|
||||
width: 140,
|
||||
className: 'reference_no',
|
||||
}
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -60,14 +60,19 @@ function ReceiptsDataTable({
|
||||
openAlert('receipt-delete', { receiptId: receipt.id });
|
||||
};
|
||||
|
||||
// Handle drawer receipts.
|
||||
const handleDrawerReceipt = ({ id }) => {
|
||||
openDrawer('receipt-drawer', { receiptId: id });
|
||||
};
|
||||
|
||||
// Handles receipt close action.
|
||||
const handleCloseReceipt = (receipt) => {
|
||||
openAlert('receipt-close', { receiptId: receipt.id });
|
||||
};
|
||||
|
||||
// Handle drawer receipts.
|
||||
const handleDrawerReceipt = ({ id }) => {
|
||||
openDrawer('receipt-drawer', { receiptId: id });
|
||||
// Handle view detail receipt.
|
||||
const handleViewDetailReceipt = ({ id }) => {
|
||||
openDrawer('receipt-detail-drawer', { receiptId: id });
|
||||
};
|
||||
|
||||
// Handles the datable fetch data once the state changing.
|
||||
@@ -112,6 +117,7 @@ function ReceiptsDataTable({
|
||||
onDelete: handleDeleteReceipt,
|
||||
onClose: handleCloseReceipt,
|
||||
onDrawer: handleDrawerReceipt,
|
||||
onViewDetails: handleViewDetailReceipt,
|
||||
baseCurrency,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -16,16 +16,15 @@ import { Choose, Money, Icon, If } from 'components';
|
||||
import moment from 'moment';
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onEdit, onDelete, onClose, onDrawer },
|
||||
payload: { onEdit, onDelete, onClose, onDrawer, onViewDetails },
|
||||
row: { original: receipt },
|
||||
}) {
|
||||
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, receipt)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
@@ -94,8 +93,6 @@ export function StatusAccessor(receipt) {
|
||||
* Retrieve receipts table columns.
|
||||
*/
|
||||
export function useReceiptsTableColumns() {
|
||||
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
|
||||
@@ -466,3 +466,22 @@ export function useInventoryItemDetailsReport(query, props) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions by reference report.
|
||||
*/
|
||||
export function useTransactionsByReference(query, props) {
|
||||
return useRequestQuery(
|
||||
[t.TRANSACTIONS_BY_REFERENCE, query],
|
||||
{
|
||||
method: 'get',
|
||||
url: `/financial_statements/transactions-by-reference`,
|
||||
params: query,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ const FINANCIAL_REPORTS = {
|
||||
INVENTORY_VALUATION: 'INVENTORY_VALUATION',
|
||||
CASH_FLOW_STATEMENT: 'CASH_FLOW_STATEMENT',
|
||||
INVENTORY_ITEM_DETAILS: 'INVENTORY_ITEM_DETAILS',
|
||||
TRANSACTIONS_BY_REFERENCE: 'TRANSACTIONS_BY_REFERENCE',
|
||||
};
|
||||
|
||||
const BILLS = {
|
||||
|
||||
@@ -1166,5 +1166,11 @@
|
||||
"From transaction": "From transaction",
|
||||
"Landed": "Landed",
|
||||
"This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.": "This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.",
|
||||
"Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?": "Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?"
|
||||
"Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?": "Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?",
|
||||
"journal_entries":"Journal entries",
|
||||
"contact":"Contact",
|
||||
"invoice_details":"Invoice details",
|
||||
"receipt_details":"Receipt details",
|
||||
"payment_receive_details":"Payment receive details",
|
||||
"payment_made_details":"Payment made details"
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import t from 'store/types';
|
||||
const getSubscriptionPlans = () => [
|
||||
{
|
||||
name: intl.get('Starter'),
|
||||
slug: 'starter',
|
||||
slug: 'free',
|
||||
// slug: 'starter',
|
||||
description: [
|
||||
intl.get('Sale and purchase invoices.'),
|
||||
intl.get('Customers/vendors accounts.'),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
@import '../../Base.scss';
|
||||
@import '../../../Base.scss';
|
||||
|
||||
.bill-drawer {
|
||||
.view-detail-drawer {
|
||||
.bp3-tabs {
|
||||
.bp3-tab-list {
|
||||
position: relative;
|
||||
background-color: #FFF;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -31,10 +31,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-tab-panel{
|
||||
.bp3-tab-panel {
|
||||
margin-top: 0;
|
||||
|
||||
.card{
|
||||
.card {
|
||||
margin: 15px;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@
|
||||
|
||||
.datatable--landed-cost-transactions {
|
||||
.table {
|
||||
|
||||
.tbody,
|
||||
.tbody-inner {
|
||||
height: auto;
|
||||
@@ -55,11 +54,11 @@
|
||||
.tr .td {
|
||||
padding: 0.6rem;
|
||||
|
||||
&.amount{
|
||||
&.amount {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,9 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
router.get(
|
||||
'/transactions',
|
||||
[
|
||||
query('account_id').optional().isInt().toInt(),
|
||||
],
|
||||
[query('account_id').optional().isInt().toInt()],
|
||||
this.asyncMiddleware(this.accountTransactions.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/activate',
|
||||
@@ -136,6 +134,8 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
query('column_sort_by').optional(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -213,7 +213,9 @@ export default class AccountsController extends BaseController {
|
||||
tenantId,
|
||||
accountId
|
||||
);
|
||||
return res.status(200).send({ account: this.transfromToResponse(account) });
|
||||
return res
|
||||
.status(200)
|
||||
.send({ account: this.transfromToResponse(account) });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -256,7 +258,7 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
return res.status(200).send({
|
||||
id: accountId,
|
||||
message: 'The account has been activated successfully.'
|
||||
message: 'The account has been activated successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -291,22 +293,24 @@ export default class AccountsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Response}
|
||||
*/
|
||||
async getAccountsList(req: Request, res: Response, next: NextFunction) {
|
||||
public async getAccountsList(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter: IAccountsFilter = {
|
||||
filterRoles: [],
|
||||
|
||||
// Filter query.
|
||||
const filter = {
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'name',
|
||||
inactiveMode: false,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
accounts,
|
||||
filterMeta,
|
||||
} = await this.accountsService.getAccountsList(tenantId, filter);
|
||||
const { accounts, filterMeta } =
|
||||
await this.accountsService.getAccountsList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
accounts: this.transfromToResponse(accounts, 'accountTypeLabel', req),
|
||||
@@ -343,9 +347,9 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve accounts transactions list.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns {Response}
|
||||
*/
|
||||
async accountTransactions(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -353,10 +357,11 @@ export default class AccountsController extends BaseController {
|
||||
const transactionsFilter = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const { transactions } = await this.accountsService.getAccountsTransactions(
|
||||
tenantId,
|
||||
transactionsFilter
|
||||
);
|
||||
const { transactions } =
|
||||
await this.accountsService.getAccountsTransactions(
|
||||
tenantId,
|
||||
transactionsFilter
|
||||
);
|
||||
return res.status(200).send({
|
||||
transactions: this.transfromToResponse(transactions),
|
||||
});
|
||||
@@ -372,7 +377,12 @@ export default class AccountsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {ServiceError} error
|
||||
*/
|
||||
catchServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
||||
private catchServiceErrors(
|
||||
error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'account_not_found') {
|
||||
return res.boom.notFound('The given account not found.', {
|
||||
|
||||
@@ -120,6 +120,8 @@ export default class CustomersController extends ContactsController {
|
||||
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
|
||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -264,17 +266,15 @@ export default class CustomersController extends ContactsController {
|
||||
*/
|
||||
async getCustomersList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
inactiveMode: false,
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
|
||||
@@ -100,6 +100,8 @@ export default class VendorsController extends ContactsController {
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
|
||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -227,8 +229,13 @@ export default class VendorsController extends ContactsController {
|
||||
*/
|
||||
async getVendorsList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const vendorsFilter: IVendorsFilter = {
|
||||
filterRoles: [],
|
||||
inactiveMode: false,
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
|
||||
|
||||
@@ -290,16 +290,12 @@ export default class ExpensesController extends BaseController {
|
||||
async getExpensesList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const { expenses, pagination, filterMeta } =
|
||||
|
||||
@@ -17,6 +17,7 @@ import TransactionsByCustomers from './FinancialStatements/TransactionsByCustome
|
||||
import TransactionsByVendors from './FinancialStatements/TransactionsByVendors';
|
||||
import CashFlowStatementController from './FinancialStatements/CashFlow/CashFlow';
|
||||
import InventoryDetailsController from './FinancialStatements/InventoryDetails';
|
||||
import TransactionsByReferenceController from './FinancialStatements/TransactionsByReference';
|
||||
|
||||
@Service()
|
||||
export default class FinancialStatementsService {
|
||||
@@ -87,6 +88,10 @@ export default class FinancialStatementsService {
|
||||
'/inventory-item-details',
|
||||
Container.get(InventoryDetailsController).router(),
|
||||
);
|
||||
router.use(
|
||||
'/transactions-by-reference',
|
||||
Container.get(TransactionsByReferenceController).router(),
|
||||
)
|
||||
return router;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,8 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
||||
return [
|
||||
query('from_date').optional().isISO8601(),
|
||||
query('to_date').optional().isISO8601(),
|
||||
oneOf(
|
||||
[
|
||||
query('transaction_types').optional().isArray({ min: 1 }),
|
||||
query('transaction_types.*').optional().isNumeric().toInt(),
|
||||
],
|
||||
[query('transaction_types').optional().trim().escape()]
|
||||
),
|
||||
query('transaction_type').optional().trim().escape(),
|
||||
query('transaction_id').optional().isInt().toInt(),
|
||||
oneOf(
|
||||
[
|
||||
query('account_ids').optional().isArray({ min: 1 }),
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { query, ValidationChain } from 'express-validator';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import TransactionsByReferenceService from 'services/FinancialStatements/TransactionsByReference';
|
||||
|
||||
@Service()
|
||||
export default class TransactionsByReferenceController extends BaseController {
|
||||
@Inject()
|
||||
private transactionsByReferenceService: TransactionsByReferenceService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
this.validationSchema,
|
||||
this.validationResult,
|
||||
this.asyncMiddleware(this.transactionsByReference.bind(this))
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation schema.
|
||||
*/
|
||||
get validationSchema(): ValidationChain[] {
|
||||
return [
|
||||
query('reference_id').exists().isInt(),
|
||||
query('reference_type').exists().isString(),
|
||||
|
||||
query('number_format.precision')
|
||||
.optional()
|
||||
.isInt({ min: 0, max: 5 })
|
||||
.toInt(),
|
||||
query('number_format.divide_on_1000').optional().isBoolean().toBoolean(),
|
||||
query('number_format.negative_format')
|
||||
.optional()
|
||||
.isIn(['parentheses', 'mines'])
|
||||
.trim()
|
||||
.escape(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions by the given reference type and id.
|
||||
* @param {Request} req - Request object.
|
||||
* @param {Response} res - Response.
|
||||
* @param {NextFunction} next
|
||||
* @returns
|
||||
*/
|
||||
public async transactionsByReference(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const transactions =
|
||||
await this.transactionsByReferenceService.getTransactionsByReference(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
const accept = this.accepts(req);
|
||||
const acceptType = accept.types(['json']);
|
||||
|
||||
switch (acceptType) {
|
||||
case 'json':
|
||||
default:
|
||||
return res
|
||||
.status(200)
|
||||
.send(this.transformToJsonResponse(transactions));
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
private transformToJsonResponse(transactions) {
|
||||
return transactions;
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,8 @@ export default class InventoryAdjustmentsController extends BaseController {
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -199,8 +199,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
*/
|
||||
async getList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId, user } = req;
|
||||
|
||||
const itemCategoriesFilter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
...this.matchedQueryData(req),
|
||||
|
||||
@@ -185,6 +185,8 @@ export default class ItemsController extends BaseController {
|
||||
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
|
||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -339,17 +341,16 @@ export default class ItemsController extends BaseController {
|
||||
*/
|
||||
async getItemsList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
inactiveMode: false,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
items,
|
||||
|
||||
@@ -288,14 +288,10 @@ export default class ManualJournalsController extends BaseController {
|
||||
const filter = {
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
filterRoles: [],
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
try {
|
||||
const {
|
||||
manualJournals,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query } from 'express-validator';
|
||||
import * as R from 'ramda';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { IBillDTO, IBillEditDTO } from 'interfaces';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
@@ -300,14 +301,11 @@ export default class BillsController extends BaseController {
|
||||
const filter = {
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const { bills, pagination, filterMeta } =
|
||||
await this.billsService.getBills(tenantId, filter);
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import { param, query } from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BaseController from './BaseController';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import ResourceService from 'services/Resource/ResourceService';
|
||||
|
||||
@Service()
|
||||
export default class ResourceController extends BaseController{
|
||||
export default class ResourceController extends BaseController {
|
||||
@Inject()
|
||||
resourcesService: ResourceService;
|
||||
|
||||
@@ -21,42 +18,76 @@ export default class ResourceController extends BaseController{
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/:resource_model/fields', [
|
||||
...this.resourceModelParamSchema,
|
||||
],
|
||||
'/:resource_model/meta',
|
||||
[...this.resourceModelParamSchema],
|
||||
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:resource_model/fields',
|
||||
[...this.resourceModelParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.resourceFields.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:resource_model/data', [
|
||||
...this.resourceModelParamSchema,
|
||||
],
|
||||
'/:resource_model/data',
|
||||
[...this.resourceModelParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.resourceData.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
)
|
||||
this.handleServiceErrors
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
get resourceModelParamSchema() {
|
||||
return [
|
||||
param('resource_model').exists().trim().escape(),
|
||||
];
|
||||
return [param('resource_model').exists().trim().escape()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve resource model meta.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
* @returns {Response}
|
||||
*/
|
||||
private resourceMeta = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Response => {
|
||||
const { tenantId } = req;
|
||||
const { resource_model: resourceModel } = req.params;
|
||||
|
||||
try {
|
||||
const resourceMeta = this.resourcesService.getResourceMeta(
|
||||
tenantId,
|
||||
resourceModel
|
||||
);
|
||||
return res
|
||||
.status(200)
|
||||
.send({ resource_meta: this.transfromToResponse(resourceMeta) });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve resource fields of the given resource.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
resourceFields(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { resource_model: resourceModel } = req.params;
|
||||
|
||||
try {
|
||||
const resourceFields = this.resourcesService.getResourceFields(tenantId, resourceModel);
|
||||
const resourceFields = this.resourcesService.getResourceFields(
|
||||
tenantId,
|
||||
resourceModel
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
resource_fields: this.transfromToResponse(resourceFields),
|
||||
@@ -68,9 +99,9 @@ export default class ResourceController extends BaseController{
|
||||
|
||||
/**
|
||||
* Retrieve resource data of the give resource based on the given query.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async resourceData(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
@@ -78,7 +109,11 @@ export default class ResourceController extends BaseController{
|
||||
const filter = req.query;
|
||||
|
||||
try {
|
||||
const resourceData = await this.resourcesService.getResourceData(tenantId, resourceModel, filter);
|
||||
const resourceData = await this.resourcesService.getResourceData(
|
||||
tenantId,
|
||||
resourceModel,
|
||||
filter
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
resource_data: this.transfromToResponse(resourceData),
|
||||
@@ -90,12 +125,17 @@ export default class ResourceController extends BaseController{
|
||||
|
||||
/**
|
||||
* Handles service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
private handleServiceErrors(
|
||||
error: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
||||
return res.status(400).send({
|
||||
@@ -105,4 +145,4 @@ export default class ResourceController extends BaseController{
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -261,16 +261,12 @@ export default class PaymentReceivesController extends BaseController {
|
||||
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleEstimateDTO } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController'
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { ServiceError } from "exceptions";
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
@Service()
|
||||
export default class SalesEstimatesController extends BaseController {
|
||||
@@ -23,63 +23,56 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/', [
|
||||
...this.estimateValidationSchema,
|
||||
],
|
||||
'/',
|
||||
[...this.estimateValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/deliver',
|
||||
[
|
||||
...this.validateSpecificEstimateSchema,
|
||||
],
|
||||
[...this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deliverSaleEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/approve',
|
||||
[
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
[this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/reject',
|
||||
[
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
[this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
)
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id', [
|
||||
'/:id',
|
||||
[
|
||||
...this.validateSpecificEstimateSchema,
|
||||
...this.estimateValidationSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.delete(
|
||||
'/:id', [
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
'/:id',
|
||||
[this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:id',
|
||||
this.validateSpecificEstimateSchema,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
@@ -87,7 +80,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getEstimates.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.dynamicListService.handlerErrorsToResponse,
|
||||
this.dynamicListService.handlerErrorsToResponse
|
||||
);
|
||||
return router;
|
||||
}
|
||||
@@ -109,8 +102,14 @@ export default class SalesEstimatesController extends BaseController {
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim().escape(),
|
||||
check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
|
||||
check('note').optional().trim().escape(),
|
||||
check('terms_conditions').optional().trim().escape(),
|
||||
@@ -122,9 +121,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* Specific sale estimate validation schema.
|
||||
*/
|
||||
get validateSpecificEstimateSchema() {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
];
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,8 +134,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
query('column_sort_by').optional(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
]
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +149,10 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const storedEstimate = await this.saleEstimateService.createEstimate(tenantId, estimateDTO);
|
||||
const storedEstimate = await this.saleEstimateService.createEstimate(
|
||||
tenantId,
|
||||
estimateDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: storedEstimate.id,
|
||||
@@ -165,8 +165,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Handle update estimate details with associated entries.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async editEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -175,12 +175,16 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
try {
|
||||
// Update estimate with associated estimate entries.
|
||||
await this.saleEstimateService.editEstimate(tenantId, estimateId, estimateDTO);
|
||||
await this.saleEstimateService.editEstimate(
|
||||
tenantId,
|
||||
estimateId,
|
||||
estimateDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been created successfully.',
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -188,8 +192,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Deletes the given estimate with associated entries.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -200,7 +204,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been deleted successfully.'
|
||||
message: 'The sale estimate has been deleted successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -209,8 +213,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Deliver the given sale estimate.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deliverSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -230,9 +234,9 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Marks the sale estimate as approved.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async approveSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -252,9 +256,9 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Marks the sale estimate as rejected.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async rejectSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -274,16 +278,19 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve the given estimate with associated entries.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const estimate = await this.saleEstimateService.getEstimate(tenantId, estimateId);
|
||||
const estimate = await this.saleEstimateService.getEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
|
||||
return res.status(200).send({ estimate });
|
||||
} catch (error) {
|
||||
@@ -293,35 +300,28 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve estimates with pagination metadata.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
salesEstimates,
|
||||
pagination,
|
||||
filterMeta
|
||||
} = await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||
const { salesEstimates, pagination, filterMeta } =
|
||||
await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_estimates: this.transfromToResponse(salesEstimates),
|
||||
pagination,
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -329,12 +329,17 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Handles service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
private handleServiceErrors(
|
||||
error: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
@@ -409,4 +414,4 @@ export default class SalesEstimatesController extends BaseController {
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,23 +284,15 @@ export default class SaleInvoicesController extends BaseController {
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
salesInvoices,
|
||||
filterMeta,
|
||||
pagination,
|
||||
} = await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||
const { salesInvoices, filterMeta, pagination } =
|
||||
await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_invoices: salesInvoices,
|
||||
|
||||
@@ -230,16 +230,12 @@ export default class SalesReceiptsController extends BaseController {
|
||||
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface IAccountResponse extends IAccount {
|
||||
|
||||
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string,
|
||||
onlyInactive: boolean;
|
||||
};
|
||||
|
||||
export interface IAccountType {
|
||||
|
||||
@@ -62,6 +62,8 @@ export interface IBill {
|
||||
|
||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface IBillsService {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { IModel, ISortOrder } from "./Model";
|
||||
|
||||
export interface IDynamicFilter {
|
||||
setTableName(tableName: string): void;
|
||||
setModel(model: IModel): void;
|
||||
buildQuery(): void;
|
||||
getResponseMeta();
|
||||
}
|
||||
|
||||
export interface IFilterRole {
|
||||
@@ -10,19 +13,19 @@ export interface IFilterRole {
|
||||
index?: number;
|
||||
comparator?: string;
|
||||
}
|
||||
|
||||
export interface IDynamicListFilterDTO {
|
||||
export interface IDynamicListFilter {
|
||||
customViewId?: number;
|
||||
filterRoles?: IFilterRole[];
|
||||
columnSortBy: string;
|
||||
columnSortBy: ISortOrder;
|
||||
sortOrder: string;
|
||||
stringifiedFilterRoles: string;
|
||||
}
|
||||
|
||||
export interface IDynamicListService {
|
||||
dynamicList(
|
||||
tenantId: number,
|
||||
model: any,
|
||||
filter: IDynamicListFilterDTO
|
||||
filter: IDynamicListFilter
|
||||
): Promise<any>;
|
||||
handlerErrorsToResponse(error, req, res, next): void;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface IItemsFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
inactiveMode: boolean,
|
||||
};
|
||||
|
||||
export interface IItemsAutoCompleteFilter {
|
||||
|
||||
@@ -7,7 +7,9 @@ export interface IJournalReportQuery {
|
||||
noCents: boolean,
|
||||
divideOn1000: boolean,
|
||||
},
|
||||
transactionTypes: string | string[],
|
||||
transactionType: string,
|
||||
transactionId: string,
|
||||
|
||||
accountsIds: number | number[],
|
||||
fromRange: number,
|
||||
toRange: number,
|
||||
|
||||
@@ -1,17 +1,81 @@
|
||||
|
||||
|
||||
export interface IModel {
|
||||
name: string,
|
||||
tableName: string,
|
||||
fields: { [key: string]: any, },
|
||||
};
|
||||
name: string;
|
||||
tableName: string;
|
||||
fields: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface IFilterMeta {
|
||||
sortOrder: string,
|
||||
sortBy: string,
|
||||
};
|
||||
sortOrder: string;
|
||||
sortBy: string;
|
||||
}
|
||||
|
||||
export interface IPaginationMeta {
|
||||
pageSize: number,
|
||||
page: number,
|
||||
};
|
||||
pageSize: number;
|
||||
page: number;
|
||||
}
|
||||
|
||||
export interface IModelMetaDefaultSort {
|
||||
sortOrder: ISortOrder;
|
||||
sortField: string;
|
||||
}
|
||||
|
||||
export type IModelColumnType =
|
||||
| 'text'
|
||||
| 'number'
|
||||
| 'enumeration'
|
||||
| 'boolean'
|
||||
| 'relation';
|
||||
|
||||
export type ISortOrder = 'DESC' | 'ASC';
|
||||
|
||||
export interface IModelMetaFieldCommon {
|
||||
name: string;
|
||||
column: string;
|
||||
columnable?: boolean;
|
||||
fieldType: IModelColumnType;
|
||||
customQuery?: Function;
|
||||
}
|
||||
|
||||
export interface IModelMetaFieldNumber {
|
||||
fieldType: 'number';
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
export interface IModelMetaFieldOther {
|
||||
fieldType: 'text' | 'boolean';
|
||||
}
|
||||
|
||||
export type IModelMetaField = IModelMetaFieldCommon &
|
||||
(IModelMetaFieldOther | IModelMetaEnumerationField | IModelMetaRelationField);
|
||||
|
||||
export interface IModelMetaEnumerationOption {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface IModelMetaEnumerationField {
|
||||
fieldType: 'enumeration';
|
||||
options: IModelMetaEnumerationOption[];
|
||||
}
|
||||
|
||||
export interface IModelMetaRelationFieldCommon {
|
||||
fieldType: 'relation';
|
||||
}
|
||||
|
||||
export interface IModelMetaRelationEnumerationField {
|
||||
relationType: 'enumeration';
|
||||
relationKey: string;
|
||||
relationEntityLabel: string;
|
||||
relationEntityKey: string;
|
||||
}
|
||||
|
||||
export type IModelMetaRelationField = IModelMetaRelationFieldCommon & (
|
||||
IModelMetaRelationEnumerationField
|
||||
);
|
||||
|
||||
export interface IModelMeta {
|
||||
defaultFilterField: string;
|
||||
defaultSort: IModelMetaDefaultSort;
|
||||
fields: { [key: string]: IModelMetaField };
|
||||
}
|
||||
|
||||
31
server/src/interfaces/TransactionsByReference.ts
Normal file
31
server/src/interfaces/TransactionsByReference.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
export interface ITransactionsByReferenceQuery {
|
||||
referenceType: string;
|
||||
referenceId: string;
|
||||
}
|
||||
|
||||
export interface ITransactionsByReferenceAmount {
|
||||
amount: number;
|
||||
formattedAmount: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ITransactionsByReferenceTransaction{
|
||||
credit: ITransactionsByReferenceAmount;
|
||||
debit: ITransactionsByReferenceAmount;
|
||||
|
||||
contactType: string;
|
||||
formattedContactType: string;
|
||||
|
||||
contactId: number;
|
||||
|
||||
referenceType: string;
|
||||
formattedReferenceType: string;
|
||||
|
||||
referenceId: number;
|
||||
|
||||
accountName: string;
|
||||
accountCode: string;
|
||||
accountId: number;
|
||||
}
|
||||
@@ -55,6 +55,7 @@ export * from './CashFlow';
|
||||
export * from './InventoryDetails';
|
||||
export * from './LandedCost';
|
||||
export * from './Entry';
|
||||
export * from './TransactionsByReference';
|
||||
|
||||
export interface I18nService {
|
||||
__: (input: string) => string;
|
||||
|
||||
@@ -1,65 +1,85 @@
|
||||
import { forEach, uniqBy } from 'lodash';
|
||||
import { buildFilterRolesJoins } from 'lib/ViewRolesBuilder';
|
||||
import { IModel } from 'interfaces';
|
||||
import DynamicFilterAbstructor from './DynamicFilterAbstructor';
|
||||
import { IDynamicFilter, IFilterRole, IModel } from 'interfaces';
|
||||
|
||||
export default class DynamicFilter {
|
||||
model: IModel;
|
||||
tableName: string;
|
||||
export default class DynamicFilter extends DynamicFilterAbstructor{
|
||||
private model: IModel;
|
||||
private tableName: string;
|
||||
private dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {String} tableName -
|
||||
*/
|
||||
constructor(model) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
this.filters = [];
|
||||
this.dynamicFilters = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter.
|
||||
* @param {*} filterRole - Filter role.
|
||||
* Registers the given dynamic filter.
|
||||
* @param {IDynamicFilter} filterRole - Filter role.
|
||||
*/
|
||||
setFilter(filterRole) {
|
||||
filterRole.setModel(this.model);
|
||||
this.filters.push(filterRole);
|
||||
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||
dynamicFilter.setModel(this.model);
|
||||
|
||||
dynamicFilter.onInitialize();
|
||||
|
||||
this.dynamicFilters.push(dynamicFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dynamic filter build queries.
|
||||
* @returns
|
||||
*/
|
||||
private dynamicFiltersBuildQuery = () => {
|
||||
return this.dynamicFilters.map((filter) => {
|
||||
return filter.buildQuery()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dynamic filter roles.
|
||||
* @returns {IFilterRole[]}
|
||||
*/
|
||||
private dynamicFilterTableColumns = (): IFilterRole[] => {
|
||||
const localFilterRoles = [];
|
||||
|
||||
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||
const { filterRoles } = dynamicFilter;
|
||||
|
||||
localFilterRoles.push(
|
||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||
);
|
||||
});
|
||||
return localFilterRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds queries of filter roles.
|
||||
*/
|
||||
buildQuery() {
|
||||
const buildersCallbacks = [];
|
||||
const tableColumns = [];
|
||||
|
||||
this.filters.forEach((filter) => {
|
||||
const { filterRoles } = filter;
|
||||
|
||||
buildersCallbacks.push(filter.buildQuery());
|
||||
tableColumns.push(
|
||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||
);
|
||||
});
|
||||
public buildQuery = () => {
|
||||
const buildersCallbacks = this.dynamicFiltersBuildQuery();
|
||||
const tableColumns = this.dynamicFilterTableColumns();
|
||||
|
||||
return (builder) => {
|
||||
buildersCallbacks.forEach((builderCallback) => {
|
||||
builderCallback(builder);
|
||||
});
|
||||
|
||||
buildFilterRolesJoins(
|
||||
this.model,
|
||||
uniqBy(tableColumns, 'columnKey')
|
||||
)(builder);
|
||||
this.buildFilterRolesJoins(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve response metadata from all filters adapters.
|
||||
*/
|
||||
getResponseMeta() {
|
||||
public getResponseMeta = () => {
|
||||
const responseMeta = {};
|
||||
|
||||
this.filters.forEach((filter) => {
|
||||
this.dynamicFilters.forEach((filter) => {
|
||||
const { responseMeta: filterMeta } = filter;
|
||||
|
||||
forEach(filterMeta, (value, key) => {
|
||||
|
||||
40
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
40
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IModel, IFilterRole } from 'interfaces';
|
||||
import { FIELD_TYPE } from './constants';
|
||||
|
||||
export default class DynamicFilterAbstructor {
|
||||
/**
|
||||
* Extract relation table name from relation.
|
||||
* @param {String} column -
|
||||
* @return {String} - join relation table.
|
||||
*/
|
||||
protected getTableFromRelationColumn = (column: string) => {
|
||||
const splitedColumn = column.split('.');
|
||||
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds view roles join queries.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Roles.
|
||||
*/
|
||||
protected buildFilterRolesJoins = (builder) => {
|
||||
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||
const relationsFields = dynamicFilter.relationFields;
|
||||
|
||||
this.buildFieldsJoinQueries(builder, relationsFields);
|
||||
});
|
||||
};
|
||||
|
||||
private buildFieldsJoinQueries = (builder, fieldsRelations: string[]) => {
|
||||
fieldsRelations.forEach((fieldRelation) => {
|
||||
const relation = this.model.relationMappings[fieldRelation];
|
||||
|
||||
if (relation) {
|
||||
const splitToRelation = relation.join.to.split('.');
|
||||
const relationTable = splitToRelation[0] || '';
|
||||
|
||||
builder.join(relationTable, relation.join.from, '=', relation.join.to);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { difference } from 'lodash';
|
||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||
import DynamicFilterRoleAbstructor from './DynamicFilterRoleAbstructor';
|
||||
import { IFilterRole } from 'interfaces';
|
||||
|
||||
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
filterRoles: IFilterRole[];
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -13,18 +11,23 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
*/
|
||||
constructor(filterRoles: IFilterRole[]) {
|
||||
super();
|
||||
|
||||
|
||||
this.filterRoles = filterRoles;
|
||||
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
public onInitialize() {
|
||||
this.setFilterRolesRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter roles logic expression.
|
||||
* @return {string}
|
||||
*/
|
||||
private buildLogicExpression(): string {
|
||||
let expression = '';
|
||||
|
||||
|
||||
this.filterRoles.forEach((role, index) => {
|
||||
expression +=
|
||||
index === 0 ? `${role.index} ` : `${role.condition} ${role.index} `;
|
||||
@@ -35,19 +38,33 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
/**
|
||||
* Builds database query of view roles.
|
||||
*/
|
||||
buildQuery() {
|
||||
protected buildQuery() {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
|
||||
return (builder) => {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
buildFilterQuery(this.model, this.filterRoles, logicExpression)(builder);
|
||||
this.buildFilterQuery(
|
||||
this.model,
|
||||
this.filterRoles,
|
||||
logicExpression
|
||||
)(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
setResponseMeta() {
|
||||
private setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
filterRoles: this.filterRoles,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filter roles relations if field was relation type.
|
||||
*/
|
||||
private setFilterRolesRelations() {
|
||||
this.filterRoles.forEach((relationRole) => {
|
||||
this.setRelationIfRelationField(relationRole.fieldKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
61
server/src/lib/DynamicFilter/DynamicFilterQueryParser.ts
Normal file
61
server/src/lib/DynamicFilter/DynamicFilterQueryParser.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { OPERATION } from 'lib/LogicEvaluation/Parser';
|
||||
|
||||
export default class QueryParser {
|
||||
|
||||
constructor(tree, queries) {
|
||||
this.tree = tree;
|
||||
this.queries = queries;
|
||||
this.query = null;
|
||||
}
|
||||
|
||||
setQuery(query) {
|
||||
this.query = query.clone();
|
||||
}
|
||||
|
||||
parse() {
|
||||
return this.parseNode(this.tree);
|
||||
}
|
||||
|
||||
parseNode(node) {
|
||||
if (typeof node === 'string') {
|
||||
const nodeQuery = this.getQuery(node);
|
||||
return (query) => { nodeQuery(query); };
|
||||
}
|
||||
if (OPERATION[node.operation] === undefined) {
|
||||
throw new Error(`unknow expression ${node.operation}`);
|
||||
}
|
||||
const leftQuery = this.getQuery(node.left);
|
||||
const rightQuery = this.getQuery(node.right);
|
||||
|
||||
switch (node.operation) {
|
||||
case '&&':
|
||||
case 'AND':
|
||||
default:
|
||||
return (nodeQuery) => nodeQuery.where((query) => {
|
||||
query.where((q) => { leftQuery(q); });
|
||||
query.andWhere((q) => { rightQuery(q); });
|
||||
});
|
||||
case '||':
|
||||
case 'OR':
|
||||
return (nodeQuery) => nodeQuery.where((query) => {
|
||||
query.where((q) => { leftQuery(q); });
|
||||
query.orWhere((q) => { rightQuery(q); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getQuery(node) {
|
||||
if (typeof node !== 'string' && node !== null) {
|
||||
return this.parseNode(node);
|
||||
}
|
||||
const value = parseFloat(node);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
if (typeof this.queries[node] === 'undefined') {
|
||||
throw new Error(`unknow query under index ${node}`);
|
||||
}
|
||||
return this.queries[node];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,349 @@
|
||||
import { IFilterRole, IDynamicFilter, IModel } from "interfaces";
|
||||
import moment from 'moment';
|
||||
import { IFilterRole, IDynamicFilter, IModel } from 'interfaces';
|
||||
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
||||
import Parser from 'lib/LogicEvaluation/Parser';
|
||||
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
|
||||
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
|
||||
|
||||
export default class DynamicFilterAbstructor implements IDynamicFilter {
|
||||
filterRoles: IFilterRole[] = [];
|
||||
tableName: string;
|
||||
model: IModel;
|
||||
responseMeta: { [key: string]: any } = {};
|
||||
export default abstract class DynamicFilterAbstructor
|
||||
implements IDynamicFilter
|
||||
{
|
||||
protected filterRoles: IFilterRole[] = [];
|
||||
protected tableName: string;
|
||||
protected model: IModel;
|
||||
protected responseMeta: { [key: string]: any } = {};
|
||||
public relationFields = [];
|
||||
|
||||
setModel(model: IModel) {
|
||||
/**
|
||||
* Sets model the dynamic filter service.
|
||||
* @param {IModel} model
|
||||
*/
|
||||
public setModel(model: IModel) {
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes filter roles to map by index.
|
||||
* @param {IModel} model
|
||||
* @param {IFilterRole[]} roles
|
||||
* @returns
|
||||
*/
|
||||
protected convertRolesMapByIndex = (model, roles) => {
|
||||
const rolesIndexSet = {};
|
||||
|
||||
roles.forEach((role) => {
|
||||
rolesIndexSet[role.index] = this.buildRoleQuery(model, role);
|
||||
});
|
||||
return rolesIndexSet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds database query from stored view roles.
|
||||
* @param {Array} roles -
|
||||
* @return {Function}
|
||||
*/
|
||||
protected buildFilterRolesQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string = ''
|
||||
) => {
|
||||
const rolesIndexSet = this.convertRolesMapByIndex(model, roles);
|
||||
|
||||
// Lexer for logic expression.
|
||||
const lexer = new Lexer(logicExpression);
|
||||
const tokens = lexer.getTokens();
|
||||
|
||||
// Parse the logic expression.
|
||||
const parser = new Parser(tokens);
|
||||
const parsedTree = parser.parse();
|
||||
|
||||
const queryParser = new DynamicFilterQueryParser(parsedTree, rolesIndexSet);
|
||||
|
||||
return queryParser.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds filter query for query builder.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Filter roles.
|
||||
* @param {String} logicExpression - Logic expression.
|
||||
*/
|
||||
protected buildFilterQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string
|
||||
) => {
|
||||
return (builder) => {
|
||||
this.buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve relation column of comparator fieldز
|
||||
*/
|
||||
private getFieldComparatorRelationColumn(field) {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
const relationModel = relation.modelClass;
|
||||
const relationColumn =
|
||||
field.relationEntityKey === 'id'
|
||||
? 'id'
|
||||
: relationModel.getField(field.relationEntityKey, 'column');
|
||||
|
||||
return `${relationModel.tableName}.${relationColumn}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
* @param {IModel} model -
|
||||
* @param {} -
|
||||
*/
|
||||
private getFieldComparatorColumn = (field) => {
|
||||
return field.fieldType === FIELD_TYPE.RELATION
|
||||
? this.getFieldComparatorRelationColumn(field)
|
||||
: `${this.tableName}.${field.column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds roles queries.
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
protected buildRoleQuery = (model: IModel, role: IFilterRole) => {
|
||||
const field = model.getField(role.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
// Field relation custom query.
|
||||
if (typeof field.filterCustomQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
field.filterCustomQuery(builder, role);
|
||||
};
|
||||
}
|
||||
switch (field.fieldType) {
|
||||
case FIELD_TYPE.BOOLEAN:
|
||||
case FIELD_TYPE.ENUMERATION:
|
||||
return this.booleanRoleQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.NUMBER:
|
||||
return this.numberRoleQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.DATE:
|
||||
return this.dateQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.TEXT:
|
||||
default:
|
||||
return this.textRoleQueryBuilder(role, comparatorColumn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Boolean column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns
|
||||
*/
|
||||
protected booleanRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
case COMPARATOR_TYPE.IS:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
case COMPARATOR_TYPE.IS_NOT:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<>', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Numeric column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns
|
||||
*/
|
||||
protected numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.BIGGER_THAN:
|
||||
case COMPARATOR_TYPE.BIGGER:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.BIGGER_OR_EQUALS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.SMALLER_THAN:
|
||||
case COMPARATOR_TYPE.SMALLER:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.SMALLER_OR_EQUALS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<=', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Text column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns {Function}
|
||||
*/
|
||||
protected textRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.IS:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.IS_NOT:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.CONTAIN:
|
||||
case COMPARATOR_TYPE.CONTAINS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_CONTAIN:
|
||||
case COMPARATOR_TYPE.NOT_CONTAINS:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns {Function}
|
||||
*/
|
||||
protected dateQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.AFTER:
|
||||
case COMPARATOR_TYPE.BEFORE:
|
||||
return (builder) => {
|
||||
this.dateQueryAfterBeforeComparator(role, comparatorColumn, builder);
|
||||
};
|
||||
case COMPARATOR_TYPE.IN:
|
||||
return (builder) => {
|
||||
this.dateQueryInComparator(role, comparatorColumn, builder);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date query 'IN' comparator type.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @param builder
|
||||
*/
|
||||
protected dateQueryInComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
) => {
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (hasTimeFormat) {
|
||||
const targetDateTime = moment(role.value).format(dateFormat);
|
||||
builder.where(comparatorColumn, '=', targetDateTime);
|
||||
} else {
|
||||
const startDate = moment(role.value).startOf('day');
|
||||
const endDate = moment(role.value).endOf('day');
|
||||
|
||||
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
||||
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date query after/before comparator type.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn - Column.
|
||||
* @param builder
|
||||
*/
|
||||
protected dateQueryAfterBeforeComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
) => {
|
||||
const comparator = role.comparator === COMPARATOR_TYPE.BEFORE ? '<' : '>';
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const targetDate = moment(role.value);
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (!hasTimeFormat) {
|
||||
if (role.comparator === COMPARATOR_TYPE.BEFORE) {
|
||||
targetDate.startOf('day');
|
||||
} else {
|
||||
targetDate.endOf('day');
|
||||
}
|
||||
}
|
||||
const comparatorValue = targetDate.format(dateFormat);
|
||||
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers relation field if the given field was relation type
|
||||
* and not registered.
|
||||
* @param {string} fieldKey - Field key.
|
||||
*/
|
||||
protected setRelationIfRelationField = (fieldKey: string): void => {
|
||||
const field = this.model.getField(fieldKey);
|
||||
const isAlreadyRegistered = this.relationFields.some(
|
||||
(field) => field === fieldKey
|
||||
);
|
||||
|
||||
if (
|
||||
!isAlreadyRegistered &&
|
||||
field &&
|
||||
field.fieldType === FIELD_TYPE.RELATION
|
||||
) {
|
||||
this.relationFields.push(field.relationKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||
import {
|
||||
getRoleFieldColumn,
|
||||
validateFieldKeyExistance,
|
||||
getTableFromRelationColumn,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import { FIELD_TYPE } from './constants';
|
||||
|
||||
interface ISortRole {
|
||||
fieldKey: string;
|
||||
order: string;
|
||||
}
|
||||
|
||||
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
sortRole: { fieldKey: string; order: string } = {};
|
||||
private sortRole: ISortRole = {};
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -24,58 +25,65 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given field key with the model.
|
||||
* On initialize the dyanmic sort by.
|
||||
*/
|
||||
validate() {
|
||||
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
||||
public onInitialize() {
|
||||
this.setRelationIfRelationField(this.sortRole.fieldKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve field comparator relatin column.
|
||||
* @param field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorRelationColumn = (field): string => {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
const relationModel = relation.modelClass;
|
||||
const relationField = relationModel.getField(field.relationEntityLabel);
|
||||
|
||||
return `${relationModel.tableName}.${relationField.column}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
* @param {IModel} field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorColumn = (field): string => {
|
||||
return field.fieldType === FIELD_TYPE.RELATION
|
||||
? this.getFieldComparatorRelationColumn(field)
|
||||
: `${this.tableName}.${field.column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds database query of sort by column on the given direction.
|
||||
*/
|
||||
buildQuery() {
|
||||
const fieldRelation = getRoleFieldColumn(
|
||||
this.model,
|
||||
this.sortRole.fieldKey
|
||||
);
|
||||
const comparatorColumn =
|
||||
fieldRelation.relationColumn ||
|
||||
`${this.tableName}.${fieldRelation.column}`;
|
||||
public buildQuery = () => {
|
||||
const field = this.model.getField(this.sortRole.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
if (typeof fieldRelation.sortQuery !== 'undefined') {
|
||||
// Sort custom query.
|
||||
if (typeof field.sortCustomQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
fieldRelation.sortQuery(builder, this.sortRole);
|
||||
field.sortCustomQuery(builder, this.sortRole);
|
||||
};
|
||||
}
|
||||
|
||||
return (builder) => {
|
||||
if (this.sortRole.fieldKey) {
|
||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
||||
}
|
||||
this.joinBuildQuery()(builder);
|
||||
};
|
||||
}
|
||||
|
||||
joinBuildQuery() {
|
||||
const fieldColumn = getRoleFieldColumn(this.model, this.sortRole.fieldKey);
|
||||
|
||||
return (builder) => {
|
||||
if (fieldColumn.relation) {
|
||||
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
||||
|
||||
builder.join(
|
||||
joinTable,
|
||||
`${this.model.tableName}.${fieldColumn.column}`,
|
||||
'=',
|
||||
fieldColumn.relation
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
setResponseMeta() {
|
||||
public setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
sortOrder: this.sortRole.fieldKey,
|
||||
sortBy: this.sortRole.order,
|
||||
|
||||
37
server/src/lib/DynamicFilter/constants.ts
Normal file
37
server/src/lib/DynamicFilter/constants.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export const COMPARATOR_TYPE = {
|
||||
EQUAL: 'equal',
|
||||
EQUALS: 'equals',
|
||||
|
||||
NOT_EQUAL: 'not_equal',
|
||||
NOT_EQUALS: 'not_equals',
|
||||
|
||||
BIGGER_THAN: 'bigger_than',
|
||||
BIGGER: 'bigger',
|
||||
BIGGER_OR_EQUALS: 'bigger_or_equals',
|
||||
|
||||
SMALLER_THAN: 'smaller_than',
|
||||
SMALLER: 'smaller',
|
||||
SMALLER_OR_EQUALS: 'smaller_or_equals',
|
||||
|
||||
IS: 'is',
|
||||
IS_NOT: 'is_not',
|
||||
|
||||
CONTAINS: 'contains',
|
||||
CONTAIN: 'contain',
|
||||
NOT_CONTAINS: 'contains',
|
||||
NOT_CONTAIN: 'contain',
|
||||
|
||||
AFTER: 'after',
|
||||
BEFORE: 'before',
|
||||
IN: 'in',
|
||||
};
|
||||
|
||||
export const FIELD_TYPE = {
|
||||
TEXT: 'text',
|
||||
NUMBER: 'number',
|
||||
ENUMERATION: 'enumeration',
|
||||
BOOLEAN: 'boolean',
|
||||
RELATION: 'relation',
|
||||
DATE: 'date',
|
||||
COMPUTED: 'computed'
|
||||
};
|
||||
@@ -1,121 +1,7 @@
|
||||
import { difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
||||
import Parser from 'lib/LogicEvaluation/Parser';
|
||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||
|
||||
import { IFilterRole, IModel } from 'interfaces';
|
||||
|
||||
const numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
case 'equal':
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case 'not_equals':
|
||||
case 'not_equal':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case 'bigger_than':
|
||||
case 'bigger':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>', role.value);
|
||||
};
|
||||
case 'bigger_or_equals':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>=', role.value);
|
||||
};
|
||||
case 'smaller_than':
|
||||
case 'smaller':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<', role.value);
|
||||
};
|
||||
case 'smaller_or_equals':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<=', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
case 'is':
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, role.value);
|
||||
};
|
||||
case 'not_equal':
|
||||
case 'not_equals':
|
||||
case 'is_not':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case 'contain':
|
||||
case 'contains':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
case 'not_contain':
|
||||
case 'not_contains':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||
switch (role.comparator) {
|
||||
case 'after':
|
||||
case 'before':
|
||||
return (builder) => {
|
||||
const comparator = role.comparator === 'before' ? '<' : '>';
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const targetDate = moment(role.value);
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (!hasTimeFormat) {
|
||||
if (role.comparator === 'before') {
|
||||
targetDate.startOf('day');
|
||||
} else {
|
||||
targetDate.endOf('day');
|
||||
}
|
||||
}
|
||||
const comparatorValue = targetDate.format(dateFormat);
|
||||
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||
};
|
||||
case 'in':
|
||||
return (builder) => {
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (hasTimeFormat) {
|
||||
const targetDateTime = moment(role.value).format(dateFormat);
|
||||
builder.where(comparatorColumn, '=', targetDateTime);
|
||||
} else {
|
||||
const startDate = moment(role.value).startOf('day');
|
||||
const endDate = moment(role.value).endOf('day');
|
||||
|
||||
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
||||
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get field column metadata and its relation with other tables.
|
||||
* @param {String} tableName - Table name of target column.
|
||||
@@ -126,68 +12,6 @@ export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
||||
return tableFields[fieldKey] ? tableFields[fieldKey] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds roles queries.
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
export function buildRoleQuery(model: IModel, role: IFilterRole) {
|
||||
const fieldRelation = getRoleFieldColumn(model, role.fieldKey);
|
||||
const comparatorColumn =
|
||||
fieldRelation.relationColumn ||
|
||||
`${model.tableName}.${fieldRelation.column}`;
|
||||
|
||||
if (typeof fieldRelation.query !== 'undefined') {
|
||||
return (builder) => {
|
||||
fieldRelation.query(builder, role);
|
||||
};
|
||||
}
|
||||
switch (fieldRelation.columnType) {
|
||||
case 'number':
|
||||
return numberRoleQueryBuilder(role, comparatorColumn);
|
||||
case 'date':
|
||||
return dateQueryBuilder(role, comparatorColumn);
|
||||
case 'text':
|
||||
case 'varchar':
|
||||
default:
|
||||
return textRoleQueryBuilder(role, comparatorColumn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract relation table name from relation.
|
||||
* @param {String} column -
|
||||
* @return {String} - join relation table.
|
||||
*/
|
||||
export const getTableFromRelationColumn = (column: string) => {
|
||||
const splitedColumn = column.split('.');
|
||||
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds view roles join queries.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Roles.
|
||||
*/
|
||||
export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
|
||||
return (builder) => {
|
||||
roles.forEach((role) => {
|
||||
const fieldColumn = getRoleFieldColumn(model, role.fieldKey);
|
||||
|
||||
if (fieldColumn.relation) {
|
||||
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
||||
|
||||
builder.join(
|
||||
joinTable,
|
||||
`${model.tableName}.${fieldColumn.column}`,
|
||||
'=',
|
||||
fieldColumn.relation
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
||||
return (builder) => {
|
||||
const fieldColumn = getRoleFieldColumn(model, sortColumnKey);
|
||||
@@ -204,50 +28,6 @@ export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds database query from stored view roles.
|
||||
*
|
||||
* @param {Array} roles -
|
||||
* @return {Function}
|
||||
*/
|
||||
export function buildFilterRolesQuery(
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string = ''
|
||||
) {
|
||||
const rolesIndexSet = {};
|
||||
|
||||
roles.forEach((role) => {
|
||||
rolesIndexSet[role.index] = buildRoleQuery(model, role);
|
||||
});
|
||||
// Lexer for logic expression.
|
||||
const lexer = new Lexer(logicExpression);
|
||||
const tokens = lexer.getTokens();
|
||||
|
||||
// Parse the logic expression.
|
||||
const parser = new Parser(tokens);
|
||||
const parsedTree = parser.parse();
|
||||
|
||||
const queryParser = new QueryParser(parsedTree, rolesIndexSet);
|
||||
return queryParser.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter query for query builder.
|
||||
* @param {String} tableName -
|
||||
* @param {Array} roles -
|
||||
* @param {String} logicExpression -
|
||||
*/
|
||||
export const buildFilterQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string
|
||||
) => {
|
||||
return (builder) => {
|
||||
buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapes the view roles to view conditionals.
|
||||
* @param {Array} viewRoles -
|
||||
@@ -316,14 +96,6 @@ export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
||||
return model?.fields?.[fieldKey] || false;
|
||||
}
|
||||
|
||||
export function validateFilterRolesFieldsExistance(
|
||||
model,
|
||||
filterRoles: IFilterRole[]
|
||||
) {
|
||||
return filterRoles.filter((filterRole: IFilterRole) => {
|
||||
return !validateFieldKeyExistance(model, filterRole.fieldKey);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve model fields keys.
|
||||
|
||||
100
server/src/models/Account.Settings.ts
Normal file
100
server/src/models/Account.Settings.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { IModelMeta } from 'interfaces';
|
||||
import { ACCOUNT_TYPES } from 'data/AccountTypes';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
name: 'Account name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
slug: {
|
||||
name: 'Account slug',
|
||||
column: 'slug',
|
||||
fieldType: 'text',
|
||||
columnable: false,
|
||||
},
|
||||
code: {
|
||||
name: 'Account code',
|
||||
column: 'code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
root_type: {
|
||||
name: 'Root type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'asset', label: 'Asset' },
|
||||
{ key: 'liability', label: 'Liability' },
|
||||
{ key: 'equity', label: 'Equity' },
|
||||
{ key: 'Income', label: 'Income' },
|
||||
{ key: 'expense', label: 'Expense' },
|
||||
],
|
||||
filterCustomQuery: RootTypeFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
normal: {
|
||||
name: 'Account normal',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'debit', label: 'Debit' },
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
],
|
||||
filterCustomQuery: NormalTypeFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
type: {
|
||||
name: 'Type',
|
||||
column: 'account_type',
|
||||
fieldType: 'enumeration',
|
||||
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||
label: accountType.label,
|
||||
key: accountType.key
|
||||
})),
|
||||
},
|
||||
active: {
|
||||
name: 'Active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
balance: {
|
||||
name: 'Account balance',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency: {
|
||||
name: 'Currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter query of root type field .
|
||||
*/
|
||||
function RootTypeFieldFilterQuery(query, role) {
|
||||
query.modify('filterByRootType', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query of normal field .
|
||||
*/
|
||||
function NormalTypeFieldFilterQuery(query, role) {
|
||||
query.modify('filterByAccountNormal', role.value);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
/* eslint-disable global-require */
|
||||
import { Model } from 'objection';
|
||||
import { flatten, castArray } from 'lodash';
|
||||
import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import {
|
||||
buildFilterQuery,
|
||||
buildSortColumnQuery,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import { buildFilterQuery, buildSortColumnQuery } from 'lib/ViewRolesBuilder';
|
||||
import { flatToNestedArray } from 'utils';
|
||||
import DependencyGraph from 'lib/DependencyGraph';
|
||||
import AccountTypesUtils from 'lib/AccountTypes'
|
||||
import AccountTypesUtils from 'lib/AccountTypes';
|
||||
import AccountSettings from './Account.Settings';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import { ACCOUNT_TYPES } from 'data/AccountTypes';
|
||||
|
||||
export default class Account extends TenantModel {
|
||||
export default class Account extends mixin(TenantModel, [ModelSettings]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ export default class Account extends TenantModel {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class Account extends TenantModel {
|
||||
'accountRootType',
|
||||
'accountNormal',
|
||||
'isBalanceSheetAccount',
|
||||
'isPLSheet'
|
||||
'isPLSheet',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -95,6 +95,13 @@ export default class Account extends TenantModel {
|
||||
const TABLE_NAME = Account.tableName;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
|
||||
filterAccounts(query, accountIds) {
|
||||
if (accountIds.length > 0) {
|
||||
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||
@@ -111,6 +118,28 @@ export default class Account extends TenantModel {
|
||||
sortColumnBuilder(query, columnKey, direction) {
|
||||
buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by root type.
|
||||
*/
|
||||
filterByRootType(query, rootType) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.rootType === rootType
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by account normal
|
||||
*/
|
||||
filterByAccountNormal(query, accountNormal) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.normal === accountNormal,
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,10 +163,10 @@ export default class Account extends TenantModel {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
@@ -147,7 +176,7 @@ export default class Account extends TenantModel {
|
||||
|
||||
/**
|
||||
* Detarmines whether the given root type equals the account type.
|
||||
* @param {string} rootType
|
||||
* @param {string} rootType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isRootType(rootType) {
|
||||
@@ -156,11 +185,14 @@ export default class Account extends TenantModel {
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(this.accountType, parentType);
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,105 +220,32 @@ export default class Account extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts flatten accounts list to nested array.
|
||||
* @param {Array} accounts
|
||||
* @param {Object} options
|
||||
* Converts flatten accounts list to nested array.
|
||||
* @param {Array} accounts
|
||||
* @param {Object} options
|
||||
*/
|
||||
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||
return flatToNestedArray(accounts, { id: 'id', parentId: 'parentAccountId' })
|
||||
return flatToNestedArray(accounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the accounts list to depenedency graph structure.
|
||||
* @param {IAccount[]} accounts
|
||||
*/
|
||||
* @param {IAccount[]} accounts
|
||||
*/
|
||||
static toDependencyGraph(accounts) {
|
||||
return DependencyGraph.fromArray(
|
||||
accounts, { itemId: 'id', parentItemId: 'parentAccountId' }
|
||||
);
|
||||
return DependencyGraph.fromArray(accounts, {
|
||||
itemId: 'id',
|
||||
parentItemId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
name: {
|
||||
label: 'Account name',
|
||||
column: 'name',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
type: {
|
||||
label: 'Account type',
|
||||
column: 'account_type',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
|
||||
fieldType: 'text',
|
||||
},
|
||||
code: {
|
||||
label: 'Account code',
|
||||
column: 'code',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
root_type: {
|
||||
label: 'Root type',
|
||||
options: [
|
||||
{ key: 'asset', label: 'Asset', },
|
||||
{ key: 'liability', label: 'Liability' },
|
||||
{ key: 'equity', label: 'Equity' },
|
||||
{ key: 'Income', label: 'Income' },
|
||||
{ key: 'expense', label: 'Expense' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
const accountsTypes = AccountTypesUtils.getTypesByRootType(role.value);
|
||||
const accountsTypesKeys = accountsTypes.map(type => type.key);
|
||||
|
||||
query.whereIn('account_type', accountsTypesKeys);
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
active: {
|
||||
label: 'Active',
|
||||
column: 'active',
|
||||
columnType: 'boolean',
|
||||
fieldType: 'checkbox',
|
||||
},
|
||||
balance: {
|
||||
label: 'Balance',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency: {
|
||||
label: 'Currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'options',
|
||||
optionsResource: 'currency',
|
||||
optionsKey: 'currency_code',
|
||||
optionsLabel: 'currency_name',
|
||||
},
|
||||
normal: {
|
||||
label: 'Account normal',
|
||||
column: 'account_type_id',
|
||||
fieldType: 'options',
|
||||
relation: 'account_types.id',
|
||||
relationColumn: 'account_types.normal',
|
||||
options: [
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
{ key: 'debit', label: 'Debit' },
|
||||
],
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return AccountSettings;
|
||||
}
|
||||
}
|
||||
|
||||
96
server/src/models/Bill.Settings.ts
Normal file
96
server/src/models/Bill.Settings.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { IModelMeta } from 'interfaces';
|
||||
import Bill from './Bill';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'vendor',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
fields: {
|
||||
vendor: {
|
||||
name: 'Vendor',
|
||||
column: 'vendor_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
bill_number: {
|
||||
name: 'Bill number',
|
||||
column: 'bill_number',
|
||||
columnable: true,
|
||||
fieldType: 'text',
|
||||
},
|
||||
bill_date: {
|
||||
name: 'Bill date',
|
||||
column: 'bill_date',
|
||||
columnable: true,
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
name: 'Due date',
|
||||
column: 'due_date',
|
||||
columnable: true,
|
||||
fieldType: 'date',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
columnable: true,
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'Status',
|
||||
fieldType: 'enumeration',
|
||||
columnable: true,
|
||||
options: [
|
||||
{ name: 'Paid', key: 'paid' },
|
||||
{ name: 'Partially paid', key: 'partially-paid' },
|
||||
{ name: 'Overdue', key: 'overdue' },
|
||||
{ name: 'Unpaid', key: 'unpaid' },
|
||||
{ name: 'Opened', key: 'opened' },
|
||||
{ name: 'Draft', key: 'draft' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
name: 'Payment amount',
|
||||
column: 'payment_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('statusFilter', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field sort custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { Model, raw, mixin } from 'objection';
|
||||
import moment from 'moment';
|
||||
import { difference } from 'lodash';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { query } from 'winston';
|
||||
import BillSettings from './Bill.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class Bill extends TenantModel {
|
||||
export default class Bill extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -12,10 +13,9 @@ export default class Bill extends TenantModel {
|
||||
return 'bills';
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
@@ -71,15 +71,42 @@ export default class Bill extends TenantModel {
|
||||
* Filters the bills from the given date.
|
||||
*/
|
||||
fromDate(query, fromDate) {
|
||||
query.where('bill_date', '<=', fromDate)
|
||||
query.where('bill_date', '<=', fromDate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the bills by full-payment bills.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`PAYMENT_AMOUNT = AMOUNT ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
statusFilter(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
default:
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,7 +130,7 @@ export default class Bill extends TenantModel {
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount'
|
||||
'unallocatedCostAmount',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -198,6 +225,13 @@ export default class Bill extends TenantModel {
|
||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
@@ -269,88 +303,4 @@ export default class Bill extends TenantModel {
|
||||
.where('id', billId)
|
||||
[changeMethod]('payment_amount', Math.abs(amount));
|
||||
}
|
||||
|
||||
static get fields() {
|
||||
return {
|
||||
vendor: {
|
||||
label: 'Vendor',
|
||||
column: 'vendor_id',
|
||||
relation: 'contacts.id',
|
||||
relationColumn: 'contacts.display_name',
|
||||
},
|
||||
bill_number: {
|
||||
label: 'Bill number',
|
||||
column: 'bill_number',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
bill_date: {
|
||||
label: 'Bill date',
|
||||
column: 'bill_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
label: 'Due date',
|
||||
column: 'due_date',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [],
|
||||
query: (query, role) => {
|
||||
switch (role.value) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'opened':
|
||||
query.modify('opened');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
},
|
||||
},
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
label: 'Payment amount',
|
||||
column: 'payment_amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
label: 'Note',
|
||||
column: 'note',
|
||||
},
|
||||
user: {},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
67
server/src/models/BillPayment.Settings.ts
Normal file
67
server/src/models/BillPayment.Settings.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'vendor',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
fields: {
|
||||
'vendor': {
|
||||
name: 'Vendor name',
|
||||
column: 'vendor_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'due_amount': {
|
||||
name: 'Due amount',
|
||||
column: 'due_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'payment_account': {
|
||||
name: 'Payment account',
|
||||
column: 'payment_account_id',
|
||||
|
||||
fieldType: 'relation',
|
||||
fieldRelation: 'paymentAccount',
|
||||
|
||||
fieldRelationType: 'enumeration',
|
||||
relationLabelField: 'name',
|
||||
relationKeyField: 'slug',
|
||||
},
|
||||
'payment_number': {
|
||||
name: 'Payment number',
|
||||
column: 'payment_number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'payment_date': {
|
||||
name: 'Payment date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'description': {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model } from "objection";
|
||||
import { Model, mixin } from "objection";
|
||||
import TenantModel from "models/TenantModel";
|
||||
import ModelSetting from "./ModelSetting";
|
||||
import BillPaymentSettings from "./BillPayment.Settings";
|
||||
|
||||
export default class BillPayment extends TenantModel {
|
||||
export default class BillPayment extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -16,8 +18,11 @@ export default class BillPayment extends TenantModel {
|
||||
return ["createdAt", "updatedAt"];
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillPaymentSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,7 +31,7 @@ export default class BillPayment extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const BillPaymentEntry = require("models/BillPaymentEntry");
|
||||
const AccountTransaction = require("models/AccountTransaction");
|
||||
const Contact = require("models/Contact");
|
||||
const Vendor = require("models/Vendor");
|
||||
const Account = require("models/Account");
|
||||
|
||||
return {
|
||||
@@ -41,7 +46,7 @@ export default class BillPayment extends TenantModel {
|
||||
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
modelClass: Vendor.default,
|
||||
join: {
|
||||
from: "bills_payments.vendorId",
|
||||
to: "contacts.id",
|
||||
@@ -73,70 +78,4 @@ export default class BillPayment extends TenantModel {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource fields.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
vendor: {
|
||||
label: "Vendor name",
|
||||
column: "vendor_id",
|
||||
relation: "contacts.id",
|
||||
relationColumn: "contacts.display_name",
|
||||
},
|
||||
amount: {
|
||||
label: "Amount",
|
||||
column: "amount",
|
||||
columnType: "number",
|
||||
fieldType: "number",
|
||||
},
|
||||
due_amount: {
|
||||
label: "Due amount",
|
||||
column: "due_amount",
|
||||
columnType: "number",
|
||||
fieldType: "number",
|
||||
},
|
||||
payment_account: {
|
||||
label: "Payment account",
|
||||
column: "payment_account_id",
|
||||
relation: "accounts.id",
|
||||
relationColumn: "accounts.name",
|
||||
|
||||
fieldType: "options",
|
||||
optionsResource: "Account",
|
||||
optionsKey: "id",
|
||||
optionsLabel: "name",
|
||||
},
|
||||
payment_number: {
|
||||
label: "Payment number",
|
||||
column: "payment_number",
|
||||
columnType: "string",
|
||||
fieldType: "text",
|
||||
},
|
||||
payment_date: {
|
||||
label: "Payment date",
|
||||
column: "payment_date",
|
||||
columnType: "date",
|
||||
fieldType: "date",
|
||||
},
|
||||
reference_no: {
|
||||
label: "Reference No.",
|
||||
column: "reference",
|
||||
columnType: "string",
|
||||
fieldType: "text",
|
||||
},
|
||||
description: {
|
||||
label: "Description",
|
||||
column: "description",
|
||||
columnType: "string",
|
||||
fieldType: "text",
|
||||
},
|
||||
created_at: {
|
||||
label: "Created at",
|
||||
column: "created_at",
|
||||
columnType: "date",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
98
server/src/models/Customer.Settings.ts
Normal file
98
server/src/models/Customer.Settings.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
export default {
|
||||
fields: {
|
||||
display_name: {
|
||||
name: 'Display name',
|
||||
column: 'display_name',
|
||||
fieldType: 'text',
|
||||
columnable: true,
|
||||
},
|
||||
email: {
|
||||
name: 'Email',
|
||||
column: 'email',
|
||||
fieldType: 'text',
|
||||
columnable: true,
|
||||
},
|
||||
work_phone: {
|
||||
name: 'Work phone',
|
||||
column: 'work_phone',
|
||||
fieldType: 'text',
|
||||
columnable: true,
|
||||
},
|
||||
personal_phone: {
|
||||
name: 'Personal phone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
columnable: true,
|
||||
},
|
||||
company_name: {
|
||||
name: 'Company name',
|
||||
column: 'company_name',
|
||||
fieldType: 'text',
|
||||
columnable: true,
|
||||
},
|
||||
website: {
|
||||
name: 'Website',
|
||||
column: 'website',
|
||||
fieldType: 'text',
|
||||
columnable: true,
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
columnable: true,
|
||||
},
|
||||
balance: {
|
||||
name: 'Balance',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
columnable: true,
|
||||
},
|
||||
opening_balance: {
|
||||
name: 'Opening balance',
|
||||
column: 'opening_balance',
|
||||
fieldType: 'number',
|
||||
columnable: true,
|
||||
},
|
||||
opening_balance_at: {
|
||||
name: 'Opening balance at',
|
||||
column: 'opening_balance_at',
|
||||
filterable: false,
|
||||
fieldType: 'date',
|
||||
columnable: true,
|
||||
},
|
||||
currency_code: {
|
||||
column: 'currency_code',
|
||||
columnable: true,
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'active', label: 'Active' },
|
||||
{ key: 'inactive', label: 'Inactive' },
|
||||
{ key: 'overdue', label: 'Overdue' },
|
||||
{ key: 'unpaid', label: 'Unpaid' },
|
||||
],
|
||||
columnable: true,
|
||||
filterQuery: statusFieldFilterQuery,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function statusFieldFilterQuery(query, role) {
|
||||
switch (role.value) {
|
||||
case 'active':
|
||||
query.modify('active');
|
||||
break;
|
||||
case 'inactive':
|
||||
query.modify('inactive');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import PaginationQueryBuilder from './Pagination';
|
||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomerSettings from './Customer.Settings';
|
||||
|
||||
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||
constructor(...args) {
|
||||
@@ -15,7 +17,7 @@ class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
export default class Customer extends TenantModel {
|
||||
export default class Customer extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Query builder.
|
||||
*/
|
||||
@@ -63,6 +65,13 @@ export default class Customer extends TenantModel {
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('active', !active);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the active customers.
|
||||
*/
|
||||
@@ -81,10 +90,9 @@ export default class Customer extends TenantModel {
|
||||
overdue(query) {
|
||||
query.select(
|
||||
'*',
|
||||
Customer
|
||||
.relatedQuery('overDueInvoices', query.knex())
|
||||
Customer.relatedQuery('overDueInvoices', query.knex())
|
||||
.count()
|
||||
.as('countOverdue'),
|
||||
.as('countOverdue')
|
||||
);
|
||||
query.having('countOverdue', '>', 0);
|
||||
},
|
||||
@@ -93,7 +101,7 @@ export default class Customer extends TenantModel {
|
||||
*/
|
||||
unpaid(query) {
|
||||
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,77 +130,12 @@ export default class Customer extends TenantModel {
|
||||
},
|
||||
filter: (query) => {
|
||||
query.modify('overdue');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get fields() {
|
||||
return {
|
||||
contact_service: {
|
||||
column: 'contact_service',
|
||||
},
|
||||
display_name: {
|
||||
column: 'display_name',
|
||||
},
|
||||
email: {
|
||||
column: 'email',
|
||||
},
|
||||
work_phone: {
|
||||
column: 'work_phone',
|
||||
},
|
||||
personal_phone: {
|
||||
column: 'personal_phone',
|
||||
},
|
||||
company_name: {
|
||||
column: 'company_name',
|
||||
},
|
||||
website: {
|
||||
column: 'website'
|
||||
},
|
||||
created_at: {
|
||||
column: 'created_at',
|
||||
},
|
||||
balance: {
|
||||
column: 'balance',
|
||||
},
|
||||
opening_balance: {
|
||||
column: 'opening_balance',
|
||||
},
|
||||
opening_balance_at: {
|
||||
column: 'opening_balance_at',
|
||||
},
|
||||
currency_code: {
|
||||
column: 'currency_code',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'active', label: 'Active' },
|
||||
{ key: 'inactive', label: 'Inactive' },
|
||||
{ key: 'overdue', label: 'Overdue' },
|
||||
{ key: 'unpaid', label: 'Unpaid' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
switch(role.value) {
|
||||
case 'active':
|
||||
query.modify('active');
|
||||
break;
|
||||
case 'inactive':
|
||||
query.modify('inactive');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
column: 'created_at',
|
||||
}
|
||||
};
|
||||
static get meta() {
|
||||
return CustomerSettings;
|
||||
}
|
||||
}
|
||||
|
||||
71
server/src/models/Expense.Settings.ts
Normal file
71
server/src/models/Expense.Settings.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Expense - Settings.
|
||||
*/
|
||||
export default {
|
||||
defaultFilterField: 'description',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
fields: {
|
||||
'payment_date': {
|
||||
name: 'Payment date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'payment_account': {
|
||||
name: 'Payment account',
|
||||
column: 'payment_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'paymentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'total_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'description': {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'published': {
|
||||
name: 'Published',
|
||||
column: 'published_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'status': {
|
||||
name: 'Status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', name: 'Draft' },
|
||||
{ key: 'published', name: 'Published' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { viewRolesBuilder } from 'lib/ViewRolesBuilder';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ExpenseSettings from './Expense.Settings';
|
||||
|
||||
export default class Expense extends TenantModel {
|
||||
export default class Expense extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -25,19 +27,8 @@ export default class Expense extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static get media() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static get virtualAttributes() {
|
||||
return ['isPublished', 'unallocatedCostAmount'];
|
||||
}
|
||||
@@ -96,6 +87,18 @@ export default class Expense extends TenantModel {
|
||||
filterByPublished(query) {
|
||||
query.whereNot('published_at', null);
|
||||
},
|
||||
|
||||
filterByStatus(query, status) {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
query.modify('filterByDraft');
|
||||
break;
|
||||
case 'published':
|
||||
default:
|
||||
query.modify('filterByPublished');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,71 +145,7 @@ export default class Expense extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
payment_date: {
|
||||
label: 'Payment date',
|
||||
column: 'payment_date',
|
||||
columnType: 'date',
|
||||
},
|
||||
payment_account: {
|
||||
label: 'Payment account',
|
||||
column: 'payment_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account',
|
||||
},
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'total_amount',
|
||||
columnType: 'number',
|
||||
},
|
||||
currency_code: {
|
||||
label: 'Currency',
|
||||
column: 'currency_code',
|
||||
optionsResource: 'currency',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
columnType: 'string',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
},
|
||||
published: {
|
||||
label: 'Published',
|
||||
column: 'published_at',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'draft', label: 'Draft' },
|
||||
{ key: 'published', label: 'Published' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
switch (role.value) {
|
||||
case 'draft':
|
||||
query.modify('filterByDraft');
|
||||
break;
|
||||
case 'published':
|
||||
query.modify('filterByPublished');
|
||||
break;
|
||||
}
|
||||
},
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return ExpenseSettings;
|
||||
}
|
||||
}
|
||||
|
||||
59
server/src/models/InventoryAdjustment.Settings.ts
Normal file
59
server/src/models/InventoryAdjustment.Settings.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
defaultFilterField: 'date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'date',
|
||||
},
|
||||
fields: {
|
||||
'date': {
|
||||
name: 'Date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'type': {
|
||||
name: 'Adjustment type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'increment', name: 'Increment' },
|
||||
{ key: 'decrement', name: 'Decrement' },
|
||||
],
|
||||
},
|
||||
'adjustment_account': {
|
||||
name: 'Adjustment account',
|
||||
column: 'adjustment_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'adjustmentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'reason': {
|
||||
name: 'Reason',
|
||||
column: 'reason',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'description': {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'published_at': {
|
||||
name: 'Published at',
|
||||
column: 'published_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import InventoryAdjustmentSettings from './InventoryAdjustment.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class InventoryAdjustment extends TenantModel {
|
||||
export default class InventoryAdjustment extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -40,8 +42,8 @@ export default class InventoryAdjustment extends TenantModel {
|
||||
|
||||
static getInventoryDirection(type) {
|
||||
const directions = {
|
||||
'increment': 'IN',
|
||||
'decrement': 'OUT',
|
||||
increment: 'IN',
|
||||
decrement: 'OUT',
|
||||
};
|
||||
return directions[type] || '';
|
||||
}
|
||||
@@ -81,52 +83,9 @@ export default class InventoryAdjustment extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
date: {
|
||||
label: 'Date',
|
||||
column: 'date',
|
||||
columnType: 'date',
|
||||
},
|
||||
type: {
|
||||
label: 'Adjustment type',
|
||||
column: 'type',
|
||||
options: [
|
||||
{ key: 'increment', label: 'Increment', },
|
||||
{ key: 'decrement', label: 'Decrement' },
|
||||
],
|
||||
},
|
||||
adjustment_account: {
|
||||
column: 'adjustment_account_id',
|
||||
},
|
||||
reason: {
|
||||
label: 'Reason',
|
||||
column: 'reason',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
},
|
||||
user: {
|
||||
label: 'User',
|
||||
column: 'user_id',
|
||||
},
|
||||
published_at: {
|
||||
label: 'Published at',
|
||||
column: 'published_at'
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return InventoryAdjustmentSettings;
|
||||
}
|
||||
}
|
||||
|
||||
123
server/src/models/Item.Settings.ts
Normal file
123
server/src/models/Item.Settings.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
fields: {
|
||||
'type': {
|
||||
name: 'Item type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'inventory', label: 'Inventory', },
|
||||
{ key: 'service', label: 'Service' },
|
||||
{ key: 'non-inventory', label: 'Non Inventory', },
|
||||
],
|
||||
},
|
||||
'name': {
|
||||
name: 'Name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'code': {
|
||||
name: 'Code',
|
||||
column: 'code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'sellable': {
|
||||
name: 'Sellable',
|
||||
column: 'sellable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
'purchasable': {
|
||||
name: 'Purchasable',
|
||||
column: 'purchasable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
'sell_price': {
|
||||
name: 'Sell price',
|
||||
column: 'sell_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'cost_price': {
|
||||
name: 'Cost price',
|
||||
column: 'cost_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'cost_account': {
|
||||
name: 'Cost account',
|
||||
column: 'cost_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'costAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'sell_account': {
|
||||
name: 'Sell account',
|
||||
column: 'sell_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'sellAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'inventory_account': {
|
||||
name: 'Inventory account',
|
||||
column: 'inventory_account_id',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'inventoryAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'sell_description': {
|
||||
name: 'Sell description',
|
||||
column: 'sell_description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'purchase_description': {
|
||||
name: 'Purchase description',
|
||||
column: 'purchase_description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'quantity_on_hand': {
|
||||
name: 'Quantity on hand',
|
||||
column: 'quantity_on_hand',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'note': {
|
||||
name: 'Note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'category': {
|
||||
name: 'Category',
|
||||
column: 'category_id',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'category',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'active': {
|
||||
name: 'Active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,20 +1,22 @@
|
||||
import { Model } from "objection";
|
||||
import TenantModel from "models/TenantModel";
|
||||
import { buildFilterQuery } from "lib/ViewRolesBuilder";
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||
import ItemSettings from './Item.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class Item extends TenantModel {
|
||||
export default class Item extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return "items";
|
||||
return 'items';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ["createdAt", "updatedAt"];
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,13 @@ export default class Item extends TenantModel {
|
||||
viewRolesBuilder(query, conditions, logicExpression) {
|
||||
buildFilterQuery(Item.tableName, conditions, logicExpression)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('items.active', !active);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,9 +51,9 @@ export default class Item extends TenantModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Media = require("models/Media");
|
||||
const Account = require("models/Account");
|
||||
const ItemCategory = require("models/ItemCategory");
|
||||
const Media = require('models/Media');
|
||||
const Account = require('models/Account');
|
||||
const ItemCategory = require('models/ItemCategory');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -54,8 +63,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemCategory.default,
|
||||
join: {
|
||||
from: "items.categoryId",
|
||||
to: "items_categories.id",
|
||||
from: 'items.categoryId',
|
||||
to: 'items_categories.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -63,8 +72,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: "items.costAccountId",
|
||||
to: "accounts.id",
|
||||
from: 'items.costAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -72,8 +81,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: "items.sellAccountId",
|
||||
to: "accounts.id",
|
||||
from: 'items.sellAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -81,8 +90,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: "items.inventoryAccountId",
|
||||
to: "accounts.id",
|
||||
from: 'items.inventoryAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -90,110 +99,21 @@ export default class Item extends TenantModel {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Media.default,
|
||||
join: {
|
||||
from: "items.id",
|
||||
from: 'items.id',
|
||||
through: {
|
||||
from: "media_links.model_id",
|
||||
to: "media_links.media_id",
|
||||
from: 'media_links.model_id',
|
||||
to: 'media_links.media_id',
|
||||
},
|
||||
to: "media.id",
|
||||
to: 'media.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Item fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
type: {
|
||||
label: "Type",
|
||||
column: "type",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
column: "name",
|
||||
},
|
||||
code: {
|
||||
label: "Code",
|
||||
column: "code",
|
||||
},
|
||||
sellable: {
|
||||
label: "Sellable",
|
||||
column: "sellable",
|
||||
},
|
||||
purchasable: {
|
||||
label: "Purchasable",
|
||||
column: "purchasable",
|
||||
},
|
||||
sell_price: {
|
||||
label: "Sell price",
|
||||
column: "sell_price",
|
||||
},
|
||||
cost_price: {
|
||||
label: "Cost price",
|
||||
column: "cost_price",
|
||||
},
|
||||
currency_code: {
|
||||
label: "Currency",
|
||||
column: "currency_code",
|
||||
},
|
||||
cost_account: {
|
||||
label: "Cost account",
|
||||
column: "cost_account_id",
|
||||
relation: "accounts.id",
|
||||
relationColumn: "accounts.name",
|
||||
},
|
||||
sell_account: {
|
||||
label: "Sell account",
|
||||
column: "sell_account_id",
|
||||
relation: "accounts.id",
|
||||
relationColumn: "accounts.name",
|
||||
},
|
||||
inventory_account: {
|
||||
label: "Inventory account",
|
||||
column: "inventory_account_id",
|
||||
relation: "accounts.id",
|
||||
relationColumn: "accounts.name",
|
||||
},
|
||||
sell_description: {
|
||||
label: "Sell description",
|
||||
column: "sell_description",
|
||||
},
|
||||
purchase_description: {
|
||||
label: "Purchase description",
|
||||
column: "purchase_description",
|
||||
},
|
||||
quantity_on_hand: {
|
||||
label: "Quantity on hand",
|
||||
column: "quantity_on_hand",
|
||||
},
|
||||
note: {
|
||||
label: "Note",
|
||||
column: "note",
|
||||
},
|
||||
category: {
|
||||
label: "Category",
|
||||
column: "category_id",
|
||||
relation: "items_categories.id",
|
||||
relationColumn: "items_categories.name",
|
||||
},
|
||||
active: {
|
||||
label: "Active",
|
||||
column: "active",
|
||||
},
|
||||
// user: {
|
||||
// label: 'User',
|
||||
// column: 'user_id',
|
||||
// relation: 'users.id',
|
||||
// relationColumn: 'users.',
|
||||
// },
|
||||
created_at: {
|
||||
label: "Created at",
|
||||
column: "created_at",
|
||||
columnType: "date",
|
||||
fieldType: "date",
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return ItemSettings;
|
||||
}
|
||||
}
|
||||
|
||||
30
server/src/models/ItemCategory.Settings.ts
Normal file
30
server/src/models/ItemCategory.Settings.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
count: {
|
||||
label: 'Count',
|
||||
column: 'count',
|
||||
fieldType: 'number',
|
||||
virtualColumn: true,
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import path from 'path';
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ItemCategorySettings from './ItemCategory.Settings';
|
||||
|
||||
export default class ItemCategory extends TenantModel {
|
||||
export default class ItemCategory extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -10,10 +11,6 @@ export default class ItemCategory extends TenantModel {
|
||||
return 'items_categories';
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
@@ -43,68 +40,23 @@ export default class ItemCategory extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Item category fields.
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get fields() {
|
||||
static get modifiers() {
|
||||
return {
|
||||
name: {
|
||||
label: 'Name',
|
||||
column: 'name',
|
||||
columnType: 'string'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string'
|
||||
},
|
||||
user: {
|
||||
label: 'User',
|
||||
column: 'user_id',
|
||||
relation: 'users.id',
|
||||
relationColumn: 'users.id',
|
||||
},
|
||||
cost_account: {
|
||||
label: 'Cost account',
|
||||
column: 'cost_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account'
|
||||
},
|
||||
sell_account: {
|
||||
label: 'Sell account',
|
||||
column: 'sell_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account'
|
||||
},
|
||||
inventory_account: {
|
||||
label: 'Inventory account',
|
||||
column: 'inventory_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account'
|
||||
},
|
||||
cost_method: {
|
||||
label: 'Cost method',
|
||||
column: 'cost_method',
|
||||
options: [{
|
||||
key: 'FIFO', label: 'First-in first-out (FIFO)',
|
||||
key: 'LIFO', label: 'Last-in first-out (LIFO)',
|
||||
key: 'average', label: 'Average rate',
|
||||
}],
|
||||
columnType: 'string',
|
||||
},
|
||||
count: {
|
||||
label: 'Count',
|
||||
column: 'count',
|
||||
sortQuery: this.sortCountQuery
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
sortByCount(query, order = 'asc') {
|
||||
query.orderBy('count', order);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static sortCountQuery(query, role) {
|
||||
query.orderBy('count', role.order);
|
||||
}
|
||||
/**
|
||||
* Model meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return ItemCategorySettings;
|
||||
}
|
||||
}
|
||||
|
||||
54
server/src/models/ManualJournal.Settings.ts
Normal file
54
server/src/models/ManualJournal.Settings.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
export default {
|
||||
defaultFilterField: 'date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
fields: {
|
||||
'date': {
|
||||
label: 'Date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'journal_number': {
|
||||
label: 'Journal number',
|
||||
column: 'journal_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'reference': {
|
||||
label: 'Reference No.',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'journal_type': {
|
||||
label: 'Journal type',
|
||||
column: 'journal_type',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'amount': {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
},
|
||||
'description': {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'status': {
|
||||
label: 'Status',
|
||||
column: 'status',
|
||||
fieldType: 'enumeration',
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
'created_at': {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
return query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { formatNumber } from 'utils';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ManualJournalSettings from './ManualJournal.Settings';
|
||||
|
||||
export default class ManualJournal extends TenantModel {
|
||||
export default class ManualJournal extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -99,52 +101,7 @@ export default class ManualJournal extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
date: {
|
||||
label: 'Date',
|
||||
column: 'date',
|
||||
columnType: 'date',
|
||||
},
|
||||
journal_number: {
|
||||
label: 'Journal number',
|
||||
column: 'journal_number',
|
||||
columnType: 'string',
|
||||
},
|
||||
reference: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference',
|
||||
columnType: 'string',
|
||||
},
|
||||
journal_type: {
|
||||
label: 'Journal type',
|
||||
column: 'journal_type',
|
||||
columnType: 'string',
|
||||
},
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
column: 'status',
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return ManualJournalSettings;
|
||||
}
|
||||
}
|
||||
|
||||
56
server/src/models/ModelSetting.ts
Normal file
56
server/src/models/ModelSetting.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { get } from 'lodash';
|
||||
import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from 'interfaces';
|
||||
|
||||
export default (Model) =>
|
||||
class ModelSettings extends Model {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static get meta(): IModelMeta {
|
||||
throw new Error('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve specific model field meta of the given field key.
|
||||
* @param {string} key
|
||||
* @returns {IModelMetaField}
|
||||
*/
|
||||
public static getField(key: string, attribute?:string): IModelMetaField {
|
||||
const field = get(this.meta.fields, key);
|
||||
|
||||
return attribute ? get(field, attribute) : field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the specific model meta.
|
||||
* @param {string} key
|
||||
* @returns
|
||||
*/
|
||||
public static getMeta(key: string) {
|
||||
return get(this.meta, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model meta fields.
|
||||
* @return {{ [key: string]: IModelMetaField }}
|
||||
*/
|
||||
public static get fields(): { [key: string]: IModelMetaField } {
|
||||
return this.getMeta('fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model default sort settings.
|
||||
* @return {IModelMetaDefaultSort}
|
||||
*/
|
||||
public static get defaultSort(): IModelMetaDefaultSort {
|
||||
return this.getMeta('defaultSort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default filter field key.
|
||||
* @return {string}
|
||||
*/
|
||||
public static get defaultFilterField(): string {
|
||||
return this.getMeta('defaultFilterField');
|
||||
}
|
||||
};
|
||||
57
server/src/models/PaymentReceive.Settings.ts
Normal file
57
server/src/models/PaymentReceive.Settings.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
export default {
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
payment_date: {
|
||||
name: 'Payment date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
deposit_account: {
|
||||
name: 'Deposit account',
|
||||
column: 'deposit_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'depositAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
payment_receive_no: {
|
||||
name: 'Payment receive No.',
|
||||
column: 'payment_receive_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
statement: {
|
||||
name: 'Statement',
|
||||
column: 'statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldDate: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
||||
|
||||
export default class PaymentReceive extends TenantModel {
|
||||
export default class PaymentReceive extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -75,63 +77,9 @@ export default class PaymentReceive extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
customer: {
|
||||
label: 'Customer',
|
||||
column: 'customer_id',
|
||||
relation: 'contacts.id',
|
||||
relationColumn: 'contacts.displayName',
|
||||
|
||||
fieldType: 'options',
|
||||
optionsResource: 'customers',
|
||||
optionsKey: 'id',
|
||||
optionsLable: 'displayName',
|
||||
},
|
||||
payment_date: {
|
||||
label: 'Payment date',
|
||||
column: 'payment_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
deposit_account: {
|
||||
column: 'deposit_account_id',
|
||||
lable: 'Deposit account',
|
||||
relation: "accounts.id",
|
||||
relationColumn: 'accounts.name',
|
||||
optionsResource: "account",
|
||||
},
|
||||
payment_receive_no: {
|
||||
label: 'Payment receive No.',
|
||||
column: 'payment_receive_no',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
label: 'description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return PaymentReceiveSettings;
|
||||
}
|
||||
}
|
||||
|
||||
81
server/src/models/SaleEstimate.Settings.ts
Normal file
81
server/src/models/SaleEstimate.Settings.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
export default {
|
||||
defaultFilterField: 'estimate_date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'estimate_date',
|
||||
},
|
||||
fields: {
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'estimate_number': {
|
||||
name: 'Estimate number',
|
||||
column: 'estimate_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'customer': {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'estimate_date': {
|
||||
name: 'Estimate date',
|
||||
column: 'estimate_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'expiration_date': {
|
||||
name: 'Expiration date',
|
||||
column: 'expiration_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'note': {
|
||||
name: 'Note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'terms_conditions': {
|
||||
name: 'Terms & conditions',
|
||||
column: 'terms_conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'status': {
|
||||
name: 'Status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ name: 'Delivered', key: 'delivered' },
|
||||
{ name: 'Rejected', key: 'rejected' },
|
||||
{ name: 'Approved', key: 'approved' },
|
||||
{ name: 'Delivered', key: 'delivered' },
|
||||
{ name: 'Draft', key: 'draft' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('orderByStatus', role.order);
|
||||
}
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import moment from 'moment';
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { defaultToTransform } from 'utils';
|
||||
import HasItemEntries from 'services/Sales/HasItemsEntries';
|
||||
import { query } from 'winston';
|
||||
import SaleEstimateSettings from './SaleEstimate.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class SaleEstimate extends TenantModel {
|
||||
export default class SaleEstimate extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -29,9 +29,9 @@ export default class SaleEstimate extends TenantModel {
|
||||
'isExpired',
|
||||
'isConvertedToInvoice',
|
||||
'isApproved',
|
||||
'isRejected'
|
||||
'isRejected',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the sale estimate converted to sale invoice.
|
||||
@@ -57,7 +57,7 @@ export default class SaleEstimate extends TenantModel {
|
||||
return defaultToTransform(
|
||||
this.expirationDate,
|
||||
moment().isAfter(this.expirationDate, 'day'),
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,13 +123,38 @@ export default class SaleEstimate extends TenantModel {
|
||||
* Filters the approved estimates transactions.
|
||||
*/
|
||||
approved(query) {
|
||||
query.whereNot('approved_at', null)
|
||||
query.whereNot('approved_at', null);
|
||||
},
|
||||
/**
|
||||
* Sorting the estimates orders by delivery status.
|
||||
*/
|
||||
orderByDraft(query, order) {
|
||||
query.orderByRaw(`delivered_at is null ${order}`)
|
||||
orderByStatus(query, order) {
|
||||
query.orderByRaw(`delivered_at is null ${order}`);
|
||||
},
|
||||
/**
|
||||
* Filtering the estimates oreders by status field.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'approved':
|
||||
query.modify('approved');
|
||||
break;
|
||||
case 'rejected':
|
||||
query.modify('rejected');
|
||||
break;
|
||||
case 'invoiced':
|
||||
query.modify('invoiced');
|
||||
break;
|
||||
case 'expired':
|
||||
query.modify('expired');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -151,7 +176,7 @@ export default class SaleEstimate extends TenantModel {
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
}
|
||||
},
|
||||
},
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
@@ -168,91 +193,9 @@ export default class SaleEstimate extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
estimate_number: {
|
||||
label: 'Estimate number',
|
||||
column: 'estimate_number',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
customer: {
|
||||
label: 'Customer',
|
||||
column: 'customer_id',
|
||||
relation: 'contacts.id',
|
||||
relationColumn: 'contacts.displayName',
|
||||
|
||||
fieldType: 'options',
|
||||
optionsResource: 'customers',
|
||||
optionsKey: 'id',
|
||||
optionsLable: 'displayName',
|
||||
},
|
||||
estimate_date: {
|
||||
label: 'Estimate date',
|
||||
column: 'estimate_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
expiration_date: {
|
||||
label: 'Expiration date',
|
||||
column: 'expiration_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
reference_no: {
|
||||
label: "Reference No.",
|
||||
column: "reference",
|
||||
columnType: "number",
|
||||
fieldType: "number",
|
||||
},
|
||||
note: {
|
||||
label: 'Note',
|
||||
column: 'note',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
label: 'Terms & conditions',
|
||||
column: 'terms_conditions',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
query: (query, role) => {
|
||||
switch(role.value) {
|
||||
case 'draft':
|
||||
query.modify('draft'); break;
|
||||
case 'delivered':
|
||||
query.modify('delivered'); break;
|
||||
case 'approved':
|
||||
query.modify('approved'); break;
|
||||
case 'rejected':
|
||||
query.modify('rejected'); break;
|
||||
case 'invoiced':
|
||||
query.modify('invoiced');
|
||||
break;
|
||||
case 'expired':
|
||||
query.modify('expired'); break;
|
||||
}
|
||||
},
|
||||
sortQuery: (query, role) => {
|
||||
query.modify('orderByDraft', role.order);
|
||||
}
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return SaleEstimateSettings;
|
||||
}
|
||||
}
|
||||
|
||||
100
server/src/models/SaleInvoice.Settings.ts
Normal file
100
server/src/models/SaleInvoice.Settings.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
export default {
|
||||
defaultFilterField: 'customer',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
invoice_date: {
|
||||
name: 'Invoice date',
|
||||
column: 'invoice_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
name: 'Due date',
|
||||
column: 'due_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
invoice_no: {
|
||||
name: 'Invoice No.',
|
||||
column: 'invoice_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
invoice_message: {
|
||||
name: 'Invoice message',
|
||||
column: 'invoice_message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
name: 'Terms & conditions',
|
||||
column: 'terms_conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Invoice amount',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
name: 'Payment amount',
|
||||
column: 'payment_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
due_amount: { // calculated.
|
||||
name: 'Due amount',
|
||||
column: 'due_amount',
|
||||
fieldType: 'number',
|
||||
virtualColumn: true,
|
||||
},
|
||||
status: {
|
||||
name: 'Status',
|
||||
columnable: true,
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', name: 'Draft' },
|
||||
{ key: 'delivered', name: 'Delivered' },
|
||||
{ key: 'unpaid', name: 'Unpaid' },
|
||||
{ key: 'overdue', name: 'Overdue' },
|
||||
{ key: 'partially-paid', name: 'Partially paid' },
|
||||
{ key: 'paid', name: 'Paid' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('statusFilter', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field sort custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import moment from 'moment';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import SaleInvoiceMeta from './SaleInvoice.Settings';
|
||||
|
||||
export default class SaleInvoice extends TenantModel {
|
||||
export default class SaleInvoice extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -106,10 +108,6 @@ export default class SaleInvoice extends TenantModel {
|
||||
return this.getOverdueDays();
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} asDate
|
||||
@@ -229,6 +227,33 @@ export default class SaleInvoice extends TenantModel {
|
||||
byPrefixAndNumber(query, prefix, number) {
|
||||
query.where('invoice_no', `${prefix}${number}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
statusFilter(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
default:
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,11 +263,14 @@ export default class SaleInvoice extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Contact = require('models/Contact');
|
||||
const Customer = require('models/Customer');
|
||||
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sale invoice associated entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
@@ -255,9 +283,12 @@ export default class SaleInvoice extends TenantModel {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Belongs to customer model.
|
||||
*/
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_invoices.customerId',
|
||||
to: 'contacts.id',
|
||||
@@ -267,6 +298,9 @@ export default class SaleInvoice extends TenantModel {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice has associated account transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
@@ -316,125 +350,13 @@ export default class SaleInvoice extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
customer: {
|
||||
label: 'Customer',
|
||||
column: 'customer_id',
|
||||
relation: 'contacts.id',
|
||||
relationColumn: 'contacts.displayName',
|
||||
static get meta() {
|
||||
return SaleInvoiceMeta;
|
||||
}
|
||||
|
||||
fieldType: 'options',
|
||||
optionsResource: 'customers',
|
||||
optionsKey: 'id',
|
||||
optionsLable: 'displayName',
|
||||
},
|
||||
invoice_date: {
|
||||
label: 'Invoice date',
|
||||
column: 'invoice_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
label: 'Due date',
|
||||
column: 'due_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
invoice_no: {
|
||||
label: 'Invoice No.',
|
||||
column: 'invoice_no',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
invoice_message: {
|
||||
label: 'Invoice message',
|
||||
column: 'invoice_message',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
label: 'Terms & conditions',
|
||||
column: 'terms_conditions',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
invoice_amount: {
|
||||
label: 'Invoice amount',
|
||||
column: 'invoice_amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
label: 'Payment amount',
|
||||
column: 'payment_amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
balance: {
|
||||
label: 'Balance',
|
||||
column: 'balance',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
due_amount: {
|
||||
label: 'Due amount',
|
||||
column: 'due_amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByDueAmount', role.order);
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'draft', label: 'Draft' },
|
||||
{ key: 'delivered', label: 'Delivered' },
|
||||
{ key: 'unpaid', label: 'Unpaid' },
|
||||
{ key: 'overdue', label: 'Overdue' },
|
||||
{ key: 'partially-paid', label: 'Partially paid' },
|
||||
{ key: 'paid', label: 'Paid' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
switch (role.value) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
},
|
||||
},
|
||||
};
|
||||
static dueAmountFieldSortQuery(query, role) {
|
||||
query.modify('sortByDueAmount', role.order);
|
||||
}
|
||||
}
|
||||
|
||||
85
server/src/models/SaleReceipt.Settings.ts
Normal file
85
server/src/models/SaleReceipt.Settings.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
export default {
|
||||
defaultFilterField: 'receipt_date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
fields: {
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'deposit_account': {
|
||||
column: 'deposit_account_id',
|
||||
name: 'Deposit account',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'depositAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'customer': {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'receipt_date': {
|
||||
name: 'Receipt date',
|
||||
column: 'receipt_date',
|
||||
fieldType: 'date',
|
||||
|
||||
},
|
||||
'receipt_number': {
|
||||
name: 'Receipt No.',
|
||||
column: 'receipt_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'receipt_message': {
|
||||
name: 'Receipt message',
|
||||
column: 'receipt_message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'statement': {
|
||||
name: 'Statement',
|
||||
column: 'statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'status': {
|
||||
name: 'Status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', name: 'Draft' },
|
||||
{ key: 'closed', name: 'Closed' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import SaleReceiptSettings from './SaleReceipt.Settings';
|
||||
|
||||
export default class SaleReceipt extends TenantModel {
|
||||
export default class SaleReceipt extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -16,14 +18,11 @@ export default class SaleReceipt extends TenantModel {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'isClosed',
|
||||
'isDraft',
|
||||
];
|
||||
return ['isClosed', 'isDraft'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +65,21 @@ export default class SaleReceipt extends TenantModel {
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`CLOSED_AT IS NULL ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filtering the receipts orders by status.
|
||||
*/
|
||||
filterByStatus(query, status) {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'closed':
|
||||
default:
|
||||
query.modify('closed');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -74,7 +88,7 @@ export default class SaleReceipt extends TenantModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Contact = require('models/Contact');
|
||||
const Customer = require('models/Customer');
|
||||
const Account = require('models/Account');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
@@ -82,14 +96,14 @@ export default class SaleReceipt extends TenantModel {
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_receipts.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
depositAccount: {
|
||||
@@ -118,95 +132,19 @@ export default class SaleReceipt extends TenantModel {
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'sales_receipts.id',
|
||||
to: 'accounts_transactions.referenceId'
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleReceipt');
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
deposit_account: {
|
||||
column: 'deposit_account_id',
|
||||
label: 'Deposit account',
|
||||
relation: "accounts.id",
|
||||
optionsResource: "account",
|
||||
},
|
||||
customer: {
|
||||
label: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'options',
|
||||
optionsResource: 'customers',
|
||||
optionsKey: 'id',
|
||||
optionsLable: 'displayName',
|
||||
},
|
||||
receipt_date: {
|
||||
label: 'Receipt date',
|
||||
column: 'receipt_date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
receipt_number: {
|
||||
label: 'Receipt No.',
|
||||
column: 'receipt_number',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
receipt_message: {
|
||||
label: 'Receipt message',
|
||||
column: 'receipt_message',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
statement: {
|
||||
label: 'Statement',
|
||||
column: 'statement',
|
||||
columnType: 'text',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'draft', label: 'Draft', },
|
||||
{ key: 'closed', label: 'Closed' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
switch(role.value) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'closed':
|
||||
query.modify('closed');
|
||||
break;
|
||||
}
|
||||
},
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
}
|
||||
};
|
||||
static get meta() {
|
||||
return SaleReceiptSettings;
|
||||
}
|
||||
}
|
||||
|
||||
89
server/src/models/Vendor.Settings.ts
Normal file
89
server/src/models/Vendor.Settings.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
export default {
|
||||
defaultFilterField: 'display_name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
fields: {
|
||||
'display_name': {
|
||||
name: 'Display name',
|
||||
column: 'display_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'email': {
|
||||
name: 'Email',
|
||||
column: 'email',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'work_phone': {
|
||||
name: 'Work phone',
|
||||
column: 'work_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'personal_phone': {
|
||||
name: 'Personal phone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'company_name': {
|
||||
name: 'Company name',
|
||||
column: 'company_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'website': {
|
||||
name: 'Website',
|
||||
column: 'website',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'balance': {
|
||||
name: 'Balance',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'opening_balance': {
|
||||
name: 'Opening balance',
|
||||
column: 'opening_balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'opening_balance_at': {
|
||||
name: 'Opening balance at',
|
||||
column: 'opening_balance_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'currency_code': {
|
||||
name: 'Currency code',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'status': {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'active', label: 'Active' },
|
||||
{ key: 'inactive', label: 'Inactive' },
|
||||
{ key: 'overdue', label: 'Overdue' },
|
||||
{ key: 'unpaid', label: 'Unpaid' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
switch (role.value) {
|
||||
case 'active':
|
||||
query.modify('active');
|
||||
break;
|
||||
case 'inactive':
|
||||
query.modify('inactive');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Model, QueryBuilder } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import PaginationQueryBuilder from './Pagination';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import VendorSettings from './Vendor.Settings';
|
||||
|
||||
class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||
constructor(...args) {
|
||||
@@ -14,7 +16,7 @@ class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
export default class Vendor extends TenantModel {
|
||||
export default class Vendor extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Query builder.
|
||||
*/
|
||||
@@ -62,6 +64,13 @@ export default class Vendor extends TenantModel {
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('active', !active);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the active customers.
|
||||
*/
|
||||
@@ -125,72 +134,7 @@ export default class Vendor extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
static get fields() {
|
||||
return {
|
||||
contact_service: {
|
||||
column: 'contact_service',
|
||||
},
|
||||
display_name: {
|
||||
column: 'display_name',
|
||||
},
|
||||
email: {
|
||||
column: 'email',
|
||||
},
|
||||
work_phone: {
|
||||
column: 'work_phone',
|
||||
},
|
||||
personal_phone: {
|
||||
column: 'personal_phone',
|
||||
},
|
||||
company_name: {
|
||||
column: 'company_name',
|
||||
},
|
||||
website: {
|
||||
column: 'website'
|
||||
},
|
||||
created_at: {
|
||||
column: 'created_at',
|
||||
},
|
||||
balance: {
|
||||
column: 'balance',
|
||||
},
|
||||
opening_balance: {
|
||||
column: 'opening_balance',
|
||||
},
|
||||
opening_balance_at: {
|
||||
column: 'opening_balance_at',
|
||||
},
|
||||
currency_code: {
|
||||
column: 'currency_code',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ key: 'active', label: 'Active' },
|
||||
{ key: 'inactive', label: 'Inactive' },
|
||||
{ key: 'overdue', label: 'Overdue' },
|
||||
{ key: 'unpaid', label: 'Unpaid' },
|
||||
],
|
||||
query: (query, role) => {
|
||||
switch(role.value) {
|
||||
case 'active':
|
||||
query.modify('active');
|
||||
break;
|
||||
case 'inactive':
|
||||
query.modify('inactive');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
column: 'created_at',
|
||||
}
|
||||
};
|
||||
static get meta() {
|
||||
return VendorSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference, chain, uniq } from 'lodash';
|
||||
import { kebabCase } from 'lodash';
|
||||
import R from 'ramda';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import {
|
||||
@@ -606,6 +607,17 @@ export default class AccountsService {
|
||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsees accounts list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts datatable list.
|
||||
* @param {number} tenantId
|
||||
@@ -613,21 +625,26 @@ export default class AccountsService {
|
||||
*/
|
||||
public async getAccountsList(
|
||||
tenantId: number,
|
||||
filter: IAccountsFilter
|
||||
filterDTO: IAccountsFilter
|
||||
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Account,
|
||||
filter
|
||||
);
|
||||
|
||||
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
||||
tenantId,
|
||||
filter,
|
||||
});
|
||||
const accounts = await Account.query().onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -727,10 +744,11 @@ export default class AccountsService {
|
||||
}));
|
||||
return flatToNestedArray(
|
||||
this.i18nService.i18nMapper(_accounts, ['account_type_label'], tenantId),
|
||||
{
|
||||
id: 'id',
|
||||
parentId: 'parent_account_id',
|
||||
});
|
||||
{
|
||||
id: 'id',
|
||||
parentId: 'parent_account_id',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user