mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 08:10:32 +00:00
Merge branch 'feature/viewDetail'
This commit is contained in:
@@ -11,7 +11,7 @@ const CheckboxEditableCell = ({
|
|||||||
const [value, setValue] = React.useState(initialValue);
|
const [value, setValue] = React.useState(initialValue);
|
||||||
|
|
||||||
const onChange = (e) => {
|
const onChange = (e) => {
|
||||||
setValue(e.target.value);
|
setValue(e.target.checked);
|
||||||
};
|
};
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
payload.updateData(index, id, value);
|
payload.updateData(index, id, value);
|
||||||
@@ -24,12 +24,13 @@ const CheckboxEditableCell = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
// intent={error ? Intent.DANGER : null}
|
intent={error ? Intent.DANGER : null}
|
||||||
className={classNames(Classes.FILL)}
|
className={classNames(Classes.FILL)}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
checked={initialValue}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
className="ml2"
|
className="ml2"
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import AccountDrawer from 'containers/Drawers/AccountDrawer';
|
|||||||
import ManualJournalDrawer from 'containers/Drawers/ManualJournalDrawer';
|
import ManualJournalDrawer from 'containers/Drawers/ManualJournalDrawer';
|
||||||
import ExpenseDrawer from 'containers/Drawers/ExpenseDrawer';
|
import ExpenseDrawer from 'containers/Drawers/ExpenseDrawer';
|
||||||
import BillDrawer from 'containers/Drawers/BillDrawer';
|
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() {
|
export default function DrawersContainer() {
|
||||||
return (
|
return (
|
||||||
@@ -19,6 +23,10 @@ export default function DrawersContainer() {
|
|||||||
<ManualJournalDrawer name={'journal-drawer'} />
|
<ManualJournalDrawer name={'journal-drawer'} />
|
||||||
<ExpenseDrawer name={'expense-drawer'} />
|
<ExpenseDrawer name={'expense-drawer'} />
|
||||||
<BillDrawer name={'bill-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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import 'style/components/Drawers/BillDrawer.scss';
|
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||||
|
|
||||||
import { BillDrawerProvider } from './BillDrawerProvider';
|
import { BillDrawerProvider } from './BillDrawerProvider';
|
||||||
import BillDrawerDetails from './BillDrawerDetails';
|
import BillDrawerDetails from './BillDrawerDetails';
|
||||||
|
|||||||
@@ -3,15 +3,24 @@ import { Tabs, Tab } from '@blueprintjs/core';
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
import LocatedLandedCostTable from './LocatedLandedCostTable';
|
import LocatedLandedCostTable from './LocatedLandedCostTable';
|
||||||
|
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||||
|
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bill view details.
|
* Bill view details.
|
||||||
*/
|
*/
|
||||||
export default function BillDrawerDetails() {
|
export default function BillDrawerDetails() {
|
||||||
|
const { data } = useBillDrawerContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bill-drawer">
|
<div className="view-detail-drawer">
|
||||||
<Tabs animate={true} large={true} selectedTabId="landed_cost">
|
<Tabs animate={true} large={true} defaultSelectedTabId="landed_cost">
|
||||||
<Tab title={intl.get('details')} id={'details'} disabled={true} />
|
<Tab title={intl.get('details')} id={'details'} disabled={true} />
|
||||||
|
<Tab
|
||||||
|
title={intl.get('journal_entries')}
|
||||||
|
id={'journal_entries'}
|
||||||
|
panel={<JournalEntriesTable transactions={data} />}
|
||||||
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
title={intl.get('located_landed_cost')}
|
title={intl.get('located_landed_cost')}
|
||||||
id={'landed_cost'}
|
id={'landed_cost'}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||||
import { useBillLocatedLandedCost } from 'hooks/query';
|
import { useBillLocatedLandedCost } from 'hooks/query';
|
||||||
|
import { useTransactionsByReference } from 'hooks/query';
|
||||||
|
|
||||||
const BillDrawerContext = React.createContext();
|
const BillDrawerContext = React.createContext();
|
||||||
|
|
||||||
@@ -9,6 +10,16 @@ const BillDrawerContext = React.createContext();
|
|||||||
* Bill drawer provider.
|
* Bill drawer provider.
|
||||||
*/
|
*/
|
||||||
function BillDrawerProvider({ billId, ...props }) {
|
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.
|
// Handle fetch bill located landed cost transaction.
|
||||||
const { isLoading: isLandedCostLoading, data: transactions } =
|
const { isLoading: isLandedCostLoading, data: transactions } =
|
||||||
useBillLocatedLandedCost(billId, {
|
useBillLocatedLandedCost(billId, {
|
||||||
@@ -19,10 +30,11 @@ function BillDrawerProvider({ billId, ...props }) {
|
|||||||
const provider = {
|
const provider = {
|
||||||
transactions,
|
transactions,
|
||||||
billId,
|
billId,
|
||||||
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider loading={isLandedCostLoading}>
|
<DashboardInsider loading={isLandedCostLoading || isTransactionLoading}>
|
||||||
<DrawerHeaderContent
|
<DrawerHeaderContent
|
||||||
name="bill-drawer"
|
name="bill-drawer"
|
||||||
title={intl.get('bill_details')}
|
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(
|
const categories = values.categories.filter(
|
||||||
(category) =>
|
(category) =>
|
||||||
category.amount &&
|
category.amount && category.index && category.expense_account_id,
|
||||||
category.index &&
|
|
||||||
category.expense_account_id &&
|
|
||||||
category.landed_cost,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const defaultExpenseEntry = {
|
|||||||
amount: '',
|
amount: '',
|
||||||
expense_account_id: '',
|
expense_account_id: '',
|
||||||
description: '',
|
description: '',
|
||||||
landed_cost: false,
|
landed_cost: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExpense = {
|
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 withPaymentMade from './withPaymentMade';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
import { usePaymentMadesTableColumns, ActionsMenu } from './components';
|
import { usePaymentMadesTableColumns, ActionsMenu } from './components';
|
||||||
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
|
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
|
||||||
|
|
||||||
@@ -27,6 +28,9 @@ function PaymentMadesTable({
|
|||||||
|
|
||||||
// #withAlerts
|
// #withAlerts
|
||||||
openAlert,
|
openAlert,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
}) {
|
}) {
|
||||||
// Payment mades table columns.
|
// Payment mades table columns.
|
||||||
const columns = usePaymentMadesTableColumns();
|
const columns = usePaymentMadesTableColumns();
|
||||||
@@ -53,6 +57,11 @@ function PaymentMadesTable({
|
|||||||
openAlert('payment-made-delete', { paymentMadeId: paymentMade.id });
|
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.
|
// Handle datatable fetch data once the table state change.
|
||||||
const handleDataTableFetchData = useCallback(
|
const handleDataTableFetchData = useCallback(
|
||||||
({ pageIndex, pageSize, sortBy }) => {
|
({ pageIndex, pageSize, sortBy }) => {
|
||||||
@@ -89,6 +98,7 @@ function PaymentMadesTable({
|
|||||||
payload={{
|
payload={{
|
||||||
onEdit: handleEditPaymentMade,
|
onEdit: handleEditPaymentMade,
|
||||||
onDelete: handleDeletePaymentMade,
|
onDelete: handleDeletePaymentMade,
|
||||||
|
onViewDetails: handleViewDetailPaymentMade,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -98,6 +108,7 @@ export default compose(
|
|||||||
withPaymentMadeActions,
|
withPaymentMadeActions,
|
||||||
withPaymentMade(({ paymentMadesTableState }) => ({ paymentMadesTableState })),
|
withPaymentMade(({ paymentMadesTableState }) => ({ paymentMadesTableState })),
|
||||||
withAlertsActions,
|
withAlertsActions,
|
||||||
|
withDrawerActions,
|
||||||
withSettings(({ organizationSettings }) => ({
|
withSettings(({ organizationSettings }) => ({
|
||||||
baseCurrency: organizationSettings?.baseCurrency,
|
baseCurrency: organizationSettings?.baseCurrency,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -26,15 +26,14 @@ export function AmountAccessor(row) {
|
|||||||
*/
|
*/
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
row: { original },
|
row: { original },
|
||||||
payload: { onEdit, onDelete },
|
payload: { onEdit, onDelete, onViewDetails },
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="reader-18" />}
|
icon={<Icon icon="reader-18" />}
|
||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -70,8 +69,6 @@ export function ActionsCell(props) {
|
|||||||
* Retrieve payment mades table columns.
|
* Retrieve payment mades table columns.
|
||||||
*/
|
*/
|
||||||
export function usePaymentMadesTableColumns() {
|
export function usePaymentMadesTableColumns() {
|
||||||
|
|
||||||
|
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ function InvoicesDataTable({
|
|||||||
const handleQuickPaymentReceive = ({ id }) => {
|
const handleQuickPaymentReceive = ({ id }) => {
|
||||||
openDialog('quick-payment-receive', { invoiceId: 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.
|
// Handles fetch data once the table state change.
|
||||||
const handleDataTableFetchData = useCallback(
|
const handleDataTableFetchData = useCallback(
|
||||||
({ pageSize, pageIndex, sortBy }) => {
|
({ pageSize, pageIndex, sortBy }) => {
|
||||||
@@ -123,6 +129,7 @@ function InvoicesDataTable({
|
|||||||
onEdit: handleEditInvoice,
|
onEdit: handleEditInvoice,
|
||||||
onDrawer: handleDrawerInvoice,
|
onDrawer: handleDrawerInvoice,
|
||||||
onQuick: handleQuickPaymentReceive,
|
onQuick: handleQuickPaymentReceive,
|
||||||
|
onViewDetails: handleViewDetailInvoice,
|
||||||
baseCurrency,
|
baseCurrency,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const handleDeleteErrors = (errors) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
payload: { onEdit, onDeliver, onDelete, onDrawer, onQuick },
|
payload: { onEdit, onDeliver, onDelete, onDrawer, onQuick, onViewDetails },
|
||||||
row: { original },
|
row: { original },
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -103,6 +103,7 @@ export function ActionsMenu({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="reader-18" />}
|
icon={<Icon icon="reader-18" />}
|
||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ function PaymentReceivesDataTable({
|
|||||||
openDrawer('payment-receive-drawer', { paymentReceiveId: id });
|
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.
|
// Handle datatable fetch once the table's state changing.
|
||||||
const handleDataTableFetchData = useCallback(
|
const handleDataTableFetchData = useCallback(
|
||||||
({ pageIndex, pageSize, sortBy }) => {
|
({ pageIndex, pageSize, sortBy }) => {
|
||||||
@@ -103,6 +108,7 @@ function PaymentReceivesDataTable({
|
|||||||
onDelete: handleDeletePaymentReceive,
|
onDelete: handleDeletePaymentReceive,
|
||||||
onEdit: handleEditPaymentReceive,
|
onEdit: handleEditPaymentReceive,
|
||||||
onDrawer: handleDrawerPaymentReceive,
|
onDrawer: handleDrawerPaymentReceive,
|
||||||
|
onViewDetails: handleViewDetailPaymentReceive,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,15 +19,14 @@ import { safeCallback } from 'utils';
|
|||||||
*/
|
*/
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
row: { original: paymentReceive },
|
row: { original: paymentReceive },
|
||||||
payload: { onEdit, onDelete, onDrawer },
|
payload: { onEdit, onDelete, onDrawer, onViewDetails },
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="reader-18" />}
|
icon={<Icon icon="reader-18" />}
|
||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
|
onClick={safeCallback(onViewDetails, paymentReceive)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -82,8 +81,6 @@ export function ActionsCell(props) {
|
|||||||
* Retrieve payment receives columns.
|
* Retrieve payment receives columns.
|
||||||
*/
|
*/
|
||||||
export function usePaymentReceivesColumns() {
|
export function usePaymentReceivesColumns() {
|
||||||
|
|
||||||
|
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -128,7 +125,7 @@ export function usePaymentReceivesColumns() {
|
|||||||
accessor: 'reference_no',
|
accessor: 'reference_no',
|
||||||
width: 140,
|
width: 140,
|
||||||
className: 'reference_no',
|
className: 'reference_no',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,14 +60,19 @@ function ReceiptsDataTable({
|
|||||||
openAlert('receipt-delete', { receiptId: receipt.id });
|
openAlert('receipt-delete', { receiptId: receipt.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle drawer receipts.
|
||||||
|
const handleDrawerReceipt = ({ id }) => {
|
||||||
|
openDrawer('receipt-drawer', { receiptId: id });
|
||||||
|
};
|
||||||
|
|
||||||
// Handles receipt close action.
|
// Handles receipt close action.
|
||||||
const handleCloseReceipt = (receipt) => {
|
const handleCloseReceipt = (receipt) => {
|
||||||
openAlert('receipt-close', { receiptId: receipt.id });
|
openAlert('receipt-close', { receiptId: receipt.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle drawer receipts.
|
// Handle view detail receipt.
|
||||||
const handleDrawerReceipt = ({ id }) => {
|
const handleViewDetailReceipt = ({ id }) => {
|
||||||
openDrawer('receipt-drawer', { receiptId: id });
|
openDrawer('receipt-detail-drawer', { receiptId: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the datable fetch data once the state changing.
|
// Handles the datable fetch data once the state changing.
|
||||||
@@ -112,6 +117,7 @@ function ReceiptsDataTable({
|
|||||||
onDelete: handleDeleteReceipt,
|
onDelete: handleDeleteReceipt,
|
||||||
onClose: handleCloseReceipt,
|
onClose: handleCloseReceipt,
|
||||||
onDrawer: handleDrawerReceipt,
|
onDrawer: handleDrawerReceipt,
|
||||||
|
onViewDetails: handleViewDetailReceipt,
|
||||||
baseCurrency,
|
baseCurrency,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,16 +16,15 @@ import { Choose, Money, Icon, If } from 'components';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
payload: { onEdit, onDelete, onClose, onDrawer },
|
payload: { onEdit, onDelete, onClose, onDrawer, onViewDetails },
|
||||||
row: { original: receipt },
|
row: { original: receipt },
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="reader-18" />}
|
icon={<Icon icon="reader-18" />}
|
||||||
text={intl.get('view_details')}
|
text={intl.get('view_details')}
|
||||||
|
onClick={safeCallback(onViewDetails, receipt)}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -94,8 +93,6 @@ export function StatusAccessor(receipt) {
|
|||||||
* Retrieve receipts table columns.
|
* Retrieve receipts table columns.
|
||||||
*/
|
*/
|
||||||
export function useReceiptsTableColumns() {
|
export function useReceiptsTableColumns() {
|
||||||
|
|
||||||
|
|
||||||
return React.useMemo(
|
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',
|
INVENTORY_VALUATION: 'INVENTORY_VALUATION',
|
||||||
CASH_FLOW_STATEMENT: 'CASH_FLOW_STATEMENT',
|
CASH_FLOW_STATEMENT: 'CASH_FLOW_STATEMENT',
|
||||||
INVENTORY_ITEM_DETAILS: 'INVENTORY_ITEM_DETAILS',
|
INVENTORY_ITEM_DETAILS: 'INVENTORY_ITEM_DETAILS',
|
||||||
|
TRANSACTIONS_BY_REFERENCE: 'TRANSACTIONS_BY_REFERENCE',
|
||||||
};
|
};
|
||||||
|
|
||||||
const BILLS = {
|
const BILLS = {
|
||||||
|
|||||||
@@ -1166,5 +1166,11 @@
|
|||||||
"From transaction": "From transaction",
|
"From transaction": "From transaction",
|
||||||
"Landed": "Landed",
|
"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.",
|
"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 = () => [
|
const getSubscriptionPlans = () => [
|
||||||
{
|
{
|
||||||
name: intl.get('Starter'),
|
name: intl.get('Starter'),
|
||||||
slug: 'starter',
|
slug: 'free',
|
||||||
|
// slug: 'starter',
|
||||||
description: [
|
description: [
|
||||||
intl.get('Sale and purchase invoices.'),
|
intl.get('Sale and purchase invoices.'),
|
||||||
intl.get('Customers/vendors accounts.'),
|
intl.get('Customers/vendors accounts.'),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
@import '../../Base.scss';
|
@import '../../../Base.scss';
|
||||||
|
|
||||||
.bill-drawer {
|
.view-detail-drawer {
|
||||||
.bp3-tabs {
|
.bp3-tabs {
|
||||||
.bp3-tab-list {
|
.bp3-tab-list {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #FFF;
|
background-color: #fff;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -31,10 +31,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-tab-panel{
|
.bp3-tab-panel {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|
||||||
.card{
|
.card {
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
|
|
||||||
.datatable--landed-cost-transactions {
|
.datatable--landed-cost-transactions {
|
||||||
.table {
|
.table {
|
||||||
|
|
||||||
.tbody,
|
.tbody,
|
||||||
.tbody-inner {
|
.tbody-inner {
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
.tr .td {
|
.tr .td {
|
||||||
padding: 0.6rem;
|
padding: 0.6rem;
|
||||||
|
|
||||||
&.amount{
|
&.amount {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,9 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/transactions',
|
'/transactions',
|
||||||
[
|
[query('account_id').optional().isInt().toInt()],
|
||||||
query('account_id').optional().isInt().toInt(),
|
|
||||||
],
|
|
||||||
this.asyncMiddleware(this.accountTransactions.bind(this)),
|
this.asyncMiddleware(this.accountTransactions.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/activate',
|
'/:id/activate',
|
||||||
@@ -136,6 +134,8 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
query('column_sort_by').optional(),
|
query('column_sort_by').optional(),
|
||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +213,9 @@ export default class AccountsController extends BaseController {
|
|||||||
tenantId,
|
tenantId,
|
||||||
accountId
|
accountId
|
||||||
);
|
);
|
||||||
return res.status(200).send({ account: this.transfromToResponse(account) });
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send({ account: this.transfromToResponse(account) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -256,7 +258,7 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: accountId,
|
id: accountId,
|
||||||
message: 'The account has been activated successfully.'
|
message: 'The account has been activated successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -291,22 +293,24 @@ export default class AccountsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Response}
|
* @param {Response}
|
||||||
*/
|
*/
|
||||||
async getAccountsList(req: Request, res: Response, next: NextFunction) {
|
public async getAccountsList(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter: IAccountsFilter = {
|
|
||||||
filterRoles: [],
|
// Filter query.
|
||||||
|
const filter = {
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'name',
|
columnSortBy: 'name',
|
||||||
|
inactiveMode: false,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { accounts, filterMeta } =
|
||||||
accounts,
|
await this.accountsService.getAccountsList(tenantId, filter);
|
||||||
filterMeta,
|
|
||||||
} = await this.accountsService.getAccountsList(tenantId, filter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
accounts: this.transfromToResponse(accounts, 'accountTypeLabel', req),
|
accounts: this.transfromToResponse(accounts, 'accountTypeLabel', req),
|
||||||
@@ -353,7 +357,8 @@ export default class AccountsController extends BaseController {
|
|||||||
const transactionsFilter = this.matchedQueryData(req);
|
const transactionsFilter = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { transactions } = await this.accountsService.getAccountsTransactions(
|
const { transactions } =
|
||||||
|
await this.accountsService.getAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
transactionsFilter
|
transactionsFilter
|
||||||
);
|
);
|
||||||
@@ -372,7 +377,12 @@ export default class AccountsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {ServiceError} error
|
* @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 instanceof ServiceError) {
|
||||||
if (error.errorType === 'account_not_found') {
|
if (error.errorType === 'account_not_found') {
|
||||||
return res.boom.notFound('The given 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('custom_view_id').optional().isNumeric().toInt(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
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) {
|
async getCustomersList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
inactiveMode: false,
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export default class VendorsController extends ContactsController {
|
|||||||
|
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').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) {
|
async getVendorsList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
const vendorsFilter: IVendorsFilter = {
|
const vendorsFilter: IVendorsFilter = {
|
||||||
filterRoles: [],
|
inactiveMode: false,
|
||||||
|
sortOrder: 'asc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
page: 1,
|
||||||
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -290,16 +290,12 @@ export default class ExpensesController extends BaseController {
|
|||||||
async getExpensesList(req: Request, res: Response, next: NextFunction) {
|
async getExpensesList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { expenses, pagination, filterMeta } =
|
const { expenses, pagination, filterMeta } =
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import TransactionsByCustomers from './FinancialStatements/TransactionsByCustome
|
|||||||
import TransactionsByVendors from './FinancialStatements/TransactionsByVendors';
|
import TransactionsByVendors from './FinancialStatements/TransactionsByVendors';
|
||||||
import CashFlowStatementController from './FinancialStatements/CashFlow/CashFlow';
|
import CashFlowStatementController from './FinancialStatements/CashFlow/CashFlow';
|
||||||
import InventoryDetailsController from './FinancialStatements/InventoryDetails';
|
import InventoryDetailsController from './FinancialStatements/InventoryDetails';
|
||||||
|
import TransactionsByReferenceController from './FinancialStatements/TransactionsByReference';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class FinancialStatementsService {
|
export default class FinancialStatementsService {
|
||||||
@@ -87,6 +88,10 @@ export default class FinancialStatementsService {
|
|||||||
'/inventory-item-details',
|
'/inventory-item-details',
|
||||||
Container.get(InventoryDetailsController).router(),
|
Container.get(InventoryDetailsController).router(),
|
||||||
);
|
);
|
||||||
|
router.use(
|
||||||
|
'/transactions-by-reference',
|
||||||
|
Container.get(TransactionsByReferenceController).router(),
|
||||||
|
)
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,8 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
|||||||
return [
|
return [
|
||||||
query('from_date').optional().isISO8601(),
|
query('from_date').optional().isISO8601(),
|
||||||
query('to_date').optional().isISO8601(),
|
query('to_date').optional().isISO8601(),
|
||||||
oneOf(
|
query('transaction_type').optional().trim().escape(),
|
||||||
[
|
query('transaction_id').optional().isInt().toInt(),
|
||||||
query('transaction_types').optional().isArray({ min: 1 }),
|
|
||||||
query('transaction_types.*').optional().isNumeric().toInt(),
|
|
||||||
],
|
|
||||||
[query('transaction_types').optional().trim().escape()]
|
|
||||||
),
|
|
||||||
oneOf(
|
oneOf(
|
||||||
[
|
[
|
||||||
query('account_ids').optional().isArray({ min: 1 }),
|
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').optional().isNumeric().toInt(),
|
||||||
query('page_size').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) {
|
async getList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
|
|
||||||
const itemCategoriesFilter = {
|
const itemCategoriesFilter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ export default class ItemsController extends BaseController {
|
|||||||
|
|
||||||
query('custom_view_id').optional().isNumeric().toInt(),
|
query('custom_view_id').optional().isNumeric().toInt(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
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) {
|
async getItemsList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
|
inactiveMode: false,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
|||||||
@@ -288,14 +288,10 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
filterRoles: [],
|
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
manualJournals,
|
manualJournals,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query } from 'express-validator';
|
import { check, param, query } from 'express-validator';
|
||||||
|
import * as R from 'ramda';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { IBillDTO, IBillEditDTO } from 'interfaces';
|
import { IBillDTO, IBillEditDTO } from 'interfaces';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
@@ -300,14 +301,11 @@ export default class BillsController extends BaseController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const { bills, pagination, filterMeta } =
|
const { bills, pagination, filterMeta } =
|
||||||
await this.billsService.getBills(tenantId, filter);
|
await this.billsService.getBills(tenantId, filter);
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import {
|
import { param, query } from 'express-validator';
|
||||||
param,
|
|
||||||
query,
|
|
||||||
} from 'express-validator';
|
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import BaseController from './BaseController';
|
import BaseController from './BaseController';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import ResourceService from 'services/Resource/ResourceService';
|
import ResourceService from 'services/Resource/ResourceService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ResourceController extends BaseController{
|
export default class ResourceController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
resourcesService: ResourceService;
|
resourcesService: ResourceService;
|
||||||
|
|
||||||
@@ -21,30 +18,61 @@ export default class ResourceController extends BaseController{
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:resource_model/fields', [
|
'/:resource_model/meta',
|
||||||
...this.resourceModelParamSchema,
|
[...this.resourceModelParamSchema],
|
||||||
],
|
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:resource_model/fields',
|
||||||
|
[...this.resourceModelParamSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.resourceFields.bind(this)),
|
asyncMiddleware(this.resourceFields.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:resource_model/data', [
|
'/:resource_model/data',
|
||||||
...this.resourceModelParamSchema,
|
[...this.resourceModelParamSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.resourceData.bind(this)),
|
asyncMiddleware(this.resourceData.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
)
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
get resourceModelParamSchema() {
|
get resourceModelParamSchema() {
|
||||||
return [
|
return [param('resource_model').exists().trim().escape()];
|
||||||
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.
|
* Retrieve resource fields of the given resource.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -56,7 +84,10 @@ export default class ResourceController extends BaseController{
|
|||||||
const { resource_model: resourceModel } = req.params;
|
const { resource_model: resourceModel } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resourceFields = this.resourcesService.getResourceFields(tenantId, resourceModel);
|
const resourceFields = this.resourcesService.getResourceFields(
|
||||||
|
tenantId,
|
||||||
|
resourceModel
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
resource_fields: this.transfromToResponse(resourceFields),
|
resource_fields: this.transfromToResponse(resourceFields),
|
||||||
@@ -78,7 +109,11 @@ export default class ResourceController extends BaseController{
|
|||||||
const filter = req.query;
|
const filter = req.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resourceData = await this.resourcesService.getResourceData(tenantId, resourceModel, filter);
|
const resourceData = await this.resourcesService.getResourceData(
|
||||||
|
tenantId,
|
||||||
|
resourceModel,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
resource_data: this.transfromToResponse(resourceData),
|
resource_data: this.transfromToResponse(resourceData),
|
||||||
@@ -95,7 +130,12 @@ export default class ResourceController extends BaseController{
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @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 instanceof ServiceError) {
|
||||||
if (error.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
if (error.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
@@ -105,4 +145,4 @@ export default class ResourceController extends BaseController{
|
|||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -261,16 +261,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { check, param, query, matchedData } from 'express-validator';
|
import { check, param, query, matchedData } from 'express-validator';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { ISaleEstimateDTO } from 'interfaces';
|
import { ISaleEstimateDTO } from 'interfaces';
|
||||||
import BaseController from 'api/controllers/BaseController'
|
import BaseController from 'api/controllers/BaseController';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from "exceptions";
|
import { ServiceError } from 'exceptions';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SalesEstimatesController extends BaseController {
|
export default class SalesEstimatesController extends BaseController {
|
||||||
@@ -23,63 +23,56 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/', [
|
'/',
|
||||||
...this.estimateValidationSchema,
|
[...this.estimateValidationSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newEstimate.bind(this)),
|
asyncMiddleware(this.newEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/deliver',
|
'/:id/deliver',
|
||||||
[
|
[...this.validateSpecificEstimateSchema],
|
||||||
...this.validateSpecificEstimateSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deliverSaleEstimate.bind(this)),
|
asyncMiddleware(this.deliverSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/approve',
|
'/:id/approve',
|
||||||
[
|
[this.validateSpecificEstimateSchema],
|
||||||
this.validateSpecificEstimateSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/reject',
|
'/:id/reject',
|
||||||
[
|
[this.validateSpecificEstimateSchema],
|
||||||
this.validateSpecificEstimateSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
)
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id', [
|
'/:id',
|
||||||
|
[
|
||||||
...this.validateSpecificEstimateSchema,
|
...this.validateSpecificEstimateSchema,
|
||||||
...this.estimateValidationSchema,
|
...this.estimateValidationSchema,
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editEstimate.bind(this)),
|
asyncMiddleware(this.editEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id', [
|
'/:id',
|
||||||
this.validateSpecificEstimateSchema,
|
[this.validateSpecificEstimateSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteEstimate.bind(this)),
|
asyncMiddleware(this.deleteEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.validateSpecificEstimateSchema,
|
this.validateSpecificEstimateSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getEstimate.bind(this)),
|
asyncMiddleware(this.getEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
@@ -87,7 +80,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getEstimates.bind(this)),
|
asyncMiddleware(this.getEstimates.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
this.dynamicListService.handlerErrorsToResponse,
|
this.dynamicListService.handlerErrorsToResponse
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -109,8 +102,14 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.description').optional({ nullable: true }).trim().escape(),
|
check('entries.*.description')
|
||||||
check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
|
.optional({ nullable: true })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
check('entries.*.discount')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isNumeric()
|
||||||
|
.toFloat(),
|
||||||
|
|
||||||
check('note').optional().trim().escape(),
|
check('note').optional().trim().escape(),
|
||||||
check('terms_conditions').optional().trim().escape(),
|
check('terms_conditions').optional().trim().escape(),
|
||||||
@@ -122,9 +121,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* Specific sale estimate validation schema.
|
* Specific sale estimate validation schema.
|
||||||
*/
|
*/
|
||||||
get validateSpecificEstimateSchema() {
|
get validateSpecificEstimateSchema() {
|
||||||
return [
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
param('id').exists().isNumeric().toInt(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,7 +135,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
query('page').optional().isNumeric().toInt(),
|
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);
|
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedEstimate = await this.saleEstimateService.createEstimate(tenantId, estimateDTO);
|
const storedEstimate = await this.saleEstimateService.createEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedEstimate.id,
|
id: storedEstimate.id,
|
||||||
@@ -175,7 +175,11 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Update estimate with associated estimate entries.
|
// 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({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
@@ -200,7 +204,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
message: 'The sale estimate has been deleted successfully.'
|
message: 'The sale estimate has been deleted successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -283,7 +287,10 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const estimate = await this.saleEstimateService.getEstimate(tenantId, estimateId);
|
const estimate = await this.saleEstimateService.getEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({ estimate });
|
return res.status(200).send({ estimate });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -299,29 +306,22 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { salesEstimates, pagination, filterMeta } =
|
||||||
salesEstimates,
|
await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||||
pagination,
|
|
||||||
filterMeta
|
|
||||||
} = await this.saleEstimateService.estimatesList(tenantId, filter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_estimates: this.transfromToResponse(salesEstimates),
|
sales_estimates: this.transfromToResponse(salesEstimates),
|
||||||
pagination,
|
pagination,
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
})
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,12 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @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 instanceof ServiceError) {
|
||||||
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
@@ -409,4 +414,4 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -284,23 +284,15 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { salesInvoices, filterMeta, pagination } =
|
||||||
salesInvoices,
|
await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||||
filterMeta,
|
|
||||||
pagination,
|
|
||||||
} = await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_invoices: salesInvoices,
|
sales_invoices: salesInvoices,
|
||||||
|
|||||||
@@ -230,16 +230,12 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface IAccountResponse extends IAccount {
|
|||||||
|
|
||||||
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string,
|
stringifiedFilterRoles?: string,
|
||||||
|
onlyInactive: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IAccountType {
|
export interface IAccountType {
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export interface IBill {
|
|||||||
|
|
||||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBillsService {
|
export interface IBillsService {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { IModel, ISortOrder } from "./Model";
|
||||||
|
|
||||||
export interface IDynamicFilter {
|
export interface IDynamicFilter {
|
||||||
setTableName(tableName: string): void;
|
setModel(model: IModel): void;
|
||||||
buildQuery(): void;
|
buildQuery(): void;
|
||||||
|
getResponseMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFilterRole {
|
export interface IFilterRole {
|
||||||
@@ -10,19 +13,19 @@ export interface IFilterRole {
|
|||||||
index?: number;
|
index?: number;
|
||||||
comparator?: string;
|
comparator?: string;
|
||||||
}
|
}
|
||||||
|
export interface IDynamicListFilter {
|
||||||
export interface IDynamicListFilterDTO {
|
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
filterRoles?: IFilterRole[];
|
filterRoles?: IFilterRole[];
|
||||||
columnSortBy: string;
|
columnSortBy: ISortOrder;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
|
stringifiedFilterRoles: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDynamicListService {
|
export interface IDynamicListService {
|
||||||
dynamicList(
|
dynamicList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
model: any,
|
model: any,
|
||||||
filter: IDynamicListFilterDTO
|
filter: IDynamicListFilter
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
handlerErrorsToResponse(error, req, res, next): void;
|
handlerErrorsToResponse(error, req, res, next): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export interface IItemsFilter extends IDynamicListFilterDTO {
|
|||||||
stringifiedFilterRoles?: string,
|
stringifiedFilterRoles?: string,
|
||||||
page: number,
|
page: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
|
inactiveMode: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IItemsAutoCompleteFilter {
|
export interface IItemsAutoCompleteFilter {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export interface IJournalReportQuery {
|
|||||||
noCents: boolean,
|
noCents: boolean,
|
||||||
divideOn1000: boolean,
|
divideOn1000: boolean,
|
||||||
},
|
},
|
||||||
transactionTypes: string | string[],
|
transactionType: string,
|
||||||
|
transactionId: string,
|
||||||
|
|
||||||
accountsIds: number | number[],
|
accountsIds: number | number[],
|
||||||
fromRange: number,
|
fromRange: number,
|
||||||
toRange: number,
|
toRange: number,
|
||||||
|
|||||||
@@ -1,17 +1,81 @@
|
|||||||
|
|
||||||
|
|
||||||
export interface IModel {
|
export interface IModel {
|
||||||
name: string,
|
name: string;
|
||||||
tableName: string,
|
tableName: string;
|
||||||
fields: { [key: string]: any, },
|
fields: { [key: string]: any };
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IFilterMeta {
|
export interface IFilterMeta {
|
||||||
sortOrder: string,
|
sortOrder: string;
|
||||||
sortBy: string,
|
sortBy: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IPaginationMeta {
|
export interface IPaginationMeta {
|
||||||
pageSize: number,
|
pageSize: number;
|
||||||
page: 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 './InventoryDetails';
|
||||||
export * from './LandedCost';
|
export * from './LandedCost';
|
||||||
export * from './Entry';
|
export * from './Entry';
|
||||||
|
export * from './TransactionsByReference';
|
||||||
|
|
||||||
export interface I18nService {
|
export interface I18nService {
|
||||||
__: (input: string) => string;
|
__: (input: string) => string;
|
||||||
|
|||||||
@@ -1,65 +1,85 @@
|
|||||||
import { forEach, uniqBy } from 'lodash';
|
import { forEach, uniqBy } from 'lodash';
|
||||||
import { buildFilterRolesJoins } from 'lib/ViewRolesBuilder';
|
import DynamicFilterAbstructor from './DynamicFilterAbstructor';
|
||||||
import { IModel } from 'interfaces';
|
import { IDynamicFilter, IFilterRole, IModel } from 'interfaces';
|
||||||
|
|
||||||
export default class DynamicFilter {
|
export default class DynamicFilter extends DynamicFilterAbstructor{
|
||||||
model: IModel;
|
private model: IModel;
|
||||||
tableName: string;
|
private tableName: string;
|
||||||
|
private dynamicFilters: IDynamicFilter[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param {String} tableName -
|
* @param {String} tableName -
|
||||||
*/
|
*/
|
||||||
constructor(model) {
|
constructor(model) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.tableName = model.tableName;
|
this.tableName = model.tableName;
|
||||||
this.filters = [];
|
this.dynamicFilters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set filter.
|
* Registers the given dynamic filter.
|
||||||
* @param {*} filterRole - Filter role.
|
* @param {IDynamicFilter} filterRole - Filter role.
|
||||||
*/
|
*/
|
||||||
setFilter(filterRole) {
|
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||||
filterRole.setModel(this.model);
|
dynamicFilter.setModel(this.model);
|
||||||
this.filters.push(filterRole);
|
|
||||||
|
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.
|
* Builds queries of filter roles.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
public buildQuery = () => {
|
||||||
const buildersCallbacks = [];
|
const buildersCallbacks = this.dynamicFiltersBuildQuery();
|
||||||
const tableColumns = [];
|
const tableColumns = this.dynamicFilterTableColumns();
|
||||||
|
|
||||||
this.filters.forEach((filter) => {
|
|
||||||
const { filterRoles } = filter;
|
|
||||||
|
|
||||||
buildersCallbacks.push(filter.buildQuery());
|
|
||||||
tableColumns.push(
|
|
||||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
buildersCallbacks.forEach((builderCallback) => {
|
buildersCallbacks.forEach((builderCallback) => {
|
||||||
builderCallback(builder);
|
builderCallback(builder);
|
||||||
});
|
});
|
||||||
|
this.buildFilterRolesJoins(builder);
|
||||||
buildFilterRolesJoins(
|
|
||||||
this.model,
|
|
||||||
uniqBy(tableColumns, 'columnKey')
|
|
||||||
)(builder);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve response metadata from all filters adapters.
|
* Retrieve response metadata from all filters adapters.
|
||||||
*/
|
*/
|
||||||
getResponseMeta() {
|
public getResponseMeta = () => {
|
||||||
const responseMeta = {};
|
const responseMeta = {};
|
||||||
|
|
||||||
this.filters.forEach((filter) => {
|
this.dynamicFilters.forEach((filter) => {
|
||||||
const { responseMeta: filterMeta } = filter;
|
const { responseMeta: filterMeta } = filter;
|
||||||
|
|
||||||
forEach(filterMeta, (value, key) => {
|
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 './DynamicFilterRoleAbstructor';
|
||||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
|
||||||
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
|
||||||
import { IFilterRole } from 'interfaces';
|
import { IFilterRole } from 'interfaces';
|
||||||
|
|
||||||
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||||
filterRoles: IFilterRole[];
|
private filterRoles: IFilterRole[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -15,9 +13,14 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.filterRoles = filterRoles;
|
this.filterRoles = filterRoles;
|
||||||
|
|
||||||
this.setResponseMeta();
|
this.setResponseMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onInitialize() {
|
||||||
|
this.setFilterRolesRelations();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds filter roles logic expression.
|
* Builds filter roles logic expression.
|
||||||
* @return {string}
|
* @return {string}
|
||||||
@@ -35,19 +38,33 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
/**
|
/**
|
||||||
* Builds database query of view roles.
|
* Builds database query of view roles.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
protected buildQuery() {
|
||||||
return (builder) => {
|
|
||||||
const logicExpression = this.buildLogicExpression();
|
const logicExpression = this.buildLogicExpression();
|
||||||
buildFilterQuery(this.model, this.filterRoles, logicExpression)(builder);
|
|
||||||
|
return (builder) => {
|
||||||
|
this.buildFilterQuery(
|
||||||
|
this.model,
|
||||||
|
this.filterRoles,
|
||||||
|
logicExpression
|
||||||
|
)(builder);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets response meta.
|
* Sets response meta.
|
||||||
*/
|
*/
|
||||||
setResponseMeta() {
|
private setResponseMeta() {
|
||||||
this.responseMeta = {
|
this.responseMeta = {
|
||||||
filterRoles: this.filterRoles,
|
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 {
|
export default abstract class DynamicFilterAbstructor
|
||||||
filterRoles: IFilterRole[] = [];
|
implements IDynamicFilter
|
||||||
tableName: string;
|
{
|
||||||
model: IModel;
|
protected filterRoles: IFilterRole[] = [];
|
||||||
responseMeta: { [key: string]: any } = {};
|
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.model = model;
|
||||||
this.tableName = model.tableName;
|
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 DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||||
import {
|
import { FIELD_TYPE } from './constants';
|
||||||
getRoleFieldColumn,
|
|
||||||
validateFieldKeyExistance,
|
interface ISortRole {
|
||||||
getTableFromRelationColumn,
|
fieldKey: string;
|
||||||
} from 'lib/ViewRolesBuilder';
|
order: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||||
sortRole: { fieldKey: string; order: string } = {};
|
private sortRole: ISortRole = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* 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() {
|
public onInitialize() {
|
||||||
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
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.
|
* Builds database query of sort by column on the given direction.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
public buildQuery = () => {
|
||||||
const fieldRelation = getRoleFieldColumn(
|
const field = this.model.getField(this.sortRole.fieldKey);
|
||||||
this.model,
|
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||||
this.sortRole.fieldKey
|
|
||||||
);
|
|
||||||
const comparatorColumn =
|
|
||||||
fieldRelation.relationColumn ||
|
|
||||||
`${this.tableName}.${fieldRelation.column}`;
|
|
||||||
|
|
||||||
if (typeof fieldRelation.sortQuery !== 'undefined') {
|
// Sort custom query.
|
||||||
|
if (typeof field.sortCustomQuery !== 'undefined') {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
fieldRelation.sortQuery(builder, this.sortRole);
|
field.sortCustomQuery(builder, this.sortRole);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
if (this.sortRole.fieldKey) {
|
if (this.sortRole.fieldKey) {
|
||||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
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.
|
* Sets response meta.
|
||||||
*/
|
*/
|
||||||
setResponseMeta() {
|
public setResponseMeta() {
|
||||||
this.responseMeta = {
|
this.responseMeta = {
|
||||||
sortOrder: this.sortRole.fieldKey,
|
sortOrder: this.sortRole.fieldKey,
|
||||||
sortBy: this.sortRole.order,
|
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 { 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';
|
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.
|
* Get field column metadata and its relation with other tables.
|
||||||
* @param {String} tableName - Table name of target column.
|
* @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;
|
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) {
|
export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
const fieldColumn = getRoleFieldColumn(model, sortColumnKey);
|
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.
|
* Mapes the view roles to view conditionals.
|
||||||
* @param {Array} viewRoles -
|
* @param {Array} viewRoles -
|
||||||
@@ -316,14 +96,6 @@ export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
|||||||
return model?.fields?.[fieldKey] || false;
|
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.
|
* 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 */
|
/* eslint-disable global-require */
|
||||||
import { Model } from 'objection';
|
import { mixin, Model } from 'objection';
|
||||||
import { flatten, castArray } from 'lodash';
|
import { castArray } from 'lodash';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import {
|
import { buildFilterQuery, buildSortColumnQuery } from 'lib/ViewRolesBuilder';
|
||||||
buildFilterQuery,
|
|
||||||
buildSortColumnQuery,
|
|
||||||
} from 'lib/ViewRolesBuilder';
|
|
||||||
import { flatToNestedArray } from 'utils';
|
import { flatToNestedArray } from 'utils';
|
||||||
import DependencyGraph from 'lib/DependencyGraph';
|
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.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -21,7 +21,7 @@ export default class Account extends TenantModel {
|
|||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
static get timestamps() {
|
||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export default class Account extends TenantModel {
|
|||||||
'accountRootType',
|
'accountRootType',
|
||||||
'accountNormal',
|
'accountNormal',
|
||||||
'isBalanceSheetAccount',
|
'isBalanceSheetAccount',
|
||||||
'isPLSheet'
|
'isPLSheet',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +95,13 @@ export default class Account extends TenantModel {
|
|||||||
const TABLE_NAME = Account.tableName;
|
const TABLE_NAME = Account.tableName;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('accounts.active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
filterAccounts(query, accountIds) {
|
filterAccounts(query, accountIds) {
|
||||||
if (accountIds.length > 0) {
|
if (accountIds.length > 0) {
|
||||||
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||||
@@ -111,6 +118,28 @@ export default class Account extends TenantModel {
|
|||||||
sortColumnBuilder(query, columnKey, direction) {
|
sortColumnBuilder(query, columnKey, direction) {
|
||||||
buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
|
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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +189,10 @@ export default class Account extends TenantModel {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
isParentType(parentType) {
|
isParentType(parentType) {
|
||||||
return AccountTypesUtils.isParentTypeEqualsKey(this.accountType, parentType);
|
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||||
|
this.accountType,
|
||||||
|
parentType
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,7 +225,10 @@ export default class Account extends TenantModel {
|
|||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
static toNestedArray(accounts, options = { children: 'children' }) {
|
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||||
return flatToNestedArray(accounts, { id: 'id', parentId: 'parentAccountId' })
|
return flatToNestedArray(accounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,92 +236,16 @@ export default class Account extends TenantModel {
|
|||||||
* @param {IAccount[]} accounts
|
* @param {IAccount[]} accounts
|
||||||
*/
|
*/
|
||||||
static toDependencyGraph(accounts) {
|
static toDependencyGraph(accounts) {
|
||||||
return DependencyGraph.fromArray(
|
return DependencyGraph.fromArray(accounts, {
|
||||||
accounts, { itemId: 'id', parentItemId: 'parentAccountId' }
|
itemId: 'id',
|
||||||
);
|
parentItemId: 'parentAccountId',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return AccountSettings;
|
||||||
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' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 moment from 'moment';
|
||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
import TenantModel from 'models/TenantModel';
|
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
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -12,10 +13,9 @@ export default class Bill extends TenantModel {
|
|||||||
return 'bills';
|
return 'bills';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get resourceable() {
|
/**
|
||||||
return true;
|
* Model modifiers.
|
||||||
}
|
*/
|
||||||
|
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +71,7 @@ export default class Bill extends TenantModel {
|
|||||||
* Filters the bills from the given date.
|
* Filters the bills from the given date.
|
||||||
*/
|
*/
|
||||||
fromDate(query, fromDate) {
|
fromDate(query, fromDate) {
|
||||||
query.where('bill_date', '<=', fromDate)
|
query.where('bill_date', '<=', fromDate);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,6 +80,33 @@ export default class Bill extends TenantModel {
|
|||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`PAYMENT_AMOUNT = AMOUNT ${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',
|
'remainingDays',
|
||||||
'overdueDays',
|
'overdueDays',
|
||||||
'isOverdue',
|
'isOverdue',
|
||||||
'unallocatedCostAmount'
|
'unallocatedCostAmount',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +225,13 @@ export default class Bill extends TenantModel {
|
|||||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill model settings.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return BillSettings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
@@ -269,88 +303,4 @@ export default class Bill extends TenantModel {
|
|||||||
.where('id', billId)
|
.where('id', billId)
|
||||||
[changeMethod]('payment_amount', Math.abs(amount));
|
[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 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
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -16,8 +18,11 @@ export default class BillPayment extends TenantModel {
|
|||||||
return ["createdAt", "updatedAt"];
|
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() {
|
static get relationMappings() {
|
||||||
const BillPaymentEntry = require("models/BillPaymentEntry");
|
const BillPaymentEntry = require("models/BillPaymentEntry");
|
||||||
const AccountTransaction = require("models/AccountTransaction");
|
const AccountTransaction = require("models/AccountTransaction");
|
||||||
const Contact = require("models/Contact");
|
const Vendor = require("models/Vendor");
|
||||||
const Account = require("models/Account");
|
const Account = require("models/Account");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -41,7 +46,7 @@ export default class BillPayment extends TenantModel {
|
|||||||
|
|
||||||
vendor: {
|
vendor: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Contact.default,
|
modelClass: Vendor.default,
|
||||||
join: {
|
join: {
|
||||||
from: "bills_payments.vendorId",
|
from: "bills_payments.vendorId",
|
||||||
to: "contacts.id",
|
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 TenantModel from 'models/TenantModel';
|
||||||
import PaginationQueryBuilder from './Pagination';
|
import PaginationQueryBuilder from './Pagination';
|
||||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import CustomerSettings from './Customer.Settings';
|
||||||
|
|
||||||
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||||
constructor(...args) {
|
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.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +65,13 @@ export default class Customer extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the active customers.
|
* Filters the active customers.
|
||||||
*/
|
*/
|
||||||
@@ -81,10 +90,9 @@ export default class Customer extends TenantModel {
|
|||||||
overdue(query) {
|
overdue(query) {
|
||||||
query.select(
|
query.select(
|
||||||
'*',
|
'*',
|
||||||
Customer
|
Customer.relatedQuery('overDueInvoices', query.knex())
|
||||||
.relatedQuery('overDueInvoices', query.knex())
|
|
||||||
.count()
|
.count()
|
||||||
.as('countOverdue'),
|
.as('countOverdue')
|
||||||
);
|
);
|
||||||
query.having('countOverdue', '>', 0);
|
query.having('countOverdue', '>', 0);
|
||||||
},
|
},
|
||||||
@@ -93,7 +101,7 @@ export default class Customer extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
unpaid(query) {
|
unpaid(query) {
|
||||||
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,77 +130,12 @@ export default class Customer extends TenantModel {
|
|||||||
},
|
},
|
||||||
filter: (query) => {
|
filter: (query) => {
|
||||||
query.modify('overdue');
|
query.modify('overdue');
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return CustomerSettings;
|
||||||
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',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 TenantModel from 'models/TenantModel';
|
||||||
import { viewRolesBuilder } from 'lib/ViewRolesBuilder';
|
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
|
* 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() {
|
static get virtualAttributes() {
|
||||||
return ['isPublished', 'unallocatedCostAmount'];
|
return ['isPublished', 'unallocatedCostAmount'];
|
||||||
}
|
}
|
||||||
@@ -96,6 +87,18 @@ export default class Expense extends TenantModel {
|
|||||||
filterByPublished(query) {
|
filterByPublished(query) {
|
||||||
query.whereNot('published_at', null);
|
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 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static get meta() {
|
||||||
* Model defined fields.
|
return ExpenseSettings;
|
||||||
*/
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 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
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -40,8 +42,8 @@ export default class InventoryAdjustment extends TenantModel {
|
|||||||
|
|
||||||
static getInventoryDirection(type) {
|
static getInventoryDirection(type) {
|
||||||
const directions = {
|
const directions = {
|
||||||
'increment': 'IN',
|
increment: 'IN',
|
||||||
'decrement': 'OUT',
|
decrement: 'OUT',
|
||||||
};
|
};
|
||||||
return directions[type] || '';
|
return directions[type] || '';
|
||||||
}
|
}
|
||||||
@@ -81,52 +83,9 @@ export default class InventoryAdjustment extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return InventoryAdjustmentSettings;
|
||||||
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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 { Model, mixin } from 'objection';
|
||||||
import TenantModel from "models/TenantModel";
|
import TenantModel from 'models/TenantModel';
|
||||||
import { buildFilterQuery } from "lib/ViewRolesBuilder";
|
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
|
* Table name
|
||||||
*/
|
*/
|
||||||
static get tableName() {
|
static get tableName() {
|
||||||
return "items";
|
return 'items';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model timestamps.
|
* Model timestamps.
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
get timestamps() {
|
||||||
return ["createdAt", "updatedAt"];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +37,13 @@ export default class Item extends TenantModel {
|
|||||||
viewRolesBuilder(query, conditions, logicExpression) {
|
viewRolesBuilder(query, conditions, logicExpression) {
|
||||||
buildFilterQuery(Item.tableName, conditions, logicExpression)(query);
|
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.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
const Media = require("models/Media");
|
const Media = require('models/Media');
|
||||||
const Account = require("models/Account");
|
const Account = require('models/Account');
|
||||||
const ItemCategory = require("models/ItemCategory");
|
const ItemCategory = require('models/ItemCategory');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -54,8 +63,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: ItemCategory.default,
|
modelClass: ItemCategory.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.categoryId",
|
from: 'items.categoryId',
|
||||||
to: "items_categories.id",
|
to: 'items_categories.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -63,8 +72,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.costAccountId",
|
from: 'items.costAccountId',
|
||||||
to: "accounts.id",
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -72,8 +81,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.sellAccountId",
|
from: 'items.sellAccountId',
|
||||||
to: "accounts.id",
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -81,8 +90,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.inventoryAccountId",
|
from: 'items.inventoryAccountId',
|
||||||
to: "accounts.id",
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -90,110 +99,21 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.ManyToManyRelation,
|
relation: Model.ManyToManyRelation,
|
||||||
modelClass: Media.default,
|
modelClass: Media.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.id",
|
from: 'items.id',
|
||||||
through: {
|
through: {
|
||||||
from: "media_links.model_id",
|
from: 'media_links.model_id',
|
||||||
to: "media_links.media_id",
|
to: 'media_links.media_id',
|
||||||
},
|
},
|
||||||
to: "media.id",
|
to: 'media.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return ItemSettings;
|
||||||
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",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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, mixin } from 'objection';
|
||||||
import { Model } from 'objection';
|
|
||||||
import TenantModel from 'models/TenantModel';
|
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.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -10,10 +11,6 @@ export default class ItemCategory extends TenantModel {
|
|||||||
return 'items_categories';
|
return 'items_categories';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get resourceable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
*/
|
*/
|
||||||
@@ -43,68 +40,23 @@ export default class ItemCategory extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item category fields.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
name: {
|
/**
|
||||||
label: 'Name',
|
* Inactive/Active mode.
|
||||||
column: 'name',
|
*/
|
||||||
columnType: 'string'
|
sortByCount(query, order = 'asc') {
|
||||||
},
|
query.orderBy('count', order);
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 TenantModel from 'models/TenantModel';
|
||||||
import { formatNumber } from 'utils';
|
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.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -99,52 +101,7 @@ export default class ManualJournal extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static get meta() {
|
||||||
* Model defined fields.
|
return ManualJournalSettings;
|
||||||
*/
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 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.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -75,63 +77,9 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
*
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return PaymentReceiveSettings;
|
||||||
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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 moment from 'moment';
|
||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import { defaultToTransform } from 'utils';
|
import { defaultToTransform } from 'utils';
|
||||||
import HasItemEntries from 'services/Sales/HasItemsEntries';
|
import SaleEstimateSettings from './SaleEstimate.Settings';
|
||||||
import { query } from 'winston';
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class SaleEstimate extends TenantModel {
|
export default class SaleEstimate extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -29,7 +29,7 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
'isExpired',
|
'isExpired',
|
||||||
'isConvertedToInvoice',
|
'isConvertedToInvoice',
|
||||||
'isApproved',
|
'isApproved',
|
||||||
'isRejected'
|
'isRejected',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
return defaultToTransform(
|
return defaultToTransform(
|
||||||
this.expirationDate,
|
this.expirationDate,
|
||||||
moment().isAfter(this.expirationDate, 'day'),
|
moment().isAfter(this.expirationDate, 'day'),
|
||||||
false,
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,13 +123,38 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
* Filters the approved estimates transactions.
|
* Filters the approved estimates transactions.
|
||||||
*/
|
*/
|
||||||
approved(query) {
|
approved(query) {
|
||||||
query.whereNot('approved_at', null)
|
query.whereNot('approved_at', null);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sorting the estimates orders by delivery status.
|
* Sorting the estimates orders by delivery status.
|
||||||
*/
|
*/
|
||||||
orderByDraft(query, order) {
|
orderByStatus(query, order) {
|
||||||
query.orderByRaw(`delivered_at is null ${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) {
|
filter(query) {
|
||||||
query.where('contact_service', 'customer');
|
query.where('contact_service', 'customer');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
entries: {
|
entries: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -168,91 +193,9 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return SaleEstimateSettings;
|
||||||
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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 moment from 'moment';
|
||||||
import TenantModel from 'models/TenantModel';
|
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
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -106,10 +108,6 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
return this.getOverdueDays();
|
return this.getOverdueDays();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get resourceable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} asDate
|
* @param {*} asDate
|
||||||
@@ -229,6 +227,33 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
byPrefixAndNumber(query, prefix, number) {
|
byPrefixAndNumber(query, prefix, number) {
|
||||||
query.where('invoice_no', `${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() {
|
static get relationMappings() {
|
||||||
const AccountTransaction = require('models/AccountTransaction');
|
const AccountTransaction = require('models/AccountTransaction');
|
||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const Contact = require('models/Contact');
|
const Customer = require('models/Customer');
|
||||||
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
||||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Sale invoice associated entries.
|
||||||
|
*/
|
||||||
entries: {
|
entries: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: ItemEntry.default,
|
modelClass: ItemEntry.default,
|
||||||
@@ -255,9 +283,12 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Belongs to customer model.
|
||||||
|
*/
|
||||||
customer: {
|
customer: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Contact.default,
|
modelClass: Customer.default,
|
||||||
join: {
|
join: {
|
||||||
from: 'sales_invoices.customerId',
|
from: 'sales_invoices.customerId',
|
||||||
to: 'contacts.id',
|
to: 'contacts.id',
|
||||||
@@ -267,6 +298,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice has associated account transactions.
|
||||||
|
*/
|
||||||
transactions: {
|
transactions: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: AccountTransaction.default,
|
modelClass: AccountTransaction.default,
|
||||||
@@ -316,125 +350,13 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Sale invoice meta.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return SaleInvoiceMeta;
|
||||||
customer: {
|
|
||||||
label: 'Customer',
|
|
||||||
column: 'customer_id',
|
|
||||||
relation: 'contacts.id',
|
|
||||||
relationColumn: 'contacts.displayName',
|
|
||||||
|
|
||||||
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) {
|
static dueAmountFieldSortQuery(query, role) {
|
||||||
query.modify('sortByStatus', role.order);
|
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 { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
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
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -20,10 +22,7 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return [
|
return ['isClosed', 'isDraft'];
|
||||||
'isClosed',
|
|
||||||
'isDraft',
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,6 +65,21 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`CLOSED_AT IS NULL ${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.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
const Contact = require('models/Contact');
|
const Customer = require('models/Customer');
|
||||||
const Account = require('models/Account');
|
const Account = require('models/Account');
|
||||||
const AccountTransaction = require('models/AccountTransaction');
|
const AccountTransaction = require('models/AccountTransaction');
|
||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
@@ -82,14 +96,14 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
return {
|
return {
|
||||||
customer: {
|
customer: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Contact.default,
|
modelClass: Customer.default,
|
||||||
join: {
|
join: {
|
||||||
from: 'sales_receipts.customerId',
|
from: 'sales_receipts.customerId',
|
||||||
to: 'contacts.id',
|
to: 'contacts.id',
|
||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.where('contact_service', 'customer');
|
query.where('contact_service', 'customer');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
depositAccount: {
|
depositAccount: {
|
||||||
@@ -118,95 +132,19 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
modelClass: AccountTransaction.default,
|
modelClass: AccountTransaction.default,
|
||||||
join: {
|
join: {
|
||||||
from: 'sales_receipts.id',
|
from: 'sales_receipts.id',
|
||||||
to: 'accounts_transactions.referenceId'
|
to: 'accounts_transactions.referenceId',
|
||||||
},
|
},
|
||||||
filter(builder) {
|
filter(builder) {
|
||||||
builder.where('reference_type', 'SaleReceipt');
|
builder.where('reference_type', 'SaleReceipt');
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Sale invoice meta.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return SaleReceiptSettings;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 TenantModel from 'models/TenantModel';
|
||||||
import PaginationQueryBuilder from './Pagination';
|
import PaginationQueryBuilder from './Pagination';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import VendorSettings from './Vendor.Settings';
|
||||||
|
|
||||||
class VendorQueryBuilder extends PaginationQueryBuilder {
|
class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||||
constructor(...args) {
|
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.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
@@ -62,6 +64,13 @@ export default class Vendor extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the active customers.
|
* Filters the active customers.
|
||||||
*/
|
*/
|
||||||
@@ -125,72 +134,7 @@ export default class Vendor extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return VendorSettings;
|
||||||
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',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { difference, chain, uniq } from 'lodash';
|
import { difference, chain, uniq } from 'lodash';
|
||||||
import { kebabCase } from 'lodash';
|
import { kebabCase } from 'lodash';
|
||||||
|
import R from 'ramda';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import {
|
import {
|
||||||
@@ -606,6 +607,17 @@ export default class AccountsService {
|
|||||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
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.
|
* Retrieve accounts datatable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -613,21 +625,26 @@ export default class AccountsService {
|
|||||||
*/
|
*/
|
||||||
public async getAccountsList(
|
public async getAccountsList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: IAccountsFilter
|
filterDTO: IAccountsFilter
|
||||||
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
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(
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
Account,
|
Account,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
filter,
|
filter,
|
||||||
});
|
});
|
||||||
const accounts = await Account.query().onBuild((builder) => {
|
const accounts = await Account.query().onBuild((builder) => {
|
||||||
dynamicList.buildQuery()(builder);
|
dynamicList.buildQuery()(builder);
|
||||||
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -730,7 +747,8 @@ export default class AccountsService {
|
|||||||
{
|
{
|
||||||
id: 'id',
|
id: 'id',
|
||||||
parentId: 'parent_account_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