mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
Compare commits
26 Commits
accounts-b
...
import-fie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
079491823d | ||
|
|
f7a87a6e9c | ||
|
|
e0cdf42980 | ||
|
|
ee56653f4b | ||
|
|
2310b09778 | ||
|
|
0684e50ebd | ||
|
|
aaa8f39e50 | ||
|
|
af981ce630 | ||
|
|
a1f8417b5d | ||
|
|
086b060351 | ||
|
|
bbafdcd8bd | ||
|
|
dd9098bdc1 | ||
|
|
3851d34ba4 | ||
|
|
b9651f30d5 | ||
|
|
45b5fb4088 | ||
|
|
aa64bcf69b | ||
|
|
cbd867b334 | ||
|
|
1a8ca83786 | ||
|
|
80c14ba1a0 | ||
|
|
785045dbad | ||
|
|
291301c1e3 | ||
|
|
824e4e13d1 | ||
|
|
74da28b464 | ||
|
|
22a016b56e | ||
|
|
040f016273 | ||
|
|
8ab809fc71 |
1
packages/server/.gitignore
vendored
1
packages/server/.gitignore
vendored
@@ -3,3 +3,4 @@
|
|||||||
stdout.log
|
stdout.log
|
||||||
/dist
|
/dist
|
||||||
/build
|
/build
|
||||||
|
/public/imports
|
||||||
BIN
packages/server/public/.DS_Store
vendored
Normal file
BIN
packages/server/public/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
packages/server/public/imports/.DS_Store
vendored
Normal file
BIN
packages/server/public/imports/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -244,26 +244,28 @@
|
|||||||
"account.field.active": "Active",
|
"account.field.active": "Active",
|
||||||
"account.field.currency": "Currency",
|
"account.field.currency": "Currency",
|
||||||
"account.field.balance": "Balance",
|
"account.field.balance": "Balance",
|
||||||
|
"account.field.parent_account": "Parent Account",
|
||||||
"account.field.created_at": "Created at",
|
"account.field.created_at": "Created at",
|
||||||
"item.field.type": "Item type",
|
"item.field.type": "Item Type",
|
||||||
"item.field.type.inventory": "Inventory",
|
"item.field.type.inventory": "Inventory",
|
||||||
"item.field.type.service": "Service",
|
"item.field.type.service": "Service",
|
||||||
"item.field.type.non-inventory": "Non inventory",
|
"item.field.type.non-inventory": "Non Inventory",
|
||||||
"item.field.name": "Name",
|
"item.field.name": "Item Name",
|
||||||
"item.field.code": "Code",
|
"item.field.code": "Item Code",
|
||||||
"item.field.sellable": "Sellable",
|
"item.field.sellable": "Sellable",
|
||||||
"item.field.purchasable": "Purchasable",
|
"item.field.purchasable": "Purchasable",
|
||||||
"item.field.cost_price": "Cost price",
|
"item.field.cost_price": "Cost Price",
|
||||||
"item.field.cost_account": "Cost account",
|
"item.field.sell_price": "Sell Price",
|
||||||
"item.field.sell_account": "Sell account",
|
"item.field.cost_account": "Cost Account",
|
||||||
"item.field.sell_description": "Sell description",
|
"item.field.sell_account": "Sell Account",
|
||||||
"item.field.inventory_account": "Inventory account",
|
"item.field.sell_description": "Sell Description",
|
||||||
"item.field.purchase_description": "Purchase description",
|
"item.field.inventory_account": "Inventory Account",
|
||||||
"item.field.quantity_on_hand": "Quantity on hand",
|
"item.field.purchase_description": "Purchase Description",
|
||||||
|
"item.field.quantity_on_hand": "Quantity on Hand",
|
||||||
"item.field.note": "Note",
|
"item.field.note": "Note",
|
||||||
"item.field.category": "Category",
|
"item.field.category": "Category",
|
||||||
"item.field.active": "Active",
|
"item.field.active": "Active",
|
||||||
"item.field.created_at": "Created at",
|
"item.field.created_at": "Created At",
|
||||||
"item_category.field.name": "Name",
|
"item_category.field.name": "Name",
|
||||||
"item_category.field.description": "Description",
|
"item_category.field.description": "Description",
|
||||||
"item_category.field.count": "Count",
|
"item_category.field.count": "Count",
|
||||||
@@ -276,8 +278,14 @@
|
|||||||
"invoice.field.invoice_message": "Invoice message",
|
"invoice.field.invoice_message": "Invoice message",
|
||||||
"invoice.field.terms_conditions": "Terms & conditions",
|
"invoice.field.terms_conditions": "Terms & conditions",
|
||||||
"invoice.field.amount": "Amount",
|
"invoice.field.amount": "Amount",
|
||||||
|
"invoice.field.exchange_rate": "Exchange Rate",
|
||||||
"invoice.field.payment_amount": "Payment amount",
|
"invoice.field.payment_amount": "Payment amount",
|
||||||
"invoice.field.due_amount": "Due amount",
|
"invoice.field.due_amount": "Due amount",
|
||||||
|
"invoice.field.delivered": "Delivered",
|
||||||
|
"invoice.field.item_name": "Item Name",
|
||||||
|
"invoice.field.rate": "Rate",
|
||||||
|
"invoice.field.quantity": "Quantity",
|
||||||
|
"invoice.field.description": "Description",
|
||||||
"invoice.field.status": "Status",
|
"invoice.field.status": "Status",
|
||||||
"invoice.field.status.paid": "Paid",
|
"invoice.field.status.paid": "Paid",
|
||||||
"invoice.field.status.partially-paid": "Partially paid",
|
"invoice.field.status.partially-paid": "Partially paid",
|
||||||
@@ -286,6 +294,8 @@
|
|||||||
"invoice.field.status.delivered": "Delivered",
|
"invoice.field.status.delivered": "Delivered",
|
||||||
"invoice.field.status.draft": "Draft",
|
"invoice.field.status.draft": "Draft",
|
||||||
"invoice.field.created_at": "Created at",
|
"invoice.field.created_at": "Created at",
|
||||||
|
"invoice.field.currency": "Currency",
|
||||||
|
"invoice.field.entries": "Entries",
|
||||||
"estimate.field.amount": "Amount",
|
"estimate.field.amount": "Amount",
|
||||||
"estimate.field.estimate_number": "Estimate number",
|
"estimate.field.estimate_number": "Estimate number",
|
||||||
"estimate.field.customer": "Customer",
|
"estimate.field.customer": "Customer",
|
||||||
@@ -300,22 +310,31 @@
|
|||||||
"estimate.field.status.approved": "Approved",
|
"estimate.field.status.approved": "Approved",
|
||||||
"estimate.field.status.draft": "Draft",
|
"estimate.field.status.draft": "Draft",
|
||||||
"estimate.field.created_at": "Created at",
|
"estimate.field.created_at": "Created at",
|
||||||
"payment_receive.field.customer": "Customer",
|
|
||||||
"payment_receive.field.payment_date": "Payment date",
|
|
||||||
"payment_receive.field.amount": "Amount",
|
"payment_receive.field.amount": "Amount",
|
||||||
"payment_receive.field.reference_no": "Reference No.",
|
|
||||||
"payment_receive.field.deposit_account": "Deposit account",
|
|
||||||
"payment_receive.field.payment_receive_no": "Payment receive No.",
|
"payment_receive.field.payment_receive_no": "Payment receive No.",
|
||||||
"payment_receive.field.statement": "Statement",
|
"payment_receive.field.statement": "Statement",
|
||||||
"payment_receive.field.created_at": "Created at",
|
"payment_receive.field.created_at": "Created at",
|
||||||
|
"payment_receive.field.customer": "Customer",
|
||||||
|
"payment_receive.field.exchange_rate": "Exchange Rate",
|
||||||
|
"payment_receive.field.payment_date": "Payment Date",
|
||||||
|
"payment_receive.field.reference_no": "Reference No.",
|
||||||
|
"payment_receive.field.deposit_account": "Deposit Account",
|
||||||
|
"payment_receive.field.entries": "Entries",
|
||||||
|
"payment_receive.field.invoice": "Invoice",
|
||||||
|
"payment_receive.field.entries.payment_amount": "Payment Amount",
|
||||||
"bill_payment.field.vendor": "Vendor",
|
"bill_payment.field.vendor": "Vendor",
|
||||||
"bill_payment.field.amount": "Amount",
|
"bill_payment.field.amount": "Amount",
|
||||||
"bill_payment.field.due_amount": "Due amount",
|
"bill_payment.field.due_amount": "Due Amount",
|
||||||
"bill_payment.field.payment_account": "Payment account",
|
"bill_payment.field.payment_account": "Payment Account",
|
||||||
"bill_payment.field.payment_number": "Payment number",
|
"bill_payment.field.payment_number": "Payment No.",
|
||||||
"bill_payment.field.payment_date": "Payment date",
|
"bill_payment.field.payment_date": "Payment Date",
|
||||||
"bill_payment.field.reference_no": "Reference No.",
|
"bill_payment.field.reference_no": "Reference No.",
|
||||||
"bill_payment.field.description": "Description",
|
"bill_payment.field.description": "Description",
|
||||||
|
"bill_payment.field.exchange_rate": "Exchange Rate",
|
||||||
|
"bill_payment.field.statement": "Statement",
|
||||||
|
"bill_payment.field.entries.bill": "Bill No.",
|
||||||
|
"bill_payment.field.entries.payment_amount": "Payment Amount",
|
||||||
|
"bill_payment.field.reference": "Reference No.",
|
||||||
"bill_payment.field.created_at": "Created at",
|
"bill_payment.field.created_at": "Created at",
|
||||||
"bill.field.vendor": "Vendor",
|
"bill.field.vendor": "Vendor",
|
||||||
"bill.field.bill_number": "Bill number",
|
"bill.field.bill_number": "Bill number",
|
||||||
@@ -343,22 +362,30 @@
|
|||||||
"inventory_adjustment.field.description": "Description",
|
"inventory_adjustment.field.description": "Description",
|
||||||
"inventory_adjustment.field.published_at": "Published at",
|
"inventory_adjustment.field.published_at": "Published at",
|
||||||
"inventory_adjustment.field.created_at": "Created at",
|
"inventory_adjustment.field.created_at": "Created at",
|
||||||
"expense.field.payment_date": "Payment date",
|
"expense.field.payment_date": "Payment Date",
|
||||||
"expense.field.payment_account": "Payment account",
|
"expense.field.payment_account": "Payment Account",
|
||||||
"expense.field.amount": "Amount",
|
"expense.field.amount": "Amount",
|
||||||
|
"expense.field.currency_code": "Currency",
|
||||||
|
"expense.field.exchange_rate": "Exchange Rate",
|
||||||
"expense.field.reference_no": "Reference No.",
|
"expense.field.reference_no": "Reference No.",
|
||||||
"expense.field.description": "Description",
|
"expense.field.description": "Description",
|
||||||
|
"expense.field.line_description": "Line Description",
|
||||||
"expense.field.published": "Published",
|
"expense.field.published": "Published",
|
||||||
|
"expense.field.categories": "Categories",
|
||||||
|
"expense.field.expense_account": "Expense Account",
|
||||||
|
"expense.field.publish": "Publish",
|
||||||
"expense.field.status": "Status",
|
"expense.field.status": "Status",
|
||||||
"expense.field.status.draft": "Draft",
|
"expense.field.status.draft": "Draft",
|
||||||
"expense.field.status.published": "Published",
|
"expense.field.status.published": "Published",
|
||||||
"expense.field.created_at": "Created at",
|
"expense.field.created_at": "Created at",
|
||||||
"manual_journal.field.date": "Date",
|
"manual_journal.field.date": "Date",
|
||||||
"manual_journal.field.journal_number": "Journal number",
|
"manual_journal.field.journal_number": "Journal No.",
|
||||||
"manual_journal.field.reference": "Reference No.",
|
"manual_journal.field.reference": "Reference No.",
|
||||||
"manual_journal.field.journal_type": "Journal type",
|
"manual_journal.field.journal_type": "Journal Type",
|
||||||
"manual_journal.field.amount": "Amount",
|
"manual_journal.field.amount": "Amount",
|
||||||
"manual_journal.field.description": "Description",
|
"manual_journal.field.description": "Description",
|
||||||
|
"manual_journal.field.currency": "Currency",
|
||||||
|
"manual_journal.field.exchange_rate": "Exchange Rate",
|
||||||
"manual_journal.field.status": "Status",
|
"manual_journal.field.status": "Status",
|
||||||
"manual_journal.field.created_at": "Created at",
|
"manual_journal.field.created_at": "Created at",
|
||||||
"receipt.field.amount": "Amount",
|
"receipt.field.amount": "Amount",
|
||||||
@@ -411,6 +438,8 @@
|
|||||||
"vendor.field.status.unpaid": "Unpaid",
|
"vendor.field.status.unpaid": "Unpaid",
|
||||||
"Invoice write-off": "Invoice write-off",
|
"Invoice write-off": "Invoice write-off",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"transaction_type.credit_note": "Credit note",
|
"transaction_type.credit_note": "Credit note",
|
||||||
"transaction_type.refund_credit_note": "Refund credit note",
|
"transaction_type.refund_credit_note": "Refund credit note",
|
||||||
"transaction_type.vendor_credit": "Vendor credit",
|
"transaction_type.vendor_credit": "Vendor credit",
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export default class AccountsController extends BaseController {
|
|||||||
// Filter query.
|
// Filter query.
|
||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'createdAt',
|
columnSortBy: 'created_at',
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
structure: IAccountsStructureType.Tree,
|
structure: IAccountsStructureType.Tree,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ export default class CustomersController extends ContactsController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'createdAt',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export default class VendorsController extends ContactsController {
|
|||||||
const vendorsFilter: IVendorsFilter = {
|
const vendorsFilter: IVendorsFilter = {
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'createdAt',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export class ImportController extends BaseController {
|
|||||||
[
|
[
|
||||||
param('import_id').exists().isString(),
|
param('import_id').exists().isString(),
|
||||||
body('mapping').exists().isArray({ min: 1 }),
|
body('mapping').exists().isArray({ min: 1 }),
|
||||||
|
body('mapping.*.group').optional(),
|
||||||
body('mapping.*.from').exists(),
|
body('mapping.*.from').exists(),
|
||||||
body('mapping.*.to').exists(),
|
body('mapping.*.to').exists(),
|
||||||
],
|
],
|
||||||
@@ -47,6 +48,7 @@ export class ImportController extends BaseController {
|
|||||||
router.get(
|
router.get(
|
||||||
'/sample',
|
'/sample',
|
||||||
[query('resource').exists(), query('format').optional()],
|
[query('resource').exists(), query('format').optional()],
|
||||||
|
this.validationResult,
|
||||||
this.downloadImportSample.bind(this),
|
this.downloadImportSample.bind(this),
|
||||||
this.catchServiceErrors
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,17 +5,28 @@ export function allowSheetExtensions(req, file, cb) {
|
|||||||
if (
|
if (
|
||||||
file.mimetype !== 'text/csv' &&
|
file.mimetype !== 'text/csv' &&
|
||||||
file.mimetype !== 'application/vnd.ms-excel' &&
|
file.mimetype !== 'application/vnd.ms-excel' &&
|
||||||
file.mimetype !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
file.mimetype !==
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
) {
|
) {
|
||||||
cb(new ServiceError('IMPORTED_FILE_EXTENSION_INVALID'));
|
cb(new ServiceError('IMPORTED_FILE_EXTENSION_INVALID'));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cb(null, true);
|
cb(null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storage = Multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, './public/imports');
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
// Add the creation timestamp to clean up temp files later.
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
cb(null, uniqueSuffix);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const uploadImportFile = Multer({
|
export const uploadImportFile = Multer({
|
||||||
dest: './public/imports',
|
storage,
|
||||||
limits: { fileSize: 5 * 1024 * 1024 },
|
limits: { fileSize: 5 * 1024 * 1024 },
|
||||||
fileFilter: allowSheetExtensions,
|
fileFilter: allowSheetExtensions,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import ItemTransactionsController from './ItemsTransactions';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ItemsBaseController {
|
export default class ItemsBaseController {
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/', Container.get(ItemsController).router());
|
router.use('/', Container.get(ItemsController).router());
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ export interface IModelMetaFieldCommon {
|
|||||||
name: string;
|
name: string;
|
||||||
column: string;
|
column: string;
|
||||||
columnable?: boolean;
|
columnable?: boolean;
|
||||||
fieldType: IModelColumnType;
|
|
||||||
customQuery?: Function;
|
customQuery?: Function;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
importHint?: string;
|
importHint?: string;
|
||||||
|
importableRelationLabel?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
|
unique?: number;
|
||||||
|
dataTransferObjectKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModelMetaFieldText {
|
export interface IModelMetaFieldText {
|
||||||
@@ -67,6 +69,7 @@ export type IModelMetaField = IModelMetaFieldCommon &
|
|||||||
| IModelMetaFieldUrl
|
| IModelMetaFieldUrl
|
||||||
| IModelMetaEnumerationField
|
| IModelMetaEnumerationField
|
||||||
| IModelMetaRelationField
|
| IModelMetaRelationField
|
||||||
|
| IModelMetaCollectionField
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface IModelMetaEnumerationOption {
|
export interface IModelMetaEnumerationOption {
|
||||||
@@ -90,12 +93,71 @@ export interface IModelMetaRelationEnumerationField {
|
|||||||
relationEntityKey: string;
|
relationEntityKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaFieldWithFields {
|
||||||
|
fields: IModelMetaFieldCommon2 &
|
||||||
|
(
|
||||||
|
| IModelMetaFieldText
|
||||||
|
| IModelMetaFieldNumber
|
||||||
|
| IModelMetaFieldBoolean
|
||||||
|
| IModelMetaFieldDate
|
||||||
|
| IModelMetaFieldUrl
|
||||||
|
| IModelMetaEnumerationField
|
||||||
|
| IModelMetaRelationField
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IModelMetaCollectionObjectField extends IModelMetaFieldWithFields {
|
||||||
|
collectionOf: 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaCollectionFieldCommon {
|
||||||
|
fieldType: 'collection';
|
||||||
|
collectionMinLength?: number;
|
||||||
|
collectionMaxLength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelMetaCollectionField = IModelMetaCollectionFieldCommon &
|
||||||
|
IModelMetaCollectionObjectField;
|
||||||
|
|
||||||
export type IModelMetaRelationField = IModelMetaRelationFieldCommon &
|
export type IModelMetaRelationField = IModelMetaRelationFieldCommon &
|
||||||
IModelMetaRelationEnumerationField;
|
IModelMetaRelationEnumerationField;
|
||||||
|
|
||||||
export interface IModelMeta {
|
export interface IModelMeta {
|
||||||
defaultFilterField: string;
|
defaultFilterField: string;
|
||||||
defaultSort: IModelMetaDefaultSort;
|
defaultSort: IModelMetaDefaultSort;
|
||||||
|
|
||||||
importable?: boolean;
|
importable?: boolean;
|
||||||
|
|
||||||
|
importAggregator?: string;
|
||||||
|
importAggregateOn?: string;
|
||||||
|
importAggregateBy?: string;
|
||||||
|
|
||||||
fields: { [key: string]: IModelMetaField };
|
fields: { [key: string]: IModelMetaField };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
export interface IModelMetaFieldCommon2 {
|
||||||
|
name: string;
|
||||||
|
required?: boolean;
|
||||||
|
importHint?: string;
|
||||||
|
order?: number;
|
||||||
|
unique?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaRelationField2 {
|
||||||
|
fieldType: 'relation';
|
||||||
|
relationModel: string;
|
||||||
|
importableRelationLabel: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelMetaField2 = IModelMetaFieldCommon2 &
|
||||||
|
(
|
||||||
|
| IModelMetaFieldText
|
||||||
|
| IModelMetaFieldNumber
|
||||||
|
| IModelMetaFieldBoolean
|
||||||
|
| IModelMetaFieldDate
|
||||||
|
| IModelMetaFieldUrl
|
||||||
|
| IModelMetaEnumerationField
|
||||||
|
| IModelMetaRelationField2
|
||||||
|
| IModelMetaCollectionField
|
||||||
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEsti
|
|||||||
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob';
|
||||||
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
|
import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob';
|
||||||
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
|
||||||
|
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
|
||||||
|
|
||||||
export default ({ agenda }: { agenda: Agenda }) => {
|
export default ({ agenda }: { agenda: Agenda }) => {
|
||||||
new ResetPasswordMailJob(agenda);
|
new ResetPasswordMailJob(agenda);
|
||||||
@@ -25,6 +26,9 @@ export default ({ agenda }: { agenda: Agenda }) => {
|
|||||||
new SaleReceiptMailNotificationJob(agenda);
|
new SaleReceiptMailNotificationJob(agenda);
|
||||||
new PaymentReceiveMailNotificationJob(agenda);
|
new PaymentReceiveMailNotificationJob(agenda);
|
||||||
new PlaidFetchTransactionsJob(agenda);
|
new PlaidFetchTransactionsJob(agenda);
|
||||||
|
new ImportDeleteExpiredFilesJobs(agenda);
|
||||||
|
|
||||||
agenda.start();
|
agenda.start().then(() => {
|
||||||
|
agenda.every('1 hours', 'delete-expired-imported-files', {});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ import Task from 'models/Task';
|
|||||||
import TaxRate from 'models/TaxRate';
|
import TaxRate from 'models/TaxRate';
|
||||||
import TaxRateTransaction from 'models/TaxRateTransaction';
|
import TaxRateTransaction from 'models/TaxRateTransaction';
|
||||||
import Attachment from 'models/Attachment';
|
import Attachment from 'models/Attachment';
|
||||||
import Import from 'models/Import';
|
|
||||||
import PlaidItem from 'models/PlaidItem';
|
import PlaidItem from 'models/PlaidItem';
|
||||||
import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction';
|
import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction';
|
||||||
|
|
||||||
@@ -128,7 +127,6 @@ export default (knex) => {
|
|||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRateTransaction,
|
TaxRateTransaction,
|
||||||
Attachment,
|
Attachment,
|
||||||
Import,
|
|
||||||
PlaidItem,
|
PlaidItem,
|
||||||
UncategorizedCashflowTransaction
|
UncategorizedCashflowTransaction
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,18 +12,11 @@ export default {
|
|||||||
name: 'account.field.name',
|
name: 'account.field.name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
unique: true,
|
|
||||||
required: true,
|
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
order: 1,
|
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
name: 'account.field.description',
|
name: 'account.field.description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
},
|
},
|
||||||
slug: {
|
slug: {
|
||||||
name: 'account.field.slug',
|
name: 'account.field.slug',
|
||||||
@@ -31,19 +24,13 @@ export default {
|
|||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
columnable: false,
|
columnable: false,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
name: 'account.field.code',
|
name: 'account.field.code',
|
||||||
column: 'code',
|
column: 'code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
exportable: true,
|
|
||||||
importable: true,
|
|
||||||
minLength: 3,
|
|
||||||
maxLength: 6,
|
|
||||||
importHint: 'Unique number to identify the account.',
|
|
||||||
},
|
},
|
||||||
rootType: {
|
root_type: {
|
||||||
name: 'account.field.root_type',
|
name: 'account.field.root_type',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -55,7 +42,6 @@ export default {
|
|||||||
],
|
],
|
||||||
filterCustomQuery: RootTypeFieldFilterQuery,
|
filterCustomQuery: RootTypeFieldFilterQuery,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
name: 'account.field.normal',
|
name: 'account.field.normal',
|
||||||
@@ -66,9 +52,8 @@ export default {
|
|||||||
],
|
],
|
||||||
filterCustomQuery: NormalTypeFieldFilterQuery,
|
filterCustomQuery: NormalTypeFieldFilterQuery,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
accountType: {
|
type: {
|
||||||
name: 'account.field.type',
|
name: 'account.field.type',
|
||||||
column: 'account_type',
|
column: 'account_type',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
@@ -76,46 +61,71 @@ export default {
|
|||||||
label: accountType.label,
|
label: accountType.label,
|
||||||
key: accountType.key,
|
key: accountType.key,
|
||||||
})),
|
})),
|
||||||
required: true,
|
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
order: 2,
|
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
name: 'account.field.active',
|
name: 'account.field.active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
exportable: true,
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
name: 'account.field.balance',
|
name: 'account.field.balance',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: false,
|
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currency: {
|
||||||
name: 'account.field.currency',
|
name: 'account.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
importable: true,
|
|
||||||
exportable: true,
|
|
||||||
},
|
},
|
||||||
parentAccount: {
|
created_at: {
|
||||||
name: 'account.field.parent_account',
|
|
||||||
column: 'parent_account_id',
|
|
||||||
fieldType: 'relation',
|
|
||||||
to: { model: 'Account', to: 'id' },
|
|
||||||
importable: false,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
name: 'account.field.created_at',
|
name: 'account.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: false,
|
},
|
||||||
exportable: true,
|
},
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'account.field.name',
|
||||||
|
fieldType: 'text',
|
||||||
|
unique: true,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'account.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'account.field.code',
|
||||||
|
fieldType: 'text',
|
||||||
|
minLength: 3,
|
||||||
|
maxLength: 6,
|
||||||
|
unique: true,
|
||||||
|
importHint: 'Unique number to identify the account.',
|
||||||
|
},
|
||||||
|
accountType: {
|
||||||
|
name: 'account.field.type',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||||
|
label: accountType.label,
|
||||||
|
key: accountType.key,
|
||||||
|
})),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'account.field.active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'account.field.currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
parentAccountId: {
|
||||||
|
name: 'account.field.parent_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'vendor',
|
defaultFilterField: 'vendor',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'bill_date',
|
sortField: 'bill_date',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'billNumber',
|
||||||
fields: {
|
fields: {
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'bill.field.vendor',
|
name: 'bill.field.vendor',
|
||||||
@@ -77,6 +80,77 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
billNumber: {
|
||||||
|
name: 'Bill No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
billDate: {
|
||||||
|
name: 'Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
name: 'Due Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
vendorId: {
|
||||||
|
name: 'Vendor',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'Item',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the item name or code."
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Line Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'bill_date',
|
sortField: 'bill_date',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'paymentNumber',
|
||||||
fields: {
|
fields: {
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'bill_payment.field.vendor',
|
name: 'bill_payment.field.vendor',
|
||||||
@@ -63,4 +67,67 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
vendorId: {
|
||||||
|
name: 'bill_payment.field.vendor',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: ['displayName'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
payment_date: {
|
||||||
|
name: 'bill_payment.field.payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
paymentNumber: {
|
||||||
|
name: 'bill_payment.field.payment_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
unique: true,
|
||||||
|
importHint: "The payment number should be unique."
|
||||||
|
},
|
||||||
|
paymentAccountId: {
|
||||||
|
name: 'bill_payment.field.payment_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the account name or code."
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'bill_payment.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'bill_payment.field.statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'bill_payment.field.reference',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'bill_payment.field.entries',
|
||||||
|
column: 'entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
billId: {
|
||||||
|
name: 'bill_payment.field.entries.bill',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Bill',
|
||||||
|
relationImportMatch: 'billNumber',
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the bill number."
|
||||||
|
},
|
||||||
|
paymentAmount: {
|
||||||
|
name: 'bill_payment.field.entries.payment_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'creditNoteNumber',
|
||||||
fields: {
|
fields: {
|
||||||
customer: {
|
customer: {
|
||||||
name: 'credit_note.field.customer',
|
name: 'credit_note.field.customer',
|
||||||
@@ -77,4 +81,72 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
customerId: {
|
||||||
|
name: 'Customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
creditNoteDate: {
|
||||||
|
name: 'Credit Note Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'Terms & Conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
creditNoteNumber: {
|
||||||
|
name: 'Credit Note Number',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'Item',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: 'Matches the item name or code.',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,213 +3,246 @@ export default {
|
|||||||
defaultFilterField: 'displayName',
|
defaultFilterField: 'displayName',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'createdAt',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
|
first_name: {
|
||||||
|
name: 'vendor.field.first_name',
|
||||||
|
column: 'first_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
last_name: {
|
||||||
|
name: 'vendor.field.last_name',
|
||||||
|
column: 'last_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
display_name: {
|
||||||
|
name: 'vendor.field.display_name',
|
||||||
|
column: 'display_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'vendor.field.email',
|
||||||
|
column: 'email',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
work_phone: {
|
||||||
|
name: 'vendor.field.work_phone',
|
||||||
|
column: 'work_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
personal_phone: {
|
||||||
|
name: 'vendor.field.personal_pone',
|
||||||
|
column: 'personal_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
company_name: {
|
||||||
|
name: 'vendor.field.company_name',
|
||||||
|
column: 'company_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'vendor.field.website',
|
||||||
|
column: 'website',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'vendor.field.created_at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'vendor.field.balance',
|
||||||
|
column: 'balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
opening_balance: {
|
||||||
|
name: 'vendor.field.opening_balance',
|
||||||
|
column: 'opening_balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
opening_balance_at: {
|
||||||
|
name: 'vendor.field.opening_balance_at',
|
||||||
|
column: 'opening_balance_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
currency_code: {
|
||||||
|
name: 'vendor.field.currency',
|
||||||
|
column: 'currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
name: 'vendor.field.status',
|
||||||
|
type: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'overdue', label: 'vendor.field.status.overdue' },
|
||||||
|
{ key: 'unpaid', label: 'vendor.field.status.unpaid' },
|
||||||
|
],
|
||||||
|
filterCustomQuery: (query, role) => {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'overdue':
|
||||||
|
query.modify('overdue');
|
||||||
|
break;
|
||||||
|
case 'unpaid':
|
||||||
|
query.modify('unpaid');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
customerType: {
|
customerType: {
|
||||||
name: 'Customer Type',
|
name: 'Customer Type',
|
||||||
column: 'contact_type',
|
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'business', label: 'Business' },
|
{ key: 'business', label: 'Business' },
|
||||||
{ key: 'individual', label: 'Individual' },
|
{ key: 'individual', label: 'Individual' },
|
||||||
],
|
],
|
||||||
importable: true,
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
firstName: {
|
firstName: {
|
||||||
name: 'customer.field.first_name',
|
name: 'customer.field.first_name',
|
||||||
column: 'first_name',
|
column: 'first_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
lastName: {
|
lastName: {
|
||||||
name: 'customer.field.last_name',
|
name: 'customer.field.last_name',
|
||||||
column: 'last_name',
|
column: 'last_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
name: 'customer.field.display_name',
|
name: 'customer.field.display_name',
|
||||||
column: 'display_name',
|
column: 'display_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
name: 'customer.field.email',
|
name: 'customer.field.email',
|
||||||
column: 'email',
|
column: 'email',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
workPhone: {
|
workPhone: {
|
||||||
name: 'customer.field.work_phone',
|
name: 'customer.field.work_phone',
|
||||||
column: 'work_phone',
|
column: 'work_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
personalPhone: {
|
personalPhone: {
|
||||||
name: 'customer.field.personal_phone',
|
name: 'customer.field.personal_phone',
|
||||||
column: 'personal_phone',
|
column: 'personal_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
companyName: {
|
companyName: {
|
||||||
name: 'customer.field.company_name',
|
name: 'customer.field.company_name',
|
||||||
column: 'company_name',
|
column: 'company_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
name: 'customer.field.website',
|
name: 'customer.field.website',
|
||||||
column: 'website',
|
column: 'website',
|
||||||
fieldType: 'url',
|
fieldType: 'url',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
name: 'customer.field.balance',
|
|
||||||
column: 'balance',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
},
|
||||||
openingBalance: {
|
openingBalance: {
|
||||||
name: 'customer.field.opening_balance',
|
name: 'customer.field.opening_balance',
|
||||||
column: 'opening_balance',
|
column: 'opening_balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
openingBalanceAt: {
|
||||||
name: 'customer.field.opening_balance_at',
|
name: 'customer.field.opening_balance_at',
|
||||||
column: 'opening_balance_at',
|
column: 'opening_balance_at',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceExchangeRate: {
|
openingBalanceExchangeRate: {
|
||||||
name: 'Opening Balance Ex. Rate',
|
name: 'Opening Balance Ex. Rate',
|
||||||
column: 'opening_balance_exchange_rate',
|
column: 'opening_balance_exchange_rate',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currencyCode: {
|
||||||
name: 'customer.field.currency',
|
name: 'customer.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'Note',
|
name: 'Note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
name: 'Active',
|
name: 'Active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
name: 'customer.field.status',
|
|
||||||
fieldType: 'enumeration',
|
|
||||||
options: [
|
|
||||||
{ key: 'active', label: 'customer.field.status.active' },
|
|
||||||
{ key: 'inactive', label: 'customer.field.status.inactive' },
|
|
||||||
{ key: 'overdue', label: 'customer.field.status.overdue' },
|
|
||||||
{ key: 'unpaid', label: 'customer.field.status.unpaid' },
|
|
||||||
],
|
|
||||||
filterCustomQuery: statusFieldFilterQuery,
|
|
||||||
},
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
//
|
|
||||||
createdAt: {
|
|
||||||
name: 'customer.field.created_at',
|
|
||||||
column: 'created_at',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
fields: {
|
fields: {
|
||||||
'payment_date': {
|
payment_date: {
|
||||||
name: 'expense.field.payment_date',
|
name: 'expense.field.payment_date',
|
||||||
column: 'payment_date',
|
column: 'payment_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'payment_account': {
|
payment_account: {
|
||||||
name: 'expense.field.payment_account',
|
name: 'expense.field.payment_account',
|
||||||
column: 'payment_account_id',
|
column: 'payment_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -24,27 +25,27 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'expense.field.amount',
|
name: 'expense.field.amount',
|
||||||
column: 'total_amount',
|
column: 'total_amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'reference_no': {
|
reference_no: {
|
||||||
name: 'expense.field.reference_no',
|
name: 'expense.field.reference_no',
|
||||||
column: 'reference_no',
|
column: 'reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'description': {
|
description: {
|
||||||
name: 'expense.field.description',
|
name: 'expense.field.description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'published': {
|
published: {
|
||||||
name: 'expense.field.published',
|
name: 'expense.field.published',
|
||||||
column: 'published_at',
|
column: 'published_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'expense.field.status',
|
name: 'expense.field.status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -54,12 +55,71 @@ export default {
|
|||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'expense.field.created_at',
|
name: 'expense.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
paymentAccountId: {
|
||||||
|
name: 'expense.field.payment_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the account name or code."
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'expense.field.reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'expense.field.payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'expense.field.currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'expense.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'expense.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
name: 'expense.field.categories',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
fields: {
|
||||||
|
expenseAccountId: {
|
||||||
|
name: 'expense.field.expense_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the account name or code."
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'expense.field.amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'expense.field.line_description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
name: 'expense.field.publish',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
|
|||||||
@@ -15,45 +15,38 @@ export default {
|
|||||||
{ key: 'service', label: 'item.field.type.service' },
|
{ key: 'service', label: 'item.field.type.service' },
|
||||||
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
||||||
],
|
],
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
name: 'item.field.name',
|
name: 'item.field.name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
name: 'item.field.code',
|
name: 'item.field.code',
|
||||||
column: 'code',
|
column: 'code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellable: {
|
sellable: {
|
||||||
name: 'item.field.sellable',
|
name: 'item.field.sellable',
|
||||||
column: 'sellable',
|
column: 'sellable',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
purchasable: {
|
purchasable: {
|
||||||
name: 'item.field.purchasable',
|
name: 'item.field.purchasable',
|
||||||
column: 'purchasable',
|
column: 'purchasable',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellPrice: {
|
sell_price: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.cost_price',
|
||||||
column: 'sell_price',
|
column: 'sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
costPrice: {
|
cost_price: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_account',
|
||||||
column: 'cost_price',
|
column: 'cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
costAccount: {
|
cost_account: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.sell_account',
|
||||||
column: 'cost_account_id',
|
column: 'cost_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -63,10 +56,8 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellAccount: {
|
sell_account: {
|
||||||
name: 'item.field.sell_description',
|
name: 'item.field.sell_description',
|
||||||
column: 'sell_account_id',
|
column: 'sell_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -76,10 +67,8 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
inventoryAccount: {
|
inventory_account: {
|
||||||
name: 'item.field.inventory_account',
|
name: 'item.field.inventory_account',
|
||||||
column: 'inventory_account_id',
|
column: 'inventory_account_id',
|
||||||
|
|
||||||
@@ -88,32 +77,26 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
sellDescription: {
|
sell_description: {
|
||||||
name: 'Sell description',
|
name: 'Sell description',
|
||||||
column: 'sell_description',
|
column: 'sell_description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
purchaseDescription: {
|
purchase_description: {
|
||||||
name: 'Purchase description',
|
name: 'Purchase description',
|
||||||
column: 'purchase_description',
|
column: 'purchase_description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
quantityOnHand: {
|
quantity_on_hand: {
|
||||||
name: 'item.field.quantity_on_hand',
|
name: 'item.field.quantity_on_hand',
|
||||||
column: 'quantity_on_hand',
|
column: 'quantity_on_hand',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'item.field.note',
|
name: 'item.field.note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
name: 'item.field.category',
|
name: 'item.field.category',
|
||||||
@@ -124,19 +107,99 @@ export default {
|
|||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
name: 'item.field.active',
|
name: 'item.field.active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
importable: true,
|
filterable: false,
|
||||||
},
|
},
|
||||||
createdAt: {
|
created_at: {
|
||||||
name: 'item.field.created_at',
|
name: 'item.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
type: {
|
||||||
|
name: 'item.field.type',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'inventory', label: 'item.field.type.inventory' },
|
||||||
|
{ key: 'service', label: 'item.field.type.service' },
|
||||||
|
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
name: 'item.field.name',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'item.field.code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
sellable: {
|
||||||
|
name: 'item.field.sellable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
purchasable: {
|
||||||
|
name: 'item.field.purchasable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
sellPrice: {
|
||||||
|
name: 'item.field.sell_price',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
cost_price: {
|
||||||
|
name: 'item.field.cost_price',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
costAccount: {
|
||||||
|
name: 'item.field.cost_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
sellAccount: {
|
||||||
|
name: 'item.field.sell_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
inventoryAccount: {
|
||||||
|
name: 'item.field.inventory_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
importHint: 'Matches the account name or code.',
|
||||||
|
},
|
||||||
|
sellDescription: {
|
||||||
|
name: 'Sell Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
purchaseDescription: {
|
||||||
|
name: 'Purchase Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'item.field.note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
name: 'item.field.category',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'ItemCategory',
|
||||||
|
relationImportMatch: ['name'],
|
||||||
|
importHint: "Matches the category name."
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'item.field.active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export default {
|
|||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
name: 'item_category.field.name',
|
name: 'item_category.field.name',
|
||||||
@@ -27,4 +28,16 @@ export default {
|
|||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'item_category.field.name',
|
||||||
|
column: 'name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'item_category.field.description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,54 +4,130 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'journalNumber',
|
||||||
fields: {
|
fields: {
|
||||||
'date': {
|
date: {
|
||||||
name: 'manual_journal.field.date',
|
name: 'manual_journal.field.date',
|
||||||
column: 'date',
|
column: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'journal_number': {
|
journal_number: {
|
||||||
name: 'manual_journal.field.journal_number',
|
name: 'manual_journal.field.journal_number',
|
||||||
column: 'journal_number',
|
column: 'journal_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'reference': {
|
reference: {
|
||||||
name: 'manual_journal.field.reference',
|
name: 'manual_journal.field.reference',
|
||||||
column: 'reference',
|
column: 'reference',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'journal_type': {
|
journal_type: {
|
||||||
name: 'manual_journal.field.journal_type',
|
name: 'manual_journal.field.journal_type',
|
||||||
column: 'journal_type',
|
column: 'journal_type',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'manual_journal.field.amount',
|
name: 'manual_journal.field.amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'description': {
|
description: {
|
||||||
name: 'manual_journal.field.description',
|
name: 'manual_journal.field.description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'manual_journal.field.status',
|
name: 'manual_journal.field.status',
|
||||||
column: 'status',
|
column: 'status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'draft', label: 'Draft' },
|
{ key: 'draft', label: 'Draft' },
|
||||||
{ key: 'published', label: 'published' }
|
{ key: 'published', label: 'published' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'manual_journal.field.created_at',
|
name: 'manual_journal.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
date: {
|
||||||
|
name: 'manual_journal.field.date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
journalNumber: {
|
||||||
|
name: 'manual_journal.field.journal_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'manual_journal.field.reference',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
journalType: {
|
||||||
|
name: 'manual_journal.field.journal_type',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'manual_journal.field.currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
exchange_rate: {
|
||||||
|
name: 'manual_journal.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'manual_journal.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 2,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
credit: {
|
||||||
|
name: 'Credit',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
debit: {
|
||||||
|
name: 'Debit',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
accountId: {
|
||||||
|
name: 'Account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
name: 'Contact',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
name: 'Publish',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +140,6 @@ function StatusFieldSortQuery(query, role) {
|
|||||||
/**
|
/**
|
||||||
* Status field filter custom query.
|
* Status field filter custom query.
|
||||||
*/
|
*/
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
query.modify('filterByStatus', role.value);
|
query.modify('filterByStatus', role.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,54 @@
|
|||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from '@/interfaces';
|
import {
|
||||||
|
IModelMeta,
|
||||||
|
IModelMetaField,
|
||||||
|
IModelMetaDefaultSort,
|
||||||
|
} from '@/interfaces';
|
||||||
|
|
||||||
|
const defaultModelMeta = {
|
||||||
|
fields: {},
|
||||||
|
fields2: {},
|
||||||
|
};
|
||||||
|
|
||||||
export default (Model) =>
|
export default (Model) =>
|
||||||
class ModelSettings extends Model {
|
class ModelSettings extends Model {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
* @returns {IModelMeta}
|
||||||
*/
|
*/
|
||||||
static get meta(): IModelMeta {
|
static get meta(): IModelMeta {
|
||||||
throw new Error('');
|
throw new Error('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsed meta merged with default emta.
|
||||||
|
* @returns {IModelMeta}
|
||||||
|
*/
|
||||||
|
static get parsedMeta(): IModelMeta {
|
||||||
|
return {
|
||||||
|
...defaultModelMeta,
|
||||||
|
...this.meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve specific model field meta of the given field key.
|
* Retrieve specific model field meta of the given field key.
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {IModelMetaField}
|
* @returns {IModelMetaField}
|
||||||
*/
|
*/
|
||||||
public static getField(key: string, attribute?:string): IModelMetaField {
|
public static getField(key: string, attribute?: string): IModelMetaField {
|
||||||
const field = get(this.meta.fields, key);
|
const field = get(this.meta.fields, key);
|
||||||
|
|
||||||
return attribute ? get(field, attribute) : field;
|
return attribute ? get(field, attribute) : field;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the specific model meta.
|
* Retrieves the specific model meta.
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public static getMeta(key?: string) {
|
public static getMeta(key?: string) {
|
||||||
return key ? get(this.meta, key): this.meta;
|
return key ? get(this.parsedMeta, key) : this.parsedMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'paymentReceiveNo',
|
||||||
fields: {
|
fields: {
|
||||||
customer: {
|
customer: {
|
||||||
name: 'payment_receive.field.customer',
|
name: 'payment_receive.field.customer',
|
||||||
@@ -54,4 +57,65 @@ export default {
|
|||||||
fieldDate: 'date',
|
fieldDate: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
customerId: {
|
||||||
|
name: 'payment_receive.field.customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: ['displayName'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'payment_receive.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
paymentDate: {
|
||||||
|
name: 'payment_receive.field.payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'payment_receive.field.reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
depositAccountId: {
|
||||||
|
name: 'payment_receive.field.deposit_account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the account name or code."
|
||||||
|
},
|
||||||
|
paymentReceiveNo: {
|
||||||
|
name: 'payment_receive.field.payment_receive_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
importHint: "The payment number should be unique."
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'payment_receive.field.statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'payment_receive.field.entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
invoiceId: {
|
||||||
|
name: 'payment_receive.field.invoice',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'SaleInvoice',
|
||||||
|
relationImportMatch: 'invoiceNo',
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the invoice number."
|
||||||
|
},
|
||||||
|
paymentAmount: {
|
||||||
|
name: 'payment_receive.field.entries.payment_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,18 +4,22 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'estimate_date',
|
sortField: 'estimate_date',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'estimateNumber',
|
||||||
fields: {
|
fields: {
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'estimate.field.amount',
|
name: 'estimate.field.amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'estimate_number': {
|
estimate_number: {
|
||||||
name: 'estimate.field.estimate_number',
|
name: 'estimate.field.estimate_number',
|
||||||
column: 'estimate_number',
|
column: 'estimate_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'customer': {
|
customer: {
|
||||||
name: 'estimate.field.customer',
|
name: 'estimate.field.customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -26,32 +30,32 @@ export default {
|
|||||||
relationEntityLabel: 'display_name',
|
relationEntityLabel: 'display_name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
},
|
},
|
||||||
'estimate_date': {
|
estimate_date: {
|
||||||
name: 'estimate.field.estimate_date',
|
name: 'estimate.field.estimate_date',
|
||||||
column: 'estimate_date',
|
column: 'estimate_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'expiration_date': {
|
expiration_date: {
|
||||||
name: 'estimate.field.expiration_date',
|
name: 'estimate.field.expiration_date',
|
||||||
column: 'expiration_date',
|
column: 'expiration_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'reference_no': {
|
reference_no: {
|
||||||
name: 'estimate.field.reference_no',
|
name: 'estimate.field.reference_no',
|
||||||
column: 'reference',
|
column: 'reference',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'note': {
|
note: {
|
||||||
name: 'estimate.field.note',
|
name: 'estimate.field.note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'terms_conditions': {
|
terms_conditions: {
|
||||||
name: 'estimate.field.terms_conditions',
|
name: 'estimate.field.terms_conditions',
|
||||||
column: 'terms_conditions',
|
column: 'terms_conditions',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'estimate.field.status',
|
name: 'estimate.field.status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -63,12 +67,90 @@ export default {
|
|||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'estimate.field.created_at',
|
name: 'estimate.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
customerId: {
|
||||||
|
name: 'Customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: ['displayName'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
estimateDate: {
|
||||||
|
name: 'Estimate Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
expirationDate: {
|
||||||
|
name: 'Expiration Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
estimateNumber: {
|
||||||
|
name: 'Estimate No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
reference: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'Currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'Terms & Conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
name: 'Delivered',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'invoice.field.item_name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the item name or code."
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'invoice.field.rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'invoice.field.quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Line Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function StatusFieldSortQuery(query, role) {
|
function StatusFieldSortQuery(query, role) {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'invoiceNo',
|
||||||
fields: {
|
fields: {
|
||||||
customer: {
|
customer: {
|
||||||
name: 'invoice.field.customer',
|
name: 'invoice.field.customer',
|
||||||
@@ -83,6 +87,84 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
invoiceDate: {
|
||||||
|
name: 'invoice.field.invoice_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
name: 'invoice.field.due_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'invoice.field.reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
invoiceNo: {
|
||||||
|
name: 'invoice.field.invoice_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
customerId: {
|
||||||
|
name: 'invoice.field.customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'invoice.field.exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'invoice.field.currency',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
invoiceMessage: {
|
||||||
|
name: 'invoice.field.invoice_message',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
termsConditions: {
|
||||||
|
name: 'invoice.field.terms_conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'invoice.field.entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'invoice.field.item_name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the item name or code."
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'invoice.field.rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'invoice.field.quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'invoice.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
name: 'invoice.field.delivered',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'receiptNumber',
|
||||||
fields: {
|
fields: {
|
||||||
'amount': {
|
amount: {
|
||||||
name: 'receipt.field.amount',
|
name: 'receipt.field.amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'deposit_account': {
|
deposit_account: {
|
||||||
column: 'deposit_account_id',
|
column: 'deposit_account_id',
|
||||||
name: 'receipt.field.deposit_account',
|
name: 'receipt.field.deposit_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -21,7 +25,7 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
'customer': {
|
customer: {
|
||||||
name: 'receipt.field.customer',
|
name: 'receipt.field.customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -32,38 +36,37 @@ export default {
|
|||||||
relationEntityLabel: 'display_name',
|
relationEntityLabel: 'display_name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
},
|
},
|
||||||
'receipt_date': {
|
receipt_date: {
|
||||||
name: 'receipt.field.receipt_date',
|
name: 'receipt.field.receipt_date',
|
||||||
column: 'receipt_date',
|
column: 'receipt_date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
|
|
||||||
},
|
},
|
||||||
'receipt_number': {
|
receipt_number: {
|
||||||
name: 'receipt.field.receipt_number',
|
name: 'receipt.field.receipt_number',
|
||||||
column: 'receipt_number',
|
column: 'receipt_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'reference_no': {
|
reference_no: {
|
||||||
name: 'receipt.field.reference_no',
|
name: 'receipt.field.reference_no',
|
||||||
column: 'reference_no',
|
column: 'reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'receipt_message': {
|
receipt_message: {
|
||||||
name: 'receipt.field.receipt_message',
|
name: 'receipt.field.receipt_message',
|
||||||
column: 'receipt_message',
|
column: 'receipt_message',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'statement': {
|
statement: {
|
||||||
name: 'receipt.field.statement',
|
name: 'receipt.field.statement',
|
||||||
column: 'statement',
|
column: 'statement',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'created_at': {
|
created_at: {
|
||||||
name: 'receipt.field.created_at',
|
name: 'receipt.field.created_at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'status': {
|
status: {
|
||||||
name: 'receipt.field.status',
|
name: 'receipt.field.status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
@@ -74,6 +77,82 @@ export default {
|
|||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
receiptDate: {
|
||||||
|
name: 'Receipt Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
customerId: {
|
||||||
|
name: 'Customer',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
depositAccountId: {
|
||||||
|
name: 'Deposit Account',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Account',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Exchange Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
receiptNumber: {
|
||||||
|
name: 'Receipt Number',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
name: 'Closed',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'invoice.field.item_name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the item name or code."
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'invoice.field.rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'invoice.field.quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'invoice.field.description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'Statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
receiptMessage: {
|
||||||
|
name: 'Receipt Message',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function StatusFieldFilterQuery(query, role) {
|
function StatusFieldFilterQuery(query, role) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export default {
|
|||||||
defaultFilterField: 'createdAt',
|
defaultFilterField: 'createdAt',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'createdAt',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
importable: true,
|
importable: true,
|
||||||
fields: {
|
fields: {
|
||||||
@@ -10,33 +10,27 @@ export default {
|
|||||||
name: 'Date',
|
name: 'Date',
|
||||||
column: 'date',
|
column: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: true,
|
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
payee: {
|
payee: {
|
||||||
name: 'Payee',
|
name: 'Payee',
|
||||||
column: 'payee',
|
column: 'payee',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
name: 'Description',
|
name: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
referenceNo: {
|
referenceNo: {
|
||||||
name: 'Reference No.',
|
name: 'Reference No.',
|
||||||
column: 'reference_no',
|
column: 'reference_no',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
name: 'Amount',
|
name: 'Amount',
|
||||||
column: 'Amount',
|
column: 'Amount',
|
||||||
fieldType: 'numeric',
|
fieldType: 'numeric',
|
||||||
required: true,
|
required: true,
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
name: 'Account',
|
name: 'Account',
|
||||||
@@ -51,4 +45,28 @@ export default {
|
|||||||
importable: false,
|
importable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
date: {
|
||||||
|
name: 'Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
payee: {
|
||||||
|
name: 'Payee',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,99 +2,74 @@ export default {
|
|||||||
defaultFilterField: 'displayName',
|
defaultFilterField: 'displayName',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'createdAt',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
importable: true,
|
importable: true,
|
||||||
fields: {
|
fields: {
|
||||||
firstName: {
|
first_name: {
|
||||||
name: 'vendor.field.first_name',
|
name: 'vendor.field.first_name',
|
||||||
column: 'first_name',
|
column: 'first_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
lastName: {
|
last_name: {
|
||||||
name: 'vendor.field.last_name',
|
name: 'vendor.field.last_name',
|
||||||
column: 'last_name',
|
column: 'last_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
displayName: {
|
display_name: {
|
||||||
name: 'vendor.field.display_name',
|
name: 'vendor.field.display_name',
|
||||||
column: 'display_name',
|
column: 'display_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
required: true,
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
name: 'vendor.field.email',
|
name: 'vendor.field.email',
|
||||||
column: 'email',
|
column: 'email',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
workPhone: {
|
work_phone: {
|
||||||
name: 'vendor.field.work_phone',
|
name: 'vendor.field.work_phone',
|
||||||
column: 'work_phone',
|
column: 'work_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
personalPhone: {
|
personal_phone: {
|
||||||
name: 'vendor.field.personal_phone',
|
name: 'vendor.field.personal_pone',
|
||||||
column: 'personal_phone',
|
column: 'personal_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
companyName: {
|
company_name: {
|
||||||
name: 'vendor.field.company_name',
|
name: 'vendor.field.company_name',
|
||||||
column: 'company_name',
|
column: 'company_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
name: 'vendor.field.website',
|
name: 'vendor.field.website',
|
||||||
column: 'website',
|
column: 'website',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'vendor.field.created_at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
name: 'vendor.field.balance',
|
name: 'vendor.field.balance',
|
||||||
column: 'balance',
|
column: 'balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
openingBalance: {
|
opening_balance: {
|
||||||
name: 'vendor.field.opening_balance',
|
name: 'vendor.field.opening_balance',
|
||||||
column: 'opening_balance',
|
column: 'opening_balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
opening_balance_at: {
|
||||||
name: 'vendor.field.opening_balance_at',
|
name: 'vendor.field.opening_balance_at',
|
||||||
column: 'opening_balance_at',
|
column: 'opening_balance_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
openingBalanceExchangeRate: {
|
currency_code: {
|
||||||
name: 'Opening Balance Ex. Rate',
|
|
||||||
column: 'opening_balance_exchange_rate',
|
|
||||||
fieldType: 'number',
|
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
currencyCode: {
|
|
||||||
name: 'vendor.field.currency',
|
name: 'vendor.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
note: {
|
|
||||||
name: 'Note',
|
|
||||||
column: 'note',
|
|
||||||
fieldType: 'text',
|
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
active: {
|
|
||||||
name: 'Active',
|
|
||||||
column: 'active',
|
|
||||||
fieldType: 'boolean',
|
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
name: 'vendor.field.status',
|
name: 'vendor.field.status',
|
||||||
@@ -114,96 +89,150 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
fields2: {
|
||||||
|
firstName: {
|
||||||
|
name: 'vendor.field.first_name',
|
||||||
|
column: 'first_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
name: 'vendor.field.last_name',
|
||||||
|
column: 'last_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
name: 'vendor.field.display_name',
|
||||||
|
column: 'display_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'vendor.field.email',
|
||||||
|
column: 'email',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
workPhone: {
|
||||||
|
name: 'vendor.field.work_phone',
|
||||||
|
column: 'work_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
personalPhone: {
|
||||||
|
name: 'vendor.field.personal_phone',
|
||||||
|
column: 'personal_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
companyName: {
|
||||||
|
name: 'vendor.field.company_name',
|
||||||
|
column: 'company_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'vendor.field.website',
|
||||||
|
column: 'website',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
openingBalance: {
|
||||||
|
name: 'vendor.field.opening_balance',
|
||||||
|
column: 'opening_balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
openingBalanceAt: {
|
||||||
|
name: 'vendor.field.opening_balance_at',
|
||||||
|
column: 'opening_balance_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
openingBalanceExchangeRate: {
|
||||||
|
name: 'Opening Balance Ex. Rate',
|
||||||
|
column: 'opening_balance_exchange_rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currencyCode: {
|
||||||
|
name: 'vendor.field.currency',
|
||||||
|
column: 'currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
column: 'note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
column: 'active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
importable: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
name: 'vendor.field.created_at',
|
|
||||||
column: 'created_at',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
|
importable: true,
|
||||||
|
importAggregator: 'group',
|
||||||
|
importAggregateOn: 'entries',
|
||||||
|
importAggregateBy: 'vendorCreditNumber',
|
||||||
fields: {
|
fields: {
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'vendor_credit.field.vendor',
|
name: 'vendor_credit.field.vendor',
|
||||||
@@ -72,4 +76,69 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fields2: {
|
||||||
|
vendorId: {
|
||||||
|
name: 'Vendor',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Contact',
|
||||||
|
relationImportMatch: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exchangeRate: {
|
||||||
|
name: 'Echange Rate',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
vendorCreditNumber: {
|
||||||
|
name: 'Vendor Credit No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
referenceNo: {
|
||||||
|
name: 'Refernece No.',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
vendorCreditDate: {
|
||||||
|
name: 'Vendor Credit Date',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
name: 'Note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
name: 'Open',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
name: 'Entries',
|
||||||
|
fieldType: 'collection',
|
||||||
|
collectionOf: 'object',
|
||||||
|
collectionMinLength: 1,
|
||||||
|
required: true,
|
||||||
|
fields: {
|
||||||
|
itemId: {
|
||||||
|
name: 'Item Name',
|
||||||
|
fieldType: 'relation',
|
||||||
|
relationModel: 'Item',
|
||||||
|
relationImportMatch: ['name', 'code'],
|
||||||
|
required: true,
|
||||||
|
importHint: "Matches the item name or code."
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
name: 'Quantity',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,9 +97,11 @@ export class CommandAccountValidators {
|
|||||||
query.whereNot('id', notAccountId);
|
query.whereNot('id', notAccountId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (account.length > 0) {
|
if (account.length > 0) {
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_CODE_NOT_UNIQUE);
|
throw new ServiceError(
|
||||||
|
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
||||||
|
'Account code is not unique.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +126,10 @@ export class CommandAccountValidators {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (foundAccount) {
|
if (foundAccount) {
|
||||||
throw new ServiceError(ERRORS.ACCOUNT_NAME_NOT_UNIQUE);
|
throw new ServiceError(
|
||||||
|
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
||||||
|
'Account name is not unique.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ERRORS } from './constants';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class CashflowDeleteAccount {
|
export default class CashflowDeleteAccount {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the account has no associated cashflow transactions.
|
* Validate the account has no associated cashflow transactions.
|
||||||
|
|||||||
@@ -79,27 +79,25 @@ export interface ICashflowTransactionTypeMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BankTransactionsSampleData = [
|
export const BankTransactionsSampleData = [
|
||||||
[
|
{
|
||||||
{
|
Amount: '6,410.19',
|
||||||
Amount: '6,410.19',
|
Date: '2024-03-26',
|
||||||
Date: '2024-03-26',
|
Payee: 'MacGyver and Sons',
|
||||||
Payee: 'MacGyver and Sons',
|
'Reference No.': 'REF-1',
|
||||||
'Reference No.': 'REF-1',
|
Description: 'Commodi quo labore.',
|
||||||
Description: 'Commodi quo labore.',
|
},
|
||||||
},
|
{
|
||||||
{
|
Amount: '8,914.17',
|
||||||
Amount: '8,914.17',
|
Date: '2024-01-05',
|
||||||
Date: '2024-01-05',
|
Payee: 'Eichmann - Bergnaum',
|
||||||
Payee: 'Eichmann - Bergnaum',
|
'Reference No.': 'REF-1',
|
||||||
'Reference No.': 'REF-1',
|
Description: 'Quia enim et.',
|
||||||
Description: 'Quia enim et.',
|
},
|
||||||
},
|
{
|
||||||
{
|
Amount: '6,200.88',
|
||||||
Amount: '6,200.88',
|
Date: '2024-02-17',
|
||||||
Date: '2024-02-17',
|
Payee: 'Luettgen, Mraz and Legros',
|
||||||
Payee: 'Luettgen, Mraz and Legros',
|
'Reference No.': 'REF-1',
|
||||||
'Reference No.': 'REF-1',
|
Description: 'Occaecati consequuntur cum impedit illo.',
|
||||||
Description: 'Occaecati consequuntur cum impedit illo.',
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
|
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
|
||||||
"Billing Address 1": "862 Jessika Well",
|
"Billing Address 1": "862 Jessika Well",
|
||||||
"Billing Address 2": "1091 Dorthy Mount",
|
"Billing Address 2": "1091 Dorthy Mount",
|
||||||
@@ -42,7 +42,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
|
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
|
||||||
"Billing Address 1": "532 Simonis Spring",
|
"Billing Address 1": "532 Simonis Spring",
|
||||||
"Billing Address 2": "3122 Nicolas Inlet",
|
"Billing Address 2": "3122 Nicolas Inlet",
|
||||||
@@ -72,7 +72,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Vero quibusdam rem fugit aperiam est modi.",
|
"Note": "Vero quibusdam rem fugit aperiam est modi.",
|
||||||
"Billing Address 1": "214 Sauer Villages",
|
"Billing Address 1": "214 Sauer Villages",
|
||||||
"Billing Address 2": "30687 Kacey Square",
|
"Billing Address 2": "30687 Kacey Square",
|
||||||
@@ -102,7 +102,7 @@ export const VendorsSampleData = [
|
|||||||
"Opening Balance At": "2022-02-02",
|
"Opening Balance At": "2022-02-02",
|
||||||
"Opening Balance Ex. Rate": 2,
|
"Opening Balance Ex. Rate": 2,
|
||||||
"Currency": "LYD",
|
"Currency": "LYD",
|
||||||
"Active": "F",
|
"Active": "T",
|
||||||
"Note": "Quis cumque molestias rerum.",
|
"Note": "Quis cumque molestias rerum.",
|
||||||
"Billing Address 1": "22590 Cathy Harbor",
|
"Billing Address 1": "22590 Cathy Harbor",
|
||||||
"Billing Address 2": "24493 Brycen Brooks",
|
"Billing Address 2": "24493 Brycen Brooks",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class CreateCreditNote extends BaseCreditNotes {
|
|||||||
public newCreditNote = async (
|
public newCreditNote = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
creditNoteDTO: ICreditNoteNewDTO,
|
creditNoteDTO: ICreditNoteNewDTO,
|
||||||
authorizedUser: ISystemUser
|
trx?: Knex.Transaction
|
||||||
) => {
|
) => {
|
||||||
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -66,28 +66,32 @@ export default class CreateCreditNote extends BaseCreditNotes {
|
|||||||
customer.currencyCode
|
customer.currencyCode
|
||||||
);
|
);
|
||||||
// Creates a new credit card transactions under unit-of-work envirement.
|
// Creates a new credit card transactions under unit-of-work envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onCreditNoteCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onCreditNoteCreating` event.
|
||||||
creditNoteDTO,
|
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
||||||
trx,
|
tenantId,
|
||||||
} as ICreditNoteCreatingPayload);
|
creditNoteDTO,
|
||||||
|
trx,
|
||||||
|
} as ICreditNoteCreatingPayload);
|
||||||
|
|
||||||
// Upsert the credit note graph.
|
// Upsert the credit note graph.
|
||||||
const creditNote = await CreditNote.query(trx).upsertGraph({
|
const creditNote = await CreditNote.query(trx).upsertGraph({
|
||||||
...creditNoteModel,
|
...creditNoteModel,
|
||||||
});
|
});
|
||||||
// Triggers `onCreditNoteCreated` event.
|
// Triggers `onCreditNoteCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
|
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
creditNoteDTO,
|
creditNoteDTO,
|
||||||
creditNote,
|
creditNote,
|
||||||
creditNoteId: creditNote.id,
|
creditNoteId: creditNote.id,
|
||||||
trx,
|
trx,
|
||||||
} as ICreditNoteCreatedPayload);
|
} as ICreditNoteCreatedPayload);
|
||||||
|
|
||||||
return creditNote;
|
return creditNote;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { ICreditNoteNewDTO } from '@/interfaces';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import CreateCreditNote from './CreateCreditNote';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreditNotesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createCreditNoteImportable: CreateCreditNote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: ICreditNoteNewDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createCreditNoteImportable.newCreditNote(
|
||||||
|
tenantId,
|
||||||
|
createAccountDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,7 +88,8 @@ export class CreateExpense {
|
|||||||
public newExpense = async (
|
public newExpense = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
expenseDTO: IExpenseCreateDTO,
|
expenseDTO: IExpenseCreateDTO,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<IExpense> => {
|
): Promise<IExpense> => {
|
||||||
const { Expense } = await this.tenancy.models(tenantId);
|
const { Expense } = await this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -103,28 +104,32 @@ export class CreateExpense {
|
|||||||
);
|
);
|
||||||
// Writes the expense transaction with associated transactions under
|
// Writes the expense transaction with associated transactions under
|
||||||
// unit-of-work envirement.
|
// unit-of-work envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onExpenseCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
trx,
|
// Triggers `onExpenseCreating` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
||||||
expenseDTO,
|
trx,
|
||||||
} as IExpenseCreatingPayload);
|
tenantId,
|
||||||
|
expenseDTO,
|
||||||
|
} as IExpenseCreatingPayload);
|
||||||
|
|
||||||
// Creates a new expense transaction graph.
|
// Creates a new expense transaction graph.
|
||||||
const expense: IExpense = await Expense.query(trx).upsertGraph(
|
const expense: IExpense = await Expense.query(trx).upsertGraph(
|
||||||
expenseObj
|
expenseObj
|
||||||
);
|
);
|
||||||
// Triggers `onExpenseCreated` event.
|
// Triggers `onExpenseCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.expenses.onCreated, {
|
await this.eventPublisher.emitAsync(events.expenses.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
expenseId: expense.id,
|
expenseId: expense.id,
|
||||||
authorizedUser,
|
authorizedUser,
|
||||||
expense,
|
expense,
|
||||||
trx,
|
trx,
|
||||||
} as IExpenseCreatedPayload);
|
} as IExpenseCreatedPayload);
|
||||||
|
|
||||||
return expense;
|
return expense;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
46
packages/server/src/services/Expenses/ExpensesImportable.ts
Normal file
46
packages/server/src/services/Expenses/ExpensesImportable.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IExpenseCreateDTO } from '@/interfaces';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import { CreateExpense } from './CRUD/CreateExpense';
|
||||||
|
import { ExpensesSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ExpensesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createExpenseService: CreateExpense;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: IExpenseCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createExpenseService.newExpense(
|
||||||
|
tenantId,
|
||||||
|
createAccountDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return ExpensesSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,3 +36,43 @@ export const ERRORS = {
|
|||||||
EXPENSE_ALREADY_PUBLISHED: 'expense_already_published',
|
EXPENSE_ALREADY_PUBLISHED: 'expense_already_published',
|
||||||
EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST',
|
EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ExpensesSampleData = [
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-01',
|
||||||
|
'Reference No.': 'REF-1',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Description: 'Vel et dolorem architecto veniam.',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Expense Account': 'Utilities Expense',
|
||||||
|
Amount: 9000,
|
||||||
|
'Line Description': 'Voluptates voluptas corporis vel.',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-02',
|
||||||
|
'Reference No.': 'REF-2',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Description: 'Id est molestias.',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Expense Account': 'Utilities Expense',
|
||||||
|
Amount: 9000,
|
||||||
|
'Line Description': 'Eos voluptatem cumque et voluptate reiciendis.',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-03',
|
||||||
|
'Reference No.': 'REF-3',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Description: 'Quam cupiditate at nihil dicta dignissimos non fugit illo.',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Expense Account': 'Utilities Expense',
|
||||||
|
Amount: 9000,
|
||||||
|
'Line Description':
|
||||||
|
'Hic alias rerum sed commodi dolores sint animi perferendis.',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import fs from 'fs/promises';
|
|
||||||
import XLSX from 'xlsx';
|
import XLSX from 'xlsx';
|
||||||
import bluebird from 'bluebird';
|
import bluebird from 'bluebird';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
@@ -7,16 +6,17 @@ import { first } from 'lodash';
|
|||||||
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
|
ImportInsertError,
|
||||||
ImportOperError,
|
ImportOperError,
|
||||||
ImportOperSuccess,
|
ImportOperSuccess,
|
||||||
ImportableContext,
|
ImportableContext,
|
||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { trimObject } from './_utils';
|
import { getUniqueImportableValue, trimObject } from './_utils';
|
||||||
import { ImportableResources } from './ImportableResources';
|
import { ImportableResources } from './ImportableResources';
|
||||||
import ResourceService from '../Resource/ResourceService';
|
import ResourceService from '../Resource/ResourceService';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import Import from '@/models/Import';
|
import { Import } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileCommon {
|
export class ImportFileCommon {
|
||||||
@@ -47,14 +47,6 @@ export class ImportFileCommon {
|
|||||||
return XLSX.utils.sheet_to_json(worksheet, {});
|
return XLSX.utils.sheet_to_json(worksheet, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the import file.
|
|
||||||
* @param {string} filename
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public readImportFile(filename: string) {
|
|
||||||
return fs.readFile(`public/imports/${filename}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports the given parsed data to the resource storage through registered importable service.
|
* Imports the given parsed data to the resource storage through registered importable service.
|
||||||
@@ -70,7 +62,7 @@ export class ImportFileCommon {
|
|||||||
parsedData: Record<string, any>[],
|
parsedData: Record<string, any>[],
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<[ImportOperSuccess[], ImportOperError[]]> {
|
): Promise<[ImportOperSuccess[], ImportOperError[]]> {
|
||||||
const importableFields = this.resource.getResourceImportableFields(
|
const resourceFields = this.resource.getResourceFields2(
|
||||||
tenantId,
|
tenantId,
|
||||||
importFile.resource
|
importFile.resource
|
||||||
);
|
);
|
||||||
@@ -88,11 +80,16 @@ export class ImportFileCommon {
|
|||||||
import: importFile,
|
import: importFile,
|
||||||
};
|
};
|
||||||
const transformedDTO = importable.transform(objectDTO, context);
|
const transformedDTO = importable.transform(objectDTO, context);
|
||||||
|
const rowNumber = index + 1;
|
||||||
|
const uniqueValue = getUniqueImportableValue(resourceFields, objectDTO);
|
||||||
|
const errorContext = {
|
||||||
|
rowNumber,
|
||||||
|
uniqueValue,
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
// Validate the DTO object before passing it to the service layer.
|
// Validate the DTO object before passing it to the service layer.
|
||||||
await this.importFileValidator.validateData(
|
await this.importFileValidator.validateData(
|
||||||
importableFields,
|
resourceFields,
|
||||||
transformedDTO
|
transformedDTO
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
@@ -105,18 +102,27 @@ export class ImportFileCommon {
|
|||||||
success.push({ index, data });
|
success.push({ index, data });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ServiceError) {
|
if (err instanceof ServiceError) {
|
||||||
const error = [
|
const error: ImportInsertError[] = [
|
||||||
{
|
{
|
||||||
errorCode: 'ValidationError',
|
errorCode: 'ServiceError',
|
||||||
errorMessage: err.message || err.errorType,
|
errorMessage: err.message || err.errorType,
|
||||||
rowNumber: index + 1,
|
...errorContext,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
failed.push({ index, error });
|
||||||
|
} else {
|
||||||
|
const error: ImportInsertError[] = [
|
||||||
|
{
|
||||||
|
errorCode: 'UnknownError',
|
||||||
|
errorMessage: 'Unknown error occurred',
|
||||||
|
...errorContext,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
failed.push({ index, error });
|
failed.push({ index, error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (errors) {
|
} catch (errors) {
|
||||||
const error = errors.map((er) => ({ ...er, rowNumber: index + 1 }));
|
const error = errors.map((er) => ({ ...er, ...errorContext }));
|
||||||
failed.push({ index, error });
|
failed.push({ index, error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -187,19 +193,4 @@ export class ImportFileCommon {
|
|||||||
public parseSheetColumns(json: unknown[]): string[] {
|
public parseSheetColumns(json: unknown[]): string[] {
|
||||||
return R.compose(Object.keys, trimObject, first)(json);
|
return R.compose(Object.keys, trimObject, first)(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the imported file from the storage and database.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {} importFile
|
|
||||||
*/
|
|
||||||
public async deleteImportFile(tenantId: number, importFile: any) {
|
|
||||||
const { Import } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Deletes the import row.
|
|
||||||
await Import.query().findById(importFile.id).delete();
|
|
||||||
|
|
||||||
// Deletes the imported file.
|
|
||||||
await fs.unlink(`public/imports/${importFile.filename}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
import { Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
import bluebird from 'bluebird';
|
||||||
import { isUndefined, get, chain } from 'lodash';
|
import { isUndefined, pickBy, set } from 'lodash';
|
||||||
|
import { Knex } from 'knex';
|
||||||
import { ImportMappingAttr, ResourceMetaFieldsMap } from './interfaces';
|
import { ImportMappingAttr, ResourceMetaFieldsMap } from './interfaces';
|
||||||
import { parseBoolean } from '@/utils';
|
import {
|
||||||
import { trimObject } from './_utils';
|
valueParser,
|
||||||
|
parseKey,
|
||||||
|
getFieldKey,
|
||||||
|
aggregate,
|
||||||
|
sanitizeSheetData,
|
||||||
|
getMapToPath,
|
||||||
|
} from './_utils';
|
||||||
|
import ResourceService from '../Resource/ResourceService';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
import { CurrencyParsingDTOs } from './_constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileDataTransformer {
|
export class ImportFileDataTransformer {
|
||||||
|
@Inject()
|
||||||
|
private resource: ResourceService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Parses the given sheet data before passing to the service layer.
|
||||||
|
* based on the mapped fields and the each field type.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {}
|
* @param {}
|
||||||
*/
|
*/
|
||||||
public parseSheetData(
|
public async parseSheetData(
|
||||||
|
tenantId: number,
|
||||||
importFile: any,
|
importFile: any,
|
||||||
importableFields: any,
|
importableFields: ResourceMetaFieldsMap,
|
||||||
data: Record<string, unknown>[]
|
data: Record<string, unknown>[],
|
||||||
) {
|
trx?: Knex.Transaction
|
||||||
|
): Promise<Record<string, any>[]> {
|
||||||
// Sanitize the sheet data.
|
// Sanitize the sheet data.
|
||||||
const sanitizedData = this.sanitizeSheetData(data);
|
const sanitizedData = sanitizeSheetData(data);
|
||||||
|
|
||||||
// Map the sheet columns key with the given map.
|
// Map the sheet columns key with the given map.
|
||||||
const mappedDTOs = this.mapSheetColumns(
|
const mappedDTOs = this.mapSheetColumns(
|
||||||
@@ -26,19 +45,44 @@ export class ImportFileDataTransformer {
|
|||||||
importFile.mappingParsed
|
importFile.mappingParsed
|
||||||
);
|
);
|
||||||
// Parse the mapped sheet values.
|
// Parse the mapped sheet values.
|
||||||
const parsedValues = this.parseExcelValues(importableFields, mappedDTOs);
|
const parsedValues = await this.parseExcelValues(
|
||||||
|
tenantId,
|
||||||
return parsedValues;
|
importableFields,
|
||||||
|
mappedDTOs,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
const aggregateValues = this.aggregateParsedValues(
|
||||||
|
tenantId,
|
||||||
|
importFile.resource,
|
||||||
|
parsedValues
|
||||||
|
);
|
||||||
|
return aggregateValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes the data in the imported sheet by trimming object keys.
|
* Aggregates parsed data based on resource metadata configuration.
|
||||||
* @param json - The JSON data representing the imported sheet.
|
* @param {number} tenantId
|
||||||
* @returns {string[][]} - The sanitized data with trimmed object keys.
|
* @param {string} resourceName
|
||||||
|
* @param {Record<string, any>} parsedData
|
||||||
|
* @returns {Record<string, any>[]}
|
||||||
*/
|
*/
|
||||||
public sanitizeSheetData(json) {
|
public aggregateParsedValues = (
|
||||||
return R.compose(R.map(trimObject))(json);
|
tenantId: number,
|
||||||
}
|
resourceName: string,
|
||||||
|
parsedData: Record<string, any>[]
|
||||||
|
): Record<string, any>[] => {
|
||||||
|
let _value = parsedData;
|
||||||
|
const meta = this.resource.getResourceMeta(tenantId, resourceName);
|
||||||
|
|
||||||
|
if (meta.importAggregator === 'group') {
|
||||||
|
_value = aggregate(
|
||||||
|
_value,
|
||||||
|
meta.importAggregateBy,
|
||||||
|
meta.importAggregateOn
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _value;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the columns of the imported data based on the provided mapping attributes.
|
* Maps the columns of the imported data based on the provided mapping attributes.
|
||||||
@@ -55,7 +99,8 @@ export class ImportFileDataTransformer {
|
|||||||
map
|
map
|
||||||
.filter((mapping) => !isUndefined(item[mapping.from]))
|
.filter((mapping) => !isUndefined(item[mapping.from]))
|
||||||
.forEach((mapping) => {
|
.forEach((mapping) => {
|
||||||
newItem[mapping.to] = item[mapping.from];
|
const toPath = getMapToPath(mapping.to, mapping.group);
|
||||||
|
newItem[toPath] = item[mapping.from];
|
||||||
});
|
});
|
||||||
return newItem;
|
return newItem;
|
||||||
});
|
});
|
||||||
@@ -67,35 +112,40 @@ export class ImportFileDataTransformer {
|
|||||||
* @param {Record<string, any>} valueDTOS -
|
* @param {Record<string, any>} valueDTOS -
|
||||||
* @returns {Record<string, any>}
|
* @returns {Record<string, any>}
|
||||||
*/
|
*/
|
||||||
public parseExcelValues(
|
public async parseExcelValues(
|
||||||
|
tenantId: number,
|
||||||
fields: ResourceMetaFieldsMap,
|
fields: ResourceMetaFieldsMap,
|
||||||
valueDTOs: Record<string, any>[]
|
valueDTOs: Record<string, any>[],
|
||||||
): Record<string, any> {
|
trx?: Knex.Transaction
|
||||||
const parser = (value, key) => {
|
): Promise<Record<string, any>[]> {
|
||||||
let _value = value;
|
const tenantModels = this.tenancy.models(tenantId);
|
||||||
|
const _valueParser = valueParser(fields, tenantModels, trx);
|
||||||
|
const _keyParser = parseKey(fields);
|
||||||
|
|
||||||
// Parses the boolean value.
|
const parseAsync = async (valueDTO) => {
|
||||||
if (fields[key].fieldType === 'boolean') {
|
// Clean up the undefined keys that not exist in resource fields.
|
||||||
_value = parseBoolean(value, false);
|
const _valueDTO = pickBy(
|
||||||
|
valueDTO,
|
||||||
|
(value, key) => !isUndefined(fields[getFieldKey(key)])
|
||||||
|
);
|
||||||
|
// Keys of mapped values. key structure: `group.key` or `key`.
|
||||||
|
const keys = Object.keys(_valueDTO);
|
||||||
|
|
||||||
// Parses the enumeration value.
|
// Map the object values.
|
||||||
} else if (fields[key].fieldType === 'enumeration') {
|
return bluebird.reduce(
|
||||||
const field = fields[key];
|
keys,
|
||||||
const option = get(field, 'options', []).find(
|
async (acc, key) => {
|
||||||
(option) => option.label === value
|
const parsedValue = await _valueParser(_valueDTO[key], key);
|
||||||
);
|
const parsedKey = await _keyParser(key);
|
||||||
_value = get(option, 'key');
|
|
||||||
// Prases the numeric value.
|
set(acc, parsedKey, parsedValue);
|
||||||
} else if (fields[key].fieldType === 'number') {
|
return acc;
|
||||||
_value = parseFloat(value);
|
},
|
||||||
}
|
{}
|
||||||
return _value;
|
);
|
||||||
};
|
};
|
||||||
return valueDTOs.map((DTO) => {
|
return bluebird.map(valueDTOs, parseAsync, {
|
||||||
return chain(DTO)
|
concurrency: CurrencyParsingDTOs,
|
||||||
.pickBy((value, key) => !isUndefined(fields[key]))
|
|
||||||
.mapValues(parser)
|
|
||||||
.value();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,14 @@ export class ImportFileDataValidator {
|
|||||||
try {
|
try {
|
||||||
await YupSchema.validate(_data, { abortEarly: false });
|
await YupSchema.validate(_data, { abortEarly: false });
|
||||||
} catch (validationError) {
|
} catch (validationError) {
|
||||||
const errors = validationError.inner.map((error) => ({
|
const errors = validationError.inner.reduce((errors, error) => {
|
||||||
errorCode: 'ValidationError',
|
const newErrors = error.errors.map((errMsg) => ({
|
||||||
errorMessage: error.errors,
|
errorCode: 'ValidationError',
|
||||||
}));
|
errorMessage: errMsg,
|
||||||
|
}));
|
||||||
|
return [...errors, ...newErrors];
|
||||||
|
}, []);
|
||||||
|
|
||||||
throw errors;
|
throw errors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { fromPairs } from 'lodash';
|
import { fromPairs, isUndefined } from 'lodash';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import {
|
import {
|
||||||
ImportDateFormats,
|
ImportDateFormats,
|
||||||
ImportFileMapPOJO,
|
ImportFileMapPOJO,
|
||||||
@@ -9,12 +8,10 @@ import {
|
|||||||
import ResourceService from '../Resource/ResourceService';
|
import ResourceService from '../Resource/ResourceService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { ERRORS } from './_utils';
|
import { ERRORS } from './_utils';
|
||||||
|
import { Import } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileMapping {
|
export class ImportFileMapping {
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private resource: ResourceService;
|
private resource: ResourceService;
|
||||||
|
|
||||||
@@ -29,8 +26,6 @@ export class ImportFileMapping {
|
|||||||
importId: number,
|
importId: number,
|
||||||
maps: ImportMappingAttr[]
|
maps: ImportMappingAttr[]
|
||||||
): Promise<ImportFileMapPOJO> {
|
): Promise<ImportFileMapPOJO> {
|
||||||
const { Import } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const importFile = await Import.query()
|
const importFile = await Import.query()
|
||||||
.findOne('filename', importId)
|
.findOne('filename', importId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
@@ -69,7 +64,7 @@ export class ImportFileMapping {
|
|||||||
importFile: any,
|
importFile: any,
|
||||||
maps: ImportMappingAttr[]
|
maps: ImportMappingAttr[]
|
||||||
) {
|
) {
|
||||||
const fields = this.resource.getResourceImportableFields(
|
const fields = this.resource.getResourceFields2(
|
||||||
tenantId,
|
tenantId,
|
||||||
importFile.resource
|
importFile.resource
|
||||||
);
|
);
|
||||||
@@ -78,11 +73,20 @@ export class ImportFileMapping {
|
|||||||
);
|
);
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
|
|
||||||
|
// is not empty, is not undefined or map.group
|
||||||
maps.forEach((map) => {
|
maps.forEach((map) => {
|
||||||
if (
|
let _invalid = true;
|
||||||
'undefined' === typeof fields[map.to] ||
|
|
||||||
'undefined' === typeof columnsMap[map.from]
|
if (!map.group && fields[map.to]) {
|
||||||
) {
|
_invalid = false;
|
||||||
|
}
|
||||||
|
if (map.group && fields[map.group] && fields[map.group]?.fields[map.to]) {
|
||||||
|
_invalid = false;
|
||||||
|
}
|
||||||
|
if (columnsMap[map.from]) {
|
||||||
|
_invalid = false;
|
||||||
|
}
|
||||||
|
if (_invalid) {
|
||||||
invalid.push(map);
|
invalid.push(map);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -105,10 +109,14 @@ export class ImportFileMapping {
|
|||||||
} else {
|
} else {
|
||||||
fromMap[map.from] = true;
|
fromMap[map.from] = true;
|
||||||
}
|
}
|
||||||
if (toMap[map.to]) {
|
const toPath = !isUndefined(map?.group)
|
||||||
|
? `${map.group}.${map.to}`
|
||||||
|
: map.to;
|
||||||
|
|
||||||
|
if (toMap[toPath]) {
|
||||||
throw new ServiceError(ERRORS.DUPLICATED_TO_MAP_ATTR);
|
throw new ServiceError(ERRORS.DUPLICATED_TO_MAP_ATTR);
|
||||||
} else {
|
} else {
|
||||||
toMap[map.to] = true;
|
toMap[toPath] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,6 +136,7 @@ export class ImportFileMapping {
|
|||||||
tenantId,
|
tenantId,
|
||||||
resource
|
resource
|
||||||
);
|
);
|
||||||
|
// @todo Validate date type of the nested fields.
|
||||||
maps.forEach((map) => {
|
maps.forEach((map) => {
|
||||||
if (
|
if (
|
||||||
typeof fields[map.to] !== 'undefined' &&
|
typeof fields[map.to] !== 'undefined' &&
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { ImportFileMetaTransformer } from './ImportFileMetaTransformer';
|
import { ImportFileMetaTransformer } from './ImportFileMetaTransformer';
|
||||||
|
import { Import } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileMeta {
|
export class ImportFileMeta {
|
||||||
@@ -12,15 +13,15 @@ export class ImportFileMeta {
|
|||||||
private transformer: TransformerInjectable;
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Retrieves the import meta of the given import model id.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} importId
|
* @param {number} importId
|
||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
async getImportMeta(tenantId: number, importId: string) {
|
async getImportMeta(tenantId: number, importId: string) {
|
||||||
const { Import } = this.tenancy.models(tenantId);
|
const importFile = await Import.query()
|
||||||
|
.where('tenantId', tenantId)
|
||||||
const importFile = await Import.query().findOne('importId', importId);
|
.findOne('importId', importId);
|
||||||
|
|
||||||
// Retrieves the transformed accounts collection.
|
// Retrieves the transformed accounts collection.
|
||||||
return this.transformer.transform(
|
return this.transformer.transform(
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { ERRORS, getSheetColumns, getUnmappedSheetColumns } from './_utils';
|
import {
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
ERRORS,
|
||||||
|
getSheetColumns,
|
||||||
|
getUnmappedSheetColumns,
|
||||||
|
readImportFile,
|
||||||
|
} from './_utils';
|
||||||
import { ImportFileCommon } from './ImportFileCommon';
|
import { ImportFileCommon } from './ImportFileCommon';
|
||||||
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
|
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
|
||||||
import ResourceService from '../Resource/ResourceService';
|
import ResourceService from '../Resource/ResourceService';
|
||||||
import UnitOfWork from '../UnitOfWork';
|
import UnitOfWork from '../UnitOfWork';
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
import { ImportFilePreviewPOJO } from './interfaces';
|
||||||
|
import { Import } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileProcess {
|
export class ImportFileProcess {
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private resource: ResourceService;
|
private resource: ResourceService;
|
||||||
|
|
||||||
@@ -38,10 +40,9 @@ export class ImportFileProcess {
|
|||||||
importId: number,
|
importId: number,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
): Promise<ImportFilePreviewPOJO> {
|
||||||
const { Import } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const importFile = await Import.query()
|
const importFile = await Import.query()
|
||||||
.findOne('importId', importId)
|
.findOne('importId', importId)
|
||||||
|
.where('tenantId', tenantId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
// Throw error if the import file is not mapped yet.
|
// Throw error if the import file is not mapped yet.
|
||||||
@@ -49,27 +50,37 @@ export class ImportFileProcess {
|
|||||||
throw new ServiceError(ERRORS.IMPORT_FILE_NOT_MAPPED);
|
throw new ServiceError(ERRORS.IMPORT_FILE_NOT_MAPPED);
|
||||||
}
|
}
|
||||||
// Read the imported file.
|
// Read the imported file.
|
||||||
const buffer = await this.importCommon.readImportFile(importFile.filename);
|
const buffer = await readImportFile(importFile.filename);
|
||||||
const sheetData = this.importCommon.parseXlsxSheet(buffer);
|
const sheetData = this.importCommon.parseXlsxSheet(buffer);
|
||||||
const header = getSheetColumns(sheetData);
|
const header = getSheetColumns(sheetData);
|
||||||
|
|
||||||
const importableFields = this.resource.getResourceImportableFields(
|
const resource = importFile.resource;
|
||||||
tenantId,
|
const resourceFields = this.resource.getResourceFields2(tenantId, resource);
|
||||||
importFile.resource
|
|
||||||
);
|
|
||||||
// Prases the sheet json data.
|
|
||||||
const parsedData = this.importParser.parseSheetData(
|
|
||||||
importFile,
|
|
||||||
importableFields,
|
|
||||||
sheetData
|
|
||||||
);
|
|
||||||
// Runs the importing operation with ability to return errors that will happen.
|
// Runs the importing operation with ability to return errors that will happen.
|
||||||
const [successedImport, failedImport] = await this.uow.withTransaction(
|
const [successedImport, failedImport, allData] =
|
||||||
tenantId,
|
await this.uow.withTransaction(
|
||||||
(trx: Knex.Transaction) =>
|
tenantId,
|
||||||
this.importCommon.import(tenantId, importFile, parsedData, trx),
|
async (trx: Knex.Transaction) => {
|
||||||
trx
|
// Prases the sheet json data.
|
||||||
);
|
const parsedData = await this.importParser.parseSheetData(
|
||||||
|
tenantId,
|
||||||
|
importFile,
|
||||||
|
resourceFields,
|
||||||
|
sheetData,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
const [successedImport, failedImport] =
|
||||||
|
await this.importCommon.import(
|
||||||
|
tenantId,
|
||||||
|
importFile,
|
||||||
|
parsedData,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
return [successedImport, failedImport, parsedData];
|
||||||
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
const mapping = importFile.mappingParsed;
|
const mapping = importFile.mappingParsed;
|
||||||
const errors = chain(failedImport)
|
const errors = chain(failedImport)
|
||||||
.map((oper) => oper.error)
|
.map((oper) => oper.error)
|
||||||
@@ -77,13 +88,14 @@ export class ImportFileProcess {
|
|||||||
.value();
|
.value();
|
||||||
|
|
||||||
const unmappedColumns = getUnmappedSheetColumns(header, mapping);
|
const unmappedColumns = getUnmappedSheetColumns(header, mapping);
|
||||||
const totalCount = parsedData.length;
|
const totalCount = allData.length;
|
||||||
|
|
||||||
const createdCount = successedImport.length;
|
const createdCount = successedImport.length;
|
||||||
const errorsCount = failedImport.length;
|
const errorsCount = failedImport.length;
|
||||||
const skippedCount = errorsCount;
|
const skippedCount = errorsCount;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
resource,
|
||||||
createdCount,
|
createdCount,
|
||||||
skippedCount,
|
skippedCount,
|
||||||
totalCount,
|
totalCount,
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import {
|
||||||
import { sanitizeResourceName } from './_utils';
|
deleteImportFile,
|
||||||
|
getResourceColumns,
|
||||||
|
readImportFile,
|
||||||
|
sanitizeResourceName,
|
||||||
|
validateSheetEmpty,
|
||||||
|
} from './_utils';
|
||||||
import ResourceService from '../Resource/ResourceService';
|
import ResourceService from '../Resource/ResourceService';
|
||||||
import { IModelMetaField } from '@/interfaces';
|
|
||||||
import { ImportFileCommon } from './ImportFileCommon';
|
import { ImportFileCommon } from './ImportFileCommon';
|
||||||
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
||||||
import { ImportFileUploadPOJO } from './interfaces';
|
import { ImportFileUploadPOJO } from './interfaces';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { Import } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileUploadService {
|
export class ImportFileUploadService {
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private resourceService: ResourceService;
|
private resourceService: ResourceService;
|
||||||
|
|
||||||
@@ -23,11 +24,12 @@ export class ImportFileUploadService {
|
|||||||
private importValidator: ImportFileDataValidator;
|
private importValidator: ImportFileDataValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the imported file and stores the import file meta under unqiue id.
|
* Imports the specified file for the given resource.
|
||||||
* @param {number} tenantId - Tenant id.
|
* Deletes the file if an error occurs during the import process.
|
||||||
* @param {string} resource - Resource name.
|
* @param {number} tenantId
|
||||||
* @param {string} filePath - File path.
|
* @param {string} resourceName
|
||||||
* @param {string} fileName - File name.
|
* @param {string} filename
|
||||||
|
* @param {Record<string, number | string>} params
|
||||||
* @returns {Promise<ImportFileUploadPOJO>}
|
* @returns {Promise<ImportFileUploadPOJO>}
|
||||||
*/
|
*/
|
||||||
public async import(
|
public async import(
|
||||||
@@ -36,8 +38,35 @@ export class ImportFileUploadService {
|
|||||||
filename: string,
|
filename: string,
|
||||||
params: Record<string, number | string>
|
params: Record<string, number | string>
|
||||||
): Promise<ImportFileUploadPOJO> {
|
): Promise<ImportFileUploadPOJO> {
|
||||||
const { Import } = this.tenancy.models(tenantId);
|
console.log(filename, 'filename');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.importUnhandled(
|
||||||
|
tenantId,
|
||||||
|
resourceName,
|
||||||
|
filename,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
deleteImportFile(filename);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the imported file and stores the import file meta under unqiue id.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {string} resource - Resource name.
|
||||||
|
* @param {string} filePath - File path.
|
||||||
|
* @param {string} fileName - File name.
|
||||||
|
* @returns {Promise<ImportFileUploadPOJO>}
|
||||||
|
*/
|
||||||
|
public async importUnhandled(
|
||||||
|
tenantId: number,
|
||||||
|
resourceName: string,
|
||||||
|
filename: string,
|
||||||
|
params: Record<string, number | string>
|
||||||
|
): Promise<ImportFileUploadPOJO> {
|
||||||
const resource = sanitizeResourceName(resourceName);
|
const resource = sanitizeResourceName(resourceName);
|
||||||
const resourceMeta = this.resourceService.getResourceMeta(
|
const resourceMeta = this.resourceService.getResourceMeta(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -47,10 +76,14 @@ export class ImportFileUploadService {
|
|||||||
this.importValidator.validateResourceImportable(resourceMeta);
|
this.importValidator.validateResourceImportable(resourceMeta);
|
||||||
|
|
||||||
// Reads the imported file into buffer.
|
// Reads the imported file into buffer.
|
||||||
const buffer = await this.importFileCommon.readImportFile(filename);
|
const buffer = await readImportFile(filename);
|
||||||
|
|
||||||
// Parse the buffer file to array data.
|
// Parse the buffer file to array data.
|
||||||
const sheetData = this.importFileCommon.parseXlsxSheet(buffer);
|
const sheetData = this.importFileCommon.parseXlsxSheet(buffer);
|
||||||
|
|
||||||
|
// Throws service error if the sheet data is empty.
|
||||||
|
validateSheetEmpty(sheetData);
|
||||||
|
|
||||||
const sheetColumns = this.importFileCommon.parseSheetColumns(sheetData);
|
const sheetColumns = this.importFileCommon.parseSheetColumns(sheetData);
|
||||||
const coumnsStringified = JSON.stringify(sheetColumns);
|
const coumnsStringified = JSON.stringify(sheetColumns);
|
||||||
|
|
||||||
@@ -70,15 +103,16 @@ export class ImportFileUploadService {
|
|||||||
const importFile = await Import.query().insert({
|
const importFile = await Import.query().insert({
|
||||||
filename,
|
filename,
|
||||||
resource,
|
resource,
|
||||||
|
tenantId,
|
||||||
importId: filename,
|
importId: filename,
|
||||||
columns: coumnsStringified,
|
columns: coumnsStringified,
|
||||||
params: paramsStringified,
|
params: paramsStringified,
|
||||||
});
|
});
|
||||||
const resourceColumnsMap = this.resourceService.getResourceImportableFields(
|
const resourceColumnsMap = this.resourceService.getResourceFields2(
|
||||||
tenantId,
|
tenantId,
|
||||||
resource
|
resource
|
||||||
);
|
);
|
||||||
const resourceColumns = this.getResourceColumns(resourceColumnsMap);
|
const resourceColumns = getResourceColumns(resourceColumnsMap);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
import: {
|
import: {
|
||||||
@@ -89,23 +123,4 @@ export class ImportFileUploadService {
|
|||||||
resourceColumns,
|
resourceColumns,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getResourceColumns(resourceColumns: { [key: string]: IModelMetaField }) {
|
|
||||||
return Object.entries(resourceColumns)
|
|
||||||
.map(
|
|
||||||
([key, { name, importHint, required, order }]: [
|
|
||||||
string,
|
|
||||||
IModelMetaField
|
|
||||||
]) => ({
|
|
||||||
key,
|
|
||||||
name,
|
|
||||||
required,
|
|
||||||
hint: importHint,
|
|
||||||
order,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.sort((a, b) =>
|
|
||||||
a.order && b.order ? a.order - b.order : a.order ? -1 : b.order ? 1 : 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import bluebird from 'bluebird';
|
||||||
|
import { Import } from '@/system/models';
|
||||||
|
import { deleteImportFile } from './_utils';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ImportDeleteExpiredFiles {
|
||||||
|
/**
|
||||||
|
* Delete expired files.
|
||||||
|
*/
|
||||||
|
async deleteExpiredFiles() {
|
||||||
|
const yesterday = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm');
|
||||||
|
|
||||||
|
const expiredImports = await Import.query().where(
|
||||||
|
'createdAt',
|
||||||
|
'<',
|
||||||
|
yesterday
|
||||||
|
);
|
||||||
|
await bluebird.map(
|
||||||
|
expiredImports,
|
||||||
|
async (expiredImport) => {
|
||||||
|
await deleteImportFile(expiredImport.filename);
|
||||||
|
},
|
||||||
|
{ concurrency: 10 }
|
||||||
|
);
|
||||||
|
const expiredImportsIds = expiredImports.map(
|
||||||
|
(expiredImport) => expiredImport.id
|
||||||
|
);
|
||||||
|
if (expiredImportsIds.length > 0) {
|
||||||
|
await Import.query().whereIn('id', expiredImportsIds).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ export class ImportableRegistry {
|
|||||||
private static instance: ImportableRegistry;
|
private static instance: ImportableRegistry;
|
||||||
private importables: Record<string, Importable>;
|
private importables: Record<string, Importable>;
|
||||||
|
|
||||||
private constructor() {
|
constructor() {
|
||||||
this.importables = {};
|
this.importables = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,18 @@ import { ImportableRegistry } from './ImportableRegistry';
|
|||||||
import { UncategorizedTransactionsImportable } from '../Cashflow/UncategorizedTransactionsImportable';
|
import { UncategorizedTransactionsImportable } from '../Cashflow/UncategorizedTransactionsImportable';
|
||||||
import { CustomersImportable } from '../Contacts/Customers/CustomersImportable';
|
import { CustomersImportable } from '../Contacts/Customers/CustomersImportable';
|
||||||
import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable';
|
import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable';
|
||||||
|
import { ItemsImportable } from '../Items/ItemsImportable';
|
||||||
|
import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable';
|
||||||
|
import { ManualJournalImportable } from '../ManualJournals/ManualJournalsImport';
|
||||||
|
import { BillsImportable } from '../Purchases/Bills/BillsImportable';
|
||||||
|
import { ExpensesImportable } from '../Expenses/ExpensesImportable';
|
||||||
|
import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable';
|
||||||
|
import { SaleEstimatesImportable } from '../Sales/Estimates/SaleEstimatesImportable';
|
||||||
|
import { BillPaymentsImportable } from '../Purchases/BillPayments/BillPaymentsImportable';
|
||||||
|
import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCreditsImportable';
|
||||||
|
import { PaymentReceivesImportable } from '../Sales/PaymentReceives/PaymentReceivesImportable';
|
||||||
|
import { CreditNotesImportable } from '../CreditNotes/CreditNotesImportable';
|
||||||
|
import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportableResources {
|
export class ImportableResources {
|
||||||
@@ -24,6 +36,18 @@ export class ImportableResources {
|
|||||||
},
|
},
|
||||||
{ resource: 'Customer', importable: CustomersImportable },
|
{ resource: 'Customer', importable: CustomersImportable },
|
||||||
{ resource: 'Vendor', importable: VendorsImportable },
|
{ resource: 'Vendor', importable: VendorsImportable },
|
||||||
|
{ resource: 'Item', importable: ItemsImportable },
|
||||||
|
{ resource: 'ItemCategory', importable: ItemCategoriesImportable },
|
||||||
|
{ resource: 'ManualJournal', importable: ManualJournalImportable },
|
||||||
|
{ resource: 'Bill', importable: BillsImportable },
|
||||||
|
{ resource: 'Expense', importable: ExpensesImportable },
|
||||||
|
{ resource: 'SaleInvoice', importable: SaleInvoicesImportable },
|
||||||
|
{ resource: 'SaleEstimate', importable: SaleEstimatesImportable },
|
||||||
|
{ resource: 'BillPayment', importable: BillPaymentsImportable },
|
||||||
|
{ resource: 'PaymentReceive', importable: PaymentReceivesImportable },
|
||||||
|
{ resource: 'VendorCredit', importable: VendorCreditsImportable },
|
||||||
|
{ resource: 'CreditNote', importable: CreditNotesImportable },
|
||||||
|
{ resource: 'SaleReceipt', importable: SaleReceiptsImportable }
|
||||||
];
|
];
|
||||||
|
|
||||||
public get registry() {
|
public get registry() {
|
||||||
|
|||||||
3
packages/server/src/services/Import/_constants.ts
Normal file
3
packages/server/src/services/Import/_constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const CurrencyParsingDTOs = 10;
|
||||||
@@ -1,9 +1,27 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { upperFirst, camelCase, first, isUndefined } from 'lodash';
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import {
|
||||||
|
defaultTo,
|
||||||
|
upperFirst,
|
||||||
|
camelCase,
|
||||||
|
first,
|
||||||
|
isUndefined,
|
||||||
|
pickBy,
|
||||||
|
isEmpty,
|
||||||
|
castArray,
|
||||||
|
get,
|
||||||
|
head,
|
||||||
|
split,
|
||||||
|
last,
|
||||||
|
} from 'lodash';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { ResourceMetaFieldsMap } from './interfaces';
|
import { ResourceMetaFieldsMap } from './interfaces';
|
||||||
import { IModelMetaField } from '@/interfaces';
|
import { IModelMetaField, IModelMetaField2 } from '@/interfaces';
|
||||||
import moment from 'moment';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { multiNumberParse } from '@/utils/multi-number-parse';
|
||||||
|
|
||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
RESOURCE_NOT_IMPORTABLE: 'RESOURCE_NOT_IMPORTABLE',
|
RESOURCE_NOT_IMPORTABLE: 'RESOURCE_NOT_IMPORTABLE',
|
||||||
@@ -13,9 +31,15 @@ export const ERRORS = {
|
|||||||
IMPORT_FILE_NOT_MAPPED: 'IMPORT_FILE_NOT_MAPPED',
|
IMPORT_FILE_NOT_MAPPED: 'IMPORT_FILE_NOT_MAPPED',
|
||||||
INVALID_MAP_DATE_FORMAT: 'INVALID_MAP_DATE_FORMAT',
|
INVALID_MAP_DATE_FORMAT: 'INVALID_MAP_DATE_FORMAT',
|
||||||
MAP_DATE_FORMAT_NOT_DEFINED: 'MAP_DATE_FORMAT_NOT_DEFINED',
|
MAP_DATE_FORMAT_NOT_DEFINED: 'MAP_DATE_FORMAT_NOT_DEFINED',
|
||||||
|
IMPORTED_SHEET_EMPTY: 'IMPORTED_SHEET_EMPTY',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function trimObject(obj) {
|
/**
|
||||||
|
* Trimms the imported object string values before parsing.
|
||||||
|
* @param {Record<string, string | number>} obj
|
||||||
|
* @returns {<Record<string, string | number>}
|
||||||
|
*/
|
||||||
|
export function trimObject(obj: Record<string, string | number>) {
|
||||||
return Object.entries(obj).reduce((acc, [key, value]) => {
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
||||||
// Trim the key
|
// Trim the key
|
||||||
const trimmedKey = key.trim();
|
const trimmedKey = key.trim();
|
||||||
@@ -28,8 +52,14 @@ export function trimObject(obj) {
|
|||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the Yup validation schema based on the given resource fields.
|
||||||
|
* @param {ResourceMetaFieldsMap} fields
|
||||||
|
* @returns {Yup}
|
||||||
|
*/
|
||||||
export const convertFieldsToYupValidation = (fields: ResourceMetaFieldsMap) => {
|
export const convertFieldsToYupValidation = (fields: ResourceMetaFieldsMap) => {
|
||||||
const yupSchema = {};
|
const yupSchema = {};
|
||||||
|
|
||||||
Object.keys(fields).forEach((fieldName: string) => {
|
Object.keys(fields).forEach((fieldName: string) => {
|
||||||
const field = fields[fieldName] as IModelMetaField;
|
const field = fields[fieldName] as IModelMetaField;
|
||||||
let fieldSchema;
|
let fieldSchema;
|
||||||
@@ -79,15 +109,43 @@ export const convertFieldsToYupValidation = (fields: ResourceMetaFieldsMap) => {
|
|||||||
);
|
);
|
||||||
} else if (field.fieldType === 'url') {
|
} else if (field.fieldType === 'url') {
|
||||||
fieldSchema = fieldSchema.url();
|
fieldSchema = fieldSchema.url();
|
||||||
|
} else if (field.fieldType === 'collection') {
|
||||||
|
const nestedFieldShema = convertFieldsToYupValidation(field.fields);
|
||||||
|
fieldSchema = Yup.array().label(field.name);
|
||||||
|
|
||||||
|
if (!isUndefined(field.collectionMaxLength)) {
|
||||||
|
fieldSchema = fieldSchema.max(field.collectionMaxLength);
|
||||||
|
}
|
||||||
|
if (!isUndefined(field.collectionMinLength)) {
|
||||||
|
fieldSchema = fieldSchema.min(field.collectionMinLength);
|
||||||
|
}
|
||||||
|
fieldSchema = fieldSchema.of(nestedFieldShema);
|
||||||
}
|
}
|
||||||
if (field.required) {
|
if (field.required) {
|
||||||
fieldSchema = fieldSchema.required();
|
fieldSchema = fieldSchema.required();
|
||||||
}
|
}
|
||||||
yupSchema[fieldName] = fieldSchema;
|
const _fieldName = parseFieldName(fieldName, field);
|
||||||
|
|
||||||
|
yupSchema[_fieldName] = fieldSchema;
|
||||||
});
|
});
|
||||||
return Yup.object().shape(yupSchema);
|
return Yup.object().shape(yupSchema);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseFieldName = (fieldName: string, field: IModelMetaField) => {
|
||||||
|
let _key = fieldName;
|
||||||
|
|
||||||
|
if (field.dataTransferObjectKey) {
|
||||||
|
_key = field.dataTransferObjectKey;
|
||||||
|
}
|
||||||
|
return _key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the unmapped sheet columns.
|
||||||
|
* @param columns
|
||||||
|
* @param mapping
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const getUnmappedSheetColumns = (columns, mapping) => {
|
export const getUnmappedSheetColumns = (columns, mapping) => {
|
||||||
return columns.filter(
|
return columns.filter(
|
||||||
(column) => !mapping.some((map) => map.from === column)
|
(column) => !mapping.some((map) => map.from === column)
|
||||||
@@ -101,3 +159,293 @@ export const sanitizeResourceName = (resourceName: string) => {
|
|||||||
export const getSheetColumns = (sheetData: unknown[]) => {
|
export const getSheetColumns = (sheetData: unknown[]) => {
|
||||||
return Object.keys(first(sheetData));
|
return Object.keys(first(sheetData));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the unique value from the given imported object DTO based on the
|
||||||
|
* configured unique resource field.
|
||||||
|
* @param {{ [key: string]: IModelMetaField }} importableFields -
|
||||||
|
* @param {<Record<string, any>}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getUniqueImportableValue = (
|
||||||
|
importableFields: { [key: string]: IModelMetaField2 },
|
||||||
|
objectDTO: Record<string, any>
|
||||||
|
) => {
|
||||||
|
const uniqueImportableValue = pickBy(
|
||||||
|
importableFields,
|
||||||
|
(field) => field.unique
|
||||||
|
);
|
||||||
|
const uniqueImportableKeys = Object.keys(uniqueImportableValue);
|
||||||
|
const uniqueImportableKey = first(uniqueImportableKeys);
|
||||||
|
|
||||||
|
return defaultTo(objectDTO[uniqueImportableKey], '');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws service error the given sheet is empty.
|
||||||
|
* @param {Array<any>} sheetData
|
||||||
|
*/
|
||||||
|
export const validateSheetEmpty = (sheetData: Array<any>) => {
|
||||||
|
if (isEmpty(sheetData)) {
|
||||||
|
throw new ServiceError(ERRORS.IMPORTED_SHEET_EMPTY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const booleanValuesRepresentingTrue: string[] = ['true', 'yes', 'y', 't', '1'];
|
||||||
|
const booleanValuesRepresentingFalse: string[] = ['false', 'no', 'n', 'f', '0'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given string value to boolean.
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string|null}
|
||||||
|
*/
|
||||||
|
export const parseBoolean = (value: string): boolean | null => {
|
||||||
|
const normalizeValue = (value: string): string =>
|
||||||
|
value.toString().trim().toLowerCase();
|
||||||
|
|
||||||
|
const normalizedValue = normalizeValue(value);
|
||||||
|
const valuesRepresentingTrue =
|
||||||
|
booleanValuesRepresentingTrue.map(normalizeValue);
|
||||||
|
const valueRepresentingFalse =
|
||||||
|
booleanValuesRepresentingFalse.map(normalizeValue);
|
||||||
|
|
||||||
|
if (valuesRepresentingTrue.includes(normalizedValue)) {
|
||||||
|
return true;
|
||||||
|
} else if (valueRepresentingFalse.includes(normalizedValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformInputToGroupedFields = (input) => {
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
// Group for non-nested fields
|
||||||
|
const mainGroup = {
|
||||||
|
groupLabel: '',
|
||||||
|
groupKey: '',
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
input.forEach((item) => {
|
||||||
|
if (!item.fields) {
|
||||||
|
// If the item does not have nested fields, add it to the main group
|
||||||
|
mainGroup.fields.push(item);
|
||||||
|
} else {
|
||||||
|
// If the item has nested fields, create a new group for these fields
|
||||||
|
output.push({
|
||||||
|
groupLabel: item.name,
|
||||||
|
groupKey: item.key,
|
||||||
|
fields: item.fields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Add the main group to the output if it contains any fields
|
||||||
|
if (mainGroup.fields.length > 0) {
|
||||||
|
output.unshift(mainGroup); // Add the main group at the beginning
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getResourceColumns = (resourceColumns: {
|
||||||
|
[key: string]: IModelMetaField2;
|
||||||
|
}) => {
|
||||||
|
const mapColumn =
|
||||||
|
(group: string) =>
|
||||||
|
([fieldKey, { name, importHint, required, order, ...field }]: [
|
||||||
|
string,
|
||||||
|
IModelMetaField2
|
||||||
|
]) => {
|
||||||
|
const extra: Record<string, any> = {};
|
||||||
|
const key = fieldKey;
|
||||||
|
|
||||||
|
if (group) {
|
||||||
|
extra.group = group;
|
||||||
|
}
|
||||||
|
if (field.fieldType === 'collection') {
|
||||||
|
extra.fields = mapColumns(field.fields, key);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
name,
|
||||||
|
required,
|
||||||
|
hint: importHint,
|
||||||
|
order,
|
||||||
|
...extra,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const sortColumn = (a, b) =>
|
||||||
|
a.order && b.order ? a.order - b.order : a.order ? -1 : b.order ? 1 : 0;
|
||||||
|
|
||||||
|
const mapColumns = (columns, parentKey = '') =>
|
||||||
|
Object.entries(columns).map(mapColumn(parentKey)).sort(sortColumn);
|
||||||
|
|
||||||
|
return R.compose(transformInputToGroupedFields, mapColumns)(resourceColumns);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prases the given object value based on the field key type.
|
||||||
|
export const valueParser =
|
||||||
|
(fields: ResourceMetaFieldsMap, tenantModels: any, trx?: Knex.Transaction) =>
|
||||||
|
async (value: any, key: string, group = '') => {
|
||||||
|
let _value = value;
|
||||||
|
|
||||||
|
const fieldKey = key.includes('.') ? key.split('.')[0] : key;
|
||||||
|
const field = group ? fields[group]?.fields[fieldKey] : fields[fieldKey];
|
||||||
|
|
||||||
|
// Parses the boolean value.
|
||||||
|
if (field.fieldType === 'boolean') {
|
||||||
|
_value = parseBoolean(value);
|
||||||
|
|
||||||
|
// Parses the enumeration value.
|
||||||
|
} else if (field.fieldType === 'enumeration') {
|
||||||
|
const option = get(field, 'options', []).find(
|
||||||
|
(option) => option.label === value
|
||||||
|
);
|
||||||
|
_value = get(option, 'key');
|
||||||
|
// Parses the numeric value.
|
||||||
|
} else if (field.fieldType === 'number') {
|
||||||
|
_value = multiNumberParse(value);
|
||||||
|
// Parses the relation value.
|
||||||
|
} else if (field.fieldType === 'relation') {
|
||||||
|
const RelationModel = tenantModels[field.relationModel];
|
||||||
|
|
||||||
|
if (!RelationModel) {
|
||||||
|
throw new Error(`The relation model of ${key} field is not exist.`);
|
||||||
|
}
|
||||||
|
const relationQuery = RelationModel.query(trx);
|
||||||
|
const relationKeys = castArray(field?.relationImportMatch);
|
||||||
|
|
||||||
|
relationQuery.where(function () {
|
||||||
|
relationKeys.forEach((relationKey: string) => {
|
||||||
|
this.orWhereRaw('LOWER(??) = LOWER(?)', [relationKey, value]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const result = await relationQuery.first();
|
||||||
|
_value = get(result, 'id');
|
||||||
|
} else if (field.fieldType === 'collection') {
|
||||||
|
const ObjectFieldKey = key.includes('.') ? key.split('.')[1] : key;
|
||||||
|
const _valueParser = valueParser(fields, tenantModels);
|
||||||
|
_value = await _valueParser(value, ObjectFieldKey, fieldKey);
|
||||||
|
}
|
||||||
|
return _value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the field key and detarmines the key path.
|
||||||
|
* @param {{ [key: string]: IModelMetaField2 }} fields
|
||||||
|
* @param {string} key - Mapped key path. formats: `group.key` or `key`.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const parseKey = R.curry(
|
||||||
|
(fields: { [key: string]: IModelMetaField2 }, key: string) => {
|
||||||
|
const fieldKey = getFieldKey(key);
|
||||||
|
const field = fields[fieldKey];
|
||||||
|
let _key = key;
|
||||||
|
|
||||||
|
if (field.fieldType === 'collection') {
|
||||||
|
if (field.collectionOf === 'object') {
|
||||||
|
const nestedFieldKey = last(key.split('.'));
|
||||||
|
_key = `${fieldKey}[0].${nestedFieldKey}`;
|
||||||
|
} else if (
|
||||||
|
field.collectionOf === 'string' ||
|
||||||
|
field.collectionOf ||
|
||||||
|
'numberic'
|
||||||
|
) {
|
||||||
|
_key = `${fieldKey}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(_key);
|
||||||
|
return _key;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the field root key, for instance: I -> entries.itemId O -> entries.
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getFieldKey = (input: string) => {
|
||||||
|
const keys = split(input, '.');
|
||||||
|
const firstKey = head(keys).split('[')[0]; // Split by "[" in case of array notation
|
||||||
|
return firstKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
{ * Aggregates the input array of objects based on a comparator attribute and groups the entries.
|
||||||
|
* This function is useful for combining multiple entries into a single entry based on a specific attribute,
|
||||||
|
* while aggregating other attributes into an array.}
|
||||||
|
*
|
||||||
|
* @param {Array} input - The array of objects to be aggregated.
|
||||||
|
* @param {string} comparatorAttr - The attribute of the objects used for comparison to aggregate.
|
||||||
|
* @param {string} groupOn - The attribute of the objects where the grouped entries will be pushed.
|
||||||
|
* @returns {Array} - The aggregated array of objects.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Example input:
|
||||||
|
* const input = [
|
||||||
|
* { id: 1, name: 'John', entries: ['entry1'] },
|
||||||
|
* { id: 2, name: 'Jane', entries: ['entry2'] },
|
||||||
|
* { id: 1, name: 'John', entries: ['entry3'] },
|
||||||
|
* ];
|
||||||
|
* const comparatorAttr = 'id';
|
||||||
|
* const groupOn = 'entries';
|
||||||
|
*
|
||||||
|
* // Example output:
|
||||||
|
* const output = [
|
||||||
|
* { id: 1, name: 'John', entries: ['entry1', 'entry3'] },
|
||||||
|
* { id: 2, name: 'Jane', entries: ['entry2'] },
|
||||||
|
* ];
|
||||||
|
*/
|
||||||
|
export function aggregate(
|
||||||
|
input: Array<any>,
|
||||||
|
comparatorAttr: string,
|
||||||
|
groupOn: string
|
||||||
|
): Array<Record<string, any>> {
|
||||||
|
return input.reduce((acc, curr) => {
|
||||||
|
const existingEntry = acc.find(
|
||||||
|
(entry) => entry[comparatorAttr] === curr[comparatorAttr]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingEntry) {
|
||||||
|
existingEntry[groupOn].push(...curr.entries);
|
||||||
|
} else {
|
||||||
|
acc.push({ ...curr });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes the data in the imported sheet by trimming object keys.
|
||||||
|
* @param json - The JSON data representing the imported sheet.
|
||||||
|
* @returns {string[][]} - The sanitized data with trimmed object keys.
|
||||||
|
*/
|
||||||
|
export const sanitizeSheetData = (json) => {
|
||||||
|
return R.compose(R.map(trimObject))(json);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to map a value to based on the 'to' and 'group' parameters.
|
||||||
|
* @param {string} to - The target key to map the value to.
|
||||||
|
* @param {string} group - The group key to nest the target key under.
|
||||||
|
* @returns {string} - The path to map the value to.
|
||||||
|
*/
|
||||||
|
export const getMapToPath = (to: string, group = '') =>
|
||||||
|
group ? `${group}.${to}` : to;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the imported file from the storage and database.
|
||||||
|
* @param {string} filename
|
||||||
|
*/
|
||||||
|
export const deleteImportFile = async (filename: string) => {
|
||||||
|
// Deletes the imported file.
|
||||||
|
await fs.unlink(`public/imports/${filename}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the import file.
|
||||||
|
* @param {string} filename
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
export const readImportFile = (filename: string) => {
|
||||||
|
return fs.readFile(`public/imports/${filename}`);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { IModelMetaField } from '@/interfaces';
|
import { IModelMetaField, IModelMetaField2 } from '@/interfaces';
|
||||||
import Import from '@/models/Import';
|
import Import from '@/models/Import';
|
||||||
|
|
||||||
export interface ImportMappingAttr {
|
export interface ImportMappingAttr {
|
||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
|
group?: string;
|
||||||
dateFormat?: string;
|
dateFormat?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export interface ImportValidationError {
|
|||||||
constraints: Record<string, string>;
|
constraints: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResourceMetaFieldsMap = { [key: string]: IModelMetaField };
|
export type ResourceMetaFieldsMap = { [key: string]: IModelMetaField2 };
|
||||||
|
|
||||||
export interface ImportInsertError {
|
export interface ImportInsertError {
|
||||||
rowNumber: number;
|
rowNumber: number;
|
||||||
@@ -43,6 +44,7 @@ export interface ImportFileMapPOJO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ImportFilePreviewPOJO {
|
export interface ImportFilePreviewPOJO {
|
||||||
|
resource: string;
|
||||||
createdCount: number;
|
createdCount: number;
|
||||||
skippedCount: number;
|
skippedCount: number;
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
@@ -58,19 +60,18 @@ export interface ImportOperSuccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ImportOperError {
|
export interface ImportOperError {
|
||||||
error: ImportInsertError;
|
error: ImportInsertError[];
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImportableContext {
|
export interface ImportableContext {
|
||||||
import: Import,
|
import: Import;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const ImportDateFormats = [
|
export const ImportDateFormats = [
|
||||||
'yyyy-MM-dd',
|
'yyyy-MM-dd',
|
||||||
'dd.MM.yy',
|
'dd.MM.yy',
|
||||||
'MM/dd/yy',
|
'MM/dd/yy',
|
||||||
'dd/MMM/yyyy'
|
'dd/MMM/yyyy',
|
||||||
]
|
];
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import Container, { Service } from 'typedi';
|
||||||
|
import { ImportDeleteExpiredFiles } from '../ImportRemoveExpiredFiles';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ImportDeleteExpiredFilesJobs {
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
constructor(agenda) {
|
||||||
|
agenda.define('delete-expired-imported-files', this.handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers sending invoice mail.
|
||||||
|
*/
|
||||||
|
private handler = async (job, done: Function) => {
|
||||||
|
const importDeleteExpiredFiles = Container.get(ImportDeleteExpiredFiles);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Delete expired import files has started.');
|
||||||
|
await importDeleteExpiredFiles.deleteExpiredFiles();
|
||||||
|
done();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
done(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import ItemCategoriesService from './ItemCategoriesService';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IItemCategoryOTD } from '@/interfaces';
|
||||||
|
import { ItemCategoriesSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ItemCategoriesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private itemCategoriesService: ItemCategoriesService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to create new item category service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {any} createDTO
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public async importable(
|
||||||
|
tenantId: number,
|
||||||
|
createDTO: IItemCategoryOTD,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
await this.itemCategoriesService.newItemCategory(
|
||||||
|
tenantId,
|
||||||
|
createDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item categories sample data used to download sample sheet file.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return ItemCategoriesSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject } from 'typedi';
|
import { Inject } from 'typedi';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import Knex from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import {
|
import {
|
||||||
IItemCategory,
|
IItemCategory,
|
||||||
@@ -102,7 +102,10 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (foundItemCategory) {
|
if (foundItemCategory) {
|
||||||
throw new ServiceError(ERRORS.CATEGORY_NAME_EXISTS);
|
throw new ServiceError(
|
||||||
|
ERRORS.CATEGORY_NAME_EXISTS,
|
||||||
|
'The item category name is already exist.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +118,8 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
|||||||
public async newItemCategory(
|
public async newItemCategory(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
itemCategoryOTD: IItemCategoryOTD,
|
itemCategoryOTD: IItemCategoryOTD,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<IItemCategory> {
|
): Promise<IItemCategory> {
|
||||||
const { ItemCategory } = this.tenancy.models(tenantId);
|
const { ItemCategory } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -139,20 +143,24 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
|||||||
authorizedUser
|
authorizedUser
|
||||||
);
|
);
|
||||||
// Creates item category under unit-of-work evnirement.
|
// Creates item category under unit-of-work evnirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Inserts the item category.
|
tenantId,
|
||||||
const itemCategory = await ItemCategory.query(trx).insert({
|
async (trx: Knex.Transaction) => {
|
||||||
...itemCategoryObj,
|
// Inserts the item category.
|
||||||
});
|
const itemCategory = await ItemCategory.query(trx).insert({
|
||||||
// Triggers `onItemCategoryCreated` event.
|
...itemCategoryObj,
|
||||||
await this.eventPublisher.emitAsync(events.itemCategory.onCreated, {
|
});
|
||||||
itemCategory,
|
// Triggers `onItemCategoryCreated` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.itemCategory.onCreated, {
|
||||||
trx,
|
itemCategory,
|
||||||
} as IItemCategoryCreatedPayload);
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as IItemCategoryCreatedPayload);
|
||||||
|
|
||||||
return itemCategory;
|
return itemCategory;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,3 +11,25 @@ export const ERRORS = {
|
|||||||
INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
||||||
CATEGORY_HAVE_ITEMS: 'CATEGORY_HAVE_ITEMS',
|
CATEGORY_HAVE_ITEMS: 'CATEGORY_HAVE_ITEMS',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ItemCategoriesSampleData = [
|
||||||
|
{
|
||||||
|
Name: 'Kassulke Group',
|
||||||
|
Description: 'Optio itaque eaque qui adipisci illo sed.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: 'Crist, Mraz and Lueilwitz',
|
||||||
|
Description:
|
||||||
|
'Dolores veniam deserunt sed commodi error quia veritatis non.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: 'Gutmann and Sons',
|
||||||
|
Description:
|
||||||
|
'Ratione aperiam voluptas rem adipisci assumenda eos neque veritatis tempora.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: 'Reichel - Raynor',
|
||||||
|
Description:
|
||||||
|
'Necessitatibus repellendus placeat possimus dolores excepturi ut.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -88,7 +88,11 @@ export class CreateItem {
|
|||||||
* @param {IItemDTO} item
|
* @param {IItemDTO} item
|
||||||
* @return {Promise<IItem>}
|
* @return {Promise<IItem>}
|
||||||
*/
|
*/
|
||||||
public async createItem(tenantId: number, itemDTO: IItemDTO): Promise<IItem> {
|
public async createItem(
|
||||||
|
tenantId: number,
|
||||||
|
itemDTO: IItemDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<IItem> {
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
const { Item } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Authorize the item before creating.
|
// Authorize the item before creating.
|
||||||
@@ -111,7 +115,8 @@ export class CreateItem {
|
|||||||
} as IItemEventCreatedPayload);
|
} as IItemEventCreatedPayload);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
},
|
||||||
|
trx
|
||||||
);
|
);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ export class ItemsValidators {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (foundItems.length > 0) {
|
if (foundItems.length > 0) {
|
||||||
throw new ServiceError(ERRORS.ITEM_NAME_EXISTS);
|
throw new ServiceError(
|
||||||
|
ERRORS.ITEM_NAME_EXISTS,
|
||||||
|
'The item name is already exist.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
packages/server/src/services/Items/ItemsImportable.ts
Normal file
34
packages/server/src/services/Items/ItemsImportable.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { IItemCreateDTO } from '@/interfaces';
|
||||||
|
import { CreateItem } from './CreateItem';
|
||||||
|
import { ItemsSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ItemsImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createItemService: CreateItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapps the imported data to create a new item service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ICustomerNewDTO} createDTO
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async importable(
|
||||||
|
tenantId: number,
|
||||||
|
createDTO: IItemCreateDTO,
|
||||||
|
trx?: Knex.Transaction<any, any[]>
|
||||||
|
): Promise<void> {
|
||||||
|
await this.createItemService.createItem(tenantId, createDTO, trx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data of customers used to download sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return ItemsSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
NOT_FOUND: 'NOT_FOUND',
|
NOT_FOUND: 'NOT_FOUND',
|
||||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||||
@@ -19,7 +18,8 @@ export const ERRORS = {
|
|||||||
ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT:
|
ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT:
|
||||||
'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||||
ITEM_CANNOT_CHANGE_INVENTORY_TYPE: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
|
ITEM_CANNOT_CHANGE_INVENTORY_TYPE: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
|
||||||
TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS:
|
||||||
|
'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||||
INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||||
|
|
||||||
ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
|
ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
|
||||||
@@ -53,8 +53,84 @@ export const DEFAULT_VIEWS = [
|
|||||||
slug: 'non-inventory',
|
slug: 'non-inventory',
|
||||||
rolesLogicExpression: '1',
|
rolesLogicExpression: '1',
|
||||||
roles: [
|
roles: [
|
||||||
{ index: 1, fieldKey: 'type', comparator: 'equals', value: 'non-inventory' },
|
{
|
||||||
|
index: 1,
|
||||||
|
fieldKey: 'type',
|
||||||
|
comparator: 'equals',
|
||||||
|
value: 'non-inventory',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
columns: DEFAULT_VIEW_COLUMNS,
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
export const ItemsSampleData = [
|
||||||
|
{
|
||||||
|
'Item Type': 'Inventory',
|
||||||
|
'Item Name': 'Hettinger, Schumm and Bartoletti',
|
||||||
|
'Item Code': '1000',
|
||||||
|
Sellable: 'T',
|
||||||
|
Purchasable: 'T',
|
||||||
|
'Cost Price': '10000',
|
||||||
|
'Sell Price': '1000',
|
||||||
|
'Cost Account': 'Cost of Goods Sold',
|
||||||
|
'Sell Account': 'Other Income',
|
||||||
|
'Inventory Account': 'Inventory Asset',
|
||||||
|
'Sell Description': 'Description ....',
|
||||||
|
'Purchase Description': 'Description ....',
|
||||||
|
Category: 'sdafasdfsadf',
|
||||||
|
Note: 'At dolor est non tempore et quisquam.',
|
||||||
|
Active: 'TRUE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Item Type': 'Inventory',
|
||||||
|
'Item Name': 'Schmitt Group',
|
||||||
|
'Item Code': '1001',
|
||||||
|
Sellable: 'T',
|
||||||
|
Purchasable: 'T',
|
||||||
|
'Cost Price': '10000',
|
||||||
|
'Sell Price': '1000',
|
||||||
|
'Cost Account': 'Cost of Goods Sold',
|
||||||
|
'Sell Account': 'Other Income',
|
||||||
|
'Inventory Account': 'Inventory Asset',
|
||||||
|
'Sell Description': 'Description ....',
|
||||||
|
'Purchase Description': 'Description ....',
|
||||||
|
Category: 'sdafasdfsadf',
|
||||||
|
Note: 'Id perspiciatis at adipisci minus accusamus dolor iure dolore.',
|
||||||
|
Active: 'TRUE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Item Type': 'Inventory',
|
||||||
|
'Item Name': 'Marks - Carroll',
|
||||||
|
'Item Code': '1002',
|
||||||
|
Sellable: 'T',
|
||||||
|
Purchasable: 'T',
|
||||||
|
'Cost Price': '10000',
|
||||||
|
'Sell Price': '1000',
|
||||||
|
'Cost Account': 'Cost of Goods Sold',
|
||||||
|
'Sell Account': 'Other Income',
|
||||||
|
'Inventory Account': 'Inventory Asset',
|
||||||
|
'Sell Description': 'Description ....',
|
||||||
|
'Purchase Description': 'Description ....',
|
||||||
|
Category: 'sdafasdfsadf',
|
||||||
|
Note: 'Odio odio minus similique.',
|
||||||
|
Active: 'TRUE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Item Type': 'Inventory',
|
||||||
|
'Item Name': 'VonRueden, Ruecker and Hettinger',
|
||||||
|
'Item Code': '1003',
|
||||||
|
Sellable: 'T',
|
||||||
|
Purchasable: 'T',
|
||||||
|
'Cost Price': '10000',
|
||||||
|
'Sell Price': '1000',
|
||||||
|
'Cost Account': 'Cost of Goods Sold',
|
||||||
|
'Sell Account': 'Other Income',
|
||||||
|
'Inventory Account': 'Inventory Asset',
|
||||||
|
'Sell Description': 'Description ....',
|
||||||
|
'Purchase Description': 'Description ....',
|
||||||
|
Category: 'sdafasdfsadf',
|
||||||
|
Note: 'Quibusdam dolores illo.',
|
||||||
|
Active: 'TRUE',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -82,7 +82,10 @@ export class CommandManualJournalValidators {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (journals.length > 0) {
|
if (journals.length > 0) {
|
||||||
throw new ServiceError(ERRORS.JOURNAL_NUMBER_EXISTS);
|
throw new ServiceError(
|
||||||
|
ERRORS.JOURNAL_NUMBER_EXISTS,
|
||||||
|
'The journal number is already exist.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,7 @@ export class CreateManualJournalService {
|
|||||||
return R.compose(
|
return R.compose(
|
||||||
// Omits the `branchId` from entries if multiply branches feature not active.
|
// Omits the `branchId` from entries if multiply branches feature not active.
|
||||||
this.branchesDTOTransformer.transformDTO(tenantId)
|
this.branchesDTOTransformer.transformDTO(tenantId)
|
||||||
)(
|
)(initialDTO);
|
||||||
initialDTO
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,7 +131,8 @@ export class CreateManualJournalService {
|
|||||||
public makeJournalEntries = async (
|
public makeJournalEntries = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
manualJournalDTO: IManualJournalDTO,
|
manualJournalDTO: IManualJournalDTO,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<{ manualJournal: IManualJournal }> => {
|
): Promise<{ manualJournal: IManualJournal }> => {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -156,27 +155,31 @@ export class CreateManualJournalService {
|
|||||||
);
|
);
|
||||||
// Creates a manual journal transactions with associated transactions
|
// Creates a manual journal transactions with associated transactions
|
||||||
// under unit-of-work envirement.
|
// under unit-of-work envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onManualJournalCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.manualJournals.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onManualJournalCreating` event.
|
||||||
manualJournalDTO,
|
await this.eventPublisher.emitAsync(events.manualJournals.onCreating, {
|
||||||
trx,
|
tenantId,
|
||||||
} as IManualJournalCreatingPayload);
|
manualJournalDTO,
|
||||||
|
trx,
|
||||||
|
} as IManualJournalCreatingPayload);
|
||||||
|
|
||||||
// Upsert the manual journal object.
|
// Upsert the manual journal object.
|
||||||
const manualJournal = await ManualJournal.query(trx).upsertGraph({
|
const manualJournal = await ManualJournal.query(trx).upsertGraph({
|
||||||
...manualJournalObj,
|
...manualJournalObj,
|
||||||
});
|
});
|
||||||
// Triggers `onManualJournalCreated` event.
|
// Triggers `onManualJournalCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.manualJournals.onCreated, {
|
await this.eventPublisher.emitAsync(events.manualJournals.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
manualJournal,
|
manualJournal,
|
||||||
manualJournalId: manualJournal.id,
|
manualJournalId: manualJournal.id,
|
||||||
trx,
|
trx,
|
||||||
} as IManualJournalEventCreatedPayload);
|
} as IManualJournalEventCreatedPayload);
|
||||||
|
|
||||||
return { manualJournal };
|
return { manualJournal };
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Inject } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import { CreateManualJournalService } from './CreateManualJournal';
|
||||||
|
import { IManualJournalDTO } from '@/interfaces';
|
||||||
|
import { ImportableContext } from '../Import/interfaces';
|
||||||
|
import { ManualJournalsSampleData } from './constants';
|
||||||
|
|
||||||
|
export class ManualJournalImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createManualJournalService: CreateManualJournalService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createJournalDTO: IManualJournalDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createManualJournalService.makeJournalEntries(
|
||||||
|
tenantId,
|
||||||
|
createJournalDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the DTO before passing it to importable and validation.
|
||||||
|
* @param {Record<string, any>} createDTO
|
||||||
|
* @param {ImportableContext} context
|
||||||
|
* @returns {Record<string, any>}
|
||||||
|
*/
|
||||||
|
public transform(createDTO: Record<string, any>, context: ImportableContext) {
|
||||||
|
return createDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params validation schema.
|
||||||
|
* @returns {ValidationSchema[]}
|
||||||
|
*/
|
||||||
|
public paramsValidationSchema() {
|
||||||
|
return Yup.object().shape({
|
||||||
|
autoIncrement: Yup.boolean(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data of manual journals that used to download sample sheet.
|
||||||
|
* @returns {Record<string, any>}
|
||||||
|
*/
|
||||||
|
public sampleData(): Record<string, any>[] {
|
||||||
|
return ManualJournalsSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,3 +29,36 @@ export const CONTACTS_CONFIG = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_VIEWS = [];
|
export const DEFAULT_VIEWS = [];
|
||||||
|
|
||||||
|
export const ManualJournalsSampleData = [
|
||||||
|
{
|
||||||
|
Date: '2024-02-02',
|
||||||
|
'Journal No': 'J-100022',
|
||||||
|
'Reference No.': 'REF-10000',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Journal Type': '',
|
||||||
|
Description: 'Animi quasi qui itaque aut possimus illum est magnam enim.',
|
||||||
|
Credit: 1000,
|
||||||
|
Debit: 0,
|
||||||
|
Note: 'Qui reprehenderit voluptate.',
|
||||||
|
Account: 'Bank Account',
|
||||||
|
Contact: '',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Date: '2024-02-02',
|
||||||
|
'Journal No': 'J-100022',
|
||||||
|
'Reference No.': 'REF-10000',
|
||||||
|
'Currency Code': '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
'Journal Type': '',
|
||||||
|
Description: 'In assumenda dicta autem non est corrupti non et.',
|
||||||
|
Credit: 0,
|
||||||
|
Debit: 1000,
|
||||||
|
Note: 'Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.',
|
||||||
|
Account: 'Bank Account',
|
||||||
|
Contact: '',
|
||||||
|
Publish: 'T',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IBillPaymentDTO } from '@/interfaces';
|
||||||
|
import { CreateBillPayment } from './CreateBillPayment';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { BillsPaymentsSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillPaymentsImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createBillPaymentService: CreateBillPayment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentDTO: IBillPaymentDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createBillPaymentService.createBillPayment(
|
||||||
|
tenantId,
|
||||||
|
billPaymentDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return BillsPaymentsSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,8 @@ export class CreateBillPayment {
|
|||||||
*/
|
*/
|
||||||
public async createBillPayment(
|
public async createBillPayment(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
billPaymentDTO: IBillPaymentDTO
|
billPaymentDTO: IBillPaymentDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<IBillPayment> {
|
): Promise<IBillPayment> {
|
||||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -97,28 +98,32 @@ export class CreateBillPayment {
|
|||||||
);
|
);
|
||||||
// Writes bill payment transacation with associated transactions
|
// Writes bill payment transacation with associated transactions
|
||||||
// under unit-of-work envirement.
|
// under unit-of-work envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onBillPaymentCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onBillPaymentCreating` event.
|
||||||
billPaymentDTO,
|
await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
|
||||||
trx,
|
tenantId,
|
||||||
} as IBillPaymentCreatingPayload);
|
billPaymentDTO,
|
||||||
|
trx,
|
||||||
|
} as IBillPaymentCreatingPayload);
|
||||||
|
|
||||||
// Writes the bill payment graph to the storage.
|
// Writes the bill payment graph to the storage.
|
||||||
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
|
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
|
||||||
...billPaymentObj,
|
...billPaymentObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Triggers `onBillPaymentCreated` event.
|
// Triggers `onBillPaymentCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
billPayment,
|
billPayment,
|
||||||
billPaymentId: billPayment.id,
|
billPaymentId: billPayment.id,
|
||||||
trx,
|
trx,
|
||||||
} as IBillPaymentEventCreatedPayload);
|
} as IBillPaymentEventCreatedPayload);
|
||||||
|
|
||||||
return billPayment;
|
return billPayment;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,3 +15,36 @@ export const ERRORS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_VIEWS = [];
|
export const DEFAULT_VIEWS = [];
|
||||||
|
|
||||||
|
export const BillsPaymentsSampleData = [
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-01',
|
||||||
|
Vendor: 'Gabriel Kovacek',
|
||||||
|
'Payment No.': 'P-10001',
|
||||||
|
'Reference No.': 'REF-1',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Statement: 'Vel et dolorem architecto veniam.',
|
||||||
|
'Bill No': 'B-120',
|
||||||
|
'Payment Amount': 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-02',
|
||||||
|
Vendor: 'Gabriel Kovacek',
|
||||||
|
'Payment No.': 'P-10002',
|
||||||
|
'Reference No.': 'REF-2',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Statement: 'Id est molestias.',
|
||||||
|
'Bill No': 'B-121',
|
||||||
|
'Payment Amount': 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Payment Date': '2024-03-03',
|
||||||
|
Vendor: 'Gabriel Kovacek',
|
||||||
|
'Payment No.': 'P-10003',
|
||||||
|
'Reference No.': 'REF-3',
|
||||||
|
'Payment Account': 'Petty Cash',
|
||||||
|
Statement: 'Quam cupiditate at nihil dicta dignissimos non fugit illo.',
|
||||||
|
'Bill No': 'B-122',
|
||||||
|
'Payment Amount': 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { CreateBill } from './CreateBill';
|
||||||
|
import { IBillDTO } from '@/interfaces';
|
||||||
|
import { BillsSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillsImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createBillService: CreateBill;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: IBillDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createBillService.createBill(
|
||||||
|
tenantId,
|
||||||
|
createAccountDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return BillsSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ export class BillsValidators {
|
|||||||
*/
|
*/
|
||||||
public validateBillAmountBiggerPaidAmount(
|
public validateBillAmountBiggerPaidAmount(
|
||||||
billAmount: number,
|
billAmount: number,
|
||||||
paidAmount: number,
|
paidAmount: number
|
||||||
) {
|
) {
|
||||||
if (billAmount < paidAmount) {
|
if (billAmount < paidAmount) {
|
||||||
throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT);
|
throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT);
|
||||||
@@ -53,7 +53,10 @@ export class BillsValidators {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (foundBills.length > 0) {
|
if (foundBills.length > 0) {
|
||||||
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
|
throw new ServiceError(
|
||||||
|
ERRORS.BILL_NUMBER_EXISTS,
|
||||||
|
'The bill number is not unique.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ export class CreateBill {
|
|||||||
public async createBill(
|
public async createBill(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
billDTO: IBillDTO,
|
billDTO: IBillDTO,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<IBill> {
|
): Promise<IBill> {
|
||||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -91,26 +92,30 @@ export class CreateBill {
|
|||||||
authorizedUser
|
authorizedUser
|
||||||
);
|
);
|
||||||
// Write new bill transaction with associated transactions under UOW env.
|
// Write new bill transaction with associated transactions under UOW env.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onBillCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.bill.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
trx,
|
// Triggers `onBillCreating` event.
|
||||||
billDTO,
|
await this.eventPublisher.emitAsync(events.bill.onCreating, {
|
||||||
tenantId,
|
trx,
|
||||||
} as IBillCreatingPayload);
|
billDTO,
|
||||||
|
tenantId,
|
||||||
|
} as IBillCreatingPayload);
|
||||||
|
|
||||||
// Inserts the bill graph object to the storage.
|
// Inserts the bill graph object to the storage.
|
||||||
const bill = await Bill.query(trx).upsertGraph(billObj);
|
const bill = await Bill.query(trx).upsertGraph(billObj);
|
||||||
|
|
||||||
// Triggers `onBillCreated` event.
|
// Triggers `onBillCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.bill.onCreated, {
|
await this.eventPublisher.emitAsync(events.bill.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
bill,
|
bill,
|
||||||
billId: bill.id,
|
billId: bill.id,
|
||||||
trx,
|
trx,
|
||||||
} as IBillCreatedPayload);
|
} as IBillCreatedPayload);
|
||||||
|
|
||||||
return bill;
|
return bill;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,3 +75,49 @@ export const DEFAULT_VIEWS = [
|
|||||||
columns: DEFAULT_VIEW_COLUMNS,
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const BillsSampleData = [
|
||||||
|
{
|
||||||
|
'Bill No.': 'B-101',
|
||||||
|
'Reference No.': 'REF0',
|
||||||
|
Date: '2024-01-01',
|
||||||
|
'Due Date': '2024-03-01',
|
||||||
|
Vendor: 'Gabriel Kovacek',
|
||||||
|
'Exchange Rate': 1,
|
||||||
|
Note: 'Vel in sit sint.',
|
||||||
|
Open: 'T',
|
||||||
|
Item: 'VonRueden, Ruecker and Hettinger',
|
||||||
|
Quantity: 100,
|
||||||
|
Rate: 100,
|
||||||
|
'Line Description': 'Id a vel quis vel aut.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Bill No.': 'B-102',
|
||||||
|
'Reference No.': 'REF0',
|
||||||
|
Date: '2024-01-01',
|
||||||
|
'Due Date': '2024-03-01',
|
||||||
|
Vendor: 'Gabriel Kovacek',
|
||||||
|
'Exchange Rate': 1,
|
||||||
|
Note: 'Quia ut dolorem qui sint velit.',
|
||||||
|
Open: 'T',
|
||||||
|
Item: 'Thompson - Reichert',
|
||||||
|
Quantity: 200,
|
||||||
|
Rate: 50,
|
||||||
|
'Line Description':
|
||||||
|
'Nesciunt in adipisci quia ab reiciendis nam sed saepe consequatur.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Bill No.': 'B-103',
|
||||||
|
'Reference No.': 'REF0',
|
||||||
|
Date: '2024-01-01',
|
||||||
|
'Due Date': '2024-03-01',
|
||||||
|
Vendor: 'Gabriel Kovacek',
|
||||||
|
'Exchange Rate': 1,
|
||||||
|
Note: 'Dolore aut voluptatem minus pariatur alias pariatur.',
|
||||||
|
Open: 'T',
|
||||||
|
Item: 'VonRueden, Ruecker and Hettinger',
|
||||||
|
Quantity: 100,
|
||||||
|
Rate: 100,
|
||||||
|
'Line Description': 'Quam eligendi provident.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export default class CreateVendorCredit extends BaseVendorCredit {
|
|||||||
*/
|
*/
|
||||||
public newVendorCredit = async (
|
public newVendorCredit = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
vendorCreditCreateDTO: IVendorCreditCreateDTO
|
vendorCreditCreateDTO: IVendorCreditCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
) => {
|
) => {
|
||||||
const { VendorCredit, Vendor } = this.tenancy.models(tenantId);
|
const { VendorCredit, Vendor } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -59,27 +60,31 @@ export default class CreateVendorCredit extends BaseVendorCredit {
|
|||||||
vendor.currencyCode
|
vendor.currencyCode
|
||||||
);
|
);
|
||||||
// Saves the vendor credit transactions under UOW envirement.
|
// Saves the vendor credit transactions under UOW envirement.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onVendorCreditCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.vendorCredit.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onVendorCreditCreating` event.
|
||||||
vendorCreditCreateDTO,
|
await this.eventPublisher.emitAsync(events.vendorCredit.onCreating, {
|
||||||
trx,
|
tenantId,
|
||||||
} as IVendorCreditCreatingPayload);
|
vendorCreditCreateDTO,
|
||||||
|
trx,
|
||||||
|
} as IVendorCreditCreatingPayload);
|
||||||
|
|
||||||
// Saves the vendor credit graph.
|
// Saves the vendor credit graph.
|
||||||
const vendorCredit = await VendorCredit.query(trx).upsertGraphAndFetch({
|
const vendorCredit = await VendorCredit.query(trx).upsertGraphAndFetch({
|
||||||
...vendorCreditModel,
|
...vendorCreditModel,
|
||||||
});
|
});
|
||||||
// Triggers `onVendorCreditCreated` event.
|
// Triggers `onVendorCreditCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.vendorCredit.onCreated, {
|
await this.eventPublisher.emitAsync(events.vendorCredit.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
vendorCredit,
|
vendorCredit,
|
||||||
vendorCreditCreateDTO,
|
vendorCreditCreateDTO,
|
||||||
trx,
|
trx,
|
||||||
} as IVendorCreditCreatedPayload);
|
} as IVendorCreditCreatedPayload);
|
||||||
|
|
||||||
return vendorCredit;
|
return vendorCredit;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import CreateVendorCredit from './CreateVendorCredit';
|
||||||
|
import { IVendorCreditCreateDTO } from '@/interfaces';
|
||||||
|
import { VendorCreditsSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class VendorCreditsImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createVendorCreditService: CreateVendorCredit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createPaymentDTO: IVendorCreditCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createVendorCreditService.newVendorCredit(
|
||||||
|
tenantId,
|
||||||
|
createPaymentDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return VendorCreditsSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
VENDOR_CREDIT_NOT_FOUND: 'VENDOR_CREDIT_NOT_FOUND',
|
VENDOR_CREDIT_NOT_FOUND: 'VENDOR_CREDIT_NOT_FOUND',
|
||||||
VENDOR_CREDIT_ALREADY_OPENED: 'VENDOR_CREDIT_ALREADY_OPENED',
|
VENDOR_CREDIT_ALREADY_OPENED: 'VENDOR_CREDIT_ALREADY_OPENED',
|
||||||
VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT: 'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT',
|
VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT:
|
||||||
VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND: 'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND',
|
'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT',
|
||||||
|
VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND:
|
||||||
|
'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND',
|
||||||
BILLS_HAS_NO_REMAINING_AMOUNT: 'BILLS_HAS_NO_REMAINING_AMOUNT',
|
BILLS_HAS_NO_REMAINING_AMOUNT: 'BILLS_HAS_NO_REMAINING_AMOUNT',
|
||||||
VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS: 'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS',
|
VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS:
|
||||||
VENDOR_CREDIT_HAS_APPLIED_BILLS: 'VENDOR_CREDIT_HAS_APPLIED_BILLS'
|
'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS',
|
||||||
|
VENDOR_CREDIT_HAS_APPLIED_BILLS: 'VENDOR_CREDIT_HAS_APPLIED_BILLS',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_VIEW_COLUMNS = [];
|
export const DEFAULT_VIEW_COLUMNS = [];
|
||||||
@@ -62,3 +65,18 @@ export const DEFAULT_VIEWS = [
|
|||||||
columns: DEFAULT_VIEW_COLUMNS,
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const VendorCreditsSampleData = [
|
||||||
|
{
|
||||||
|
Vendor: 'Randall Kohler VENDOR',
|
||||||
|
'Vendor Credit Date': '2024-01-01',
|
||||||
|
'Vendor Credit No.': 'VC-0001',
|
||||||
|
'Reference No.': 'REF-00001',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
Note: 'Note',
|
||||||
|
Open: 'T',
|
||||||
|
'Item Name': 'Hettinger, Schumm and Bartoletti',
|
||||||
|
Quantity: 100,
|
||||||
|
Rate: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Service, Inject } from 'typedi';
|
|||||||
import { camelCase, upperFirst, pickBy } from 'lodash';
|
import { camelCase, upperFirst, pickBy } from 'lodash';
|
||||||
import * as qim from 'qim';
|
import * as qim from 'qim';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { IModelMeta, IModelMetaField } from '@/interfaces';
|
import { IModelMeta, IModelMetaField, IModelMetaField2 } from '@/interfaces';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import I18nService from '@/services/I18n/I18nService';
|
import I18nService from '@/services/I18n/I18nService';
|
||||||
@@ -74,6 +74,15 @@ export default class ResourceService {
|
|||||||
return meta.fields;
|
return meta.fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getResourceFields2(
|
||||||
|
tenantId: number,
|
||||||
|
modelName: string
|
||||||
|
): { [key: string]: IModelMetaField2 } {
|
||||||
|
const meta = this.getResourceMeta(tenantId, modelName);
|
||||||
|
|
||||||
|
return meta.fields2;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -96,9 +105,14 @@ export default class ResourceService {
|
|||||||
const $enumerationType = (field) =>
|
const $enumerationType = (field) =>
|
||||||
field.fieldType === 'enumeration' ? field : undefined;
|
field.fieldType === 'enumeration' ? field : undefined;
|
||||||
|
|
||||||
|
const $hasFields = (field) => 'undefined' !== typeof field.fields ? field : undefined;
|
||||||
|
|
||||||
const naviagations = [
|
const naviagations = [
|
||||||
['fields', qim.$each, 'name'],
|
['fields', qim.$each, 'name'],
|
||||||
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
|
['fields2', qim.$each, 'name'],
|
||||||
|
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
|
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||||
];
|
];
|
||||||
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export class CreateSaleEstimate {
|
|||||||
*/
|
*/
|
||||||
public async createEstimate(
|
public async createEstimate(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
estimateDTO: ISaleEstimateDTO
|
estimateDTO: ISaleEstimateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<ISaleEstimate> {
|
): Promise<ISaleEstimate> {
|
||||||
const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
|
const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -75,28 +76,32 @@ export class CreateSaleEstimate {
|
|||||||
estimateDTO.entries
|
estimateDTO.entries
|
||||||
);
|
);
|
||||||
// Creates a sale estimate transaction with associated transactions as UOW.
|
// Creates a sale estimate transaction with associated transactions as UOW.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onSaleEstimateCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
estimateDTO,
|
// Triggers `onSaleEstimateCreating` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, {
|
||||||
trx,
|
estimateDTO,
|
||||||
} as ISaleEstimateCreatingPayload);
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateCreatingPayload);
|
||||||
|
|
||||||
// Upsert the sale estimate graph to the storage.
|
// Upsert the sale estimate graph to the storage.
|
||||||
const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({
|
const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({
|
||||||
...estimateObj,
|
...estimateObj,
|
||||||
});
|
});
|
||||||
// Triggers `onSaleEstimateCreated` event.
|
// Triggers `onSaleEstimateCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.saleEstimate.onCreated, {
|
await this.eventPublisher.emitAsync(events.saleEstimate.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimate,
|
saleEstimate,
|
||||||
saleEstimateId: saleEstimate.id,
|
saleEstimateId: saleEstimate.id,
|
||||||
saleEstimateDTO: estimateDTO,
|
saleEstimateDTO: estimateDTO,
|
||||||
trx,
|
trx,
|
||||||
} as ISaleEstimateCreatedPayload);
|
} as ISaleEstimateCreatedPayload);
|
||||||
|
|
||||||
return saleEstimate;
|
return saleEstimate;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ export class SaleEstimateValidators {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (foundSaleEstimate) {
|
if (foundSaleEstimate) {
|
||||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_NUMBER_EXISTANCE);
|
throw new ServiceError(
|
||||||
|
ERRORS.SALE_ESTIMATE_NUMBER_EXISTANCE,
|
||||||
|
'The given sale estimate is not unique.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { ISaleEstimateDTO } from '@/interfaces';
|
||||||
|
import { CreateSaleEstimate } from './CreateSaleEstimate';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { SaleEstimatesSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class SaleEstimatesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createEstimateService: CreateSaleEstimate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createEstimateDTO: ISaleEstimateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createEstimateService.createEstimate(
|
||||||
|
tenantId,
|
||||||
|
createEstimateDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return SaleEstimatesSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,3 +122,54 @@ export const DEFAULT_VIEWS = [
|
|||||||
columns: DEFAULT_VIEW_COLUMNS,
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SaleEstimatesSampleData = [
|
||||||
|
{
|
||||||
|
Customer: 'Ambrose Olson',
|
||||||
|
'Estimate Date': '2024-01-01',
|
||||||
|
'Expiration Date': '2025-01-01',
|
||||||
|
'Estimate No.': 'EST-0001',
|
||||||
|
'Reference No.': 'REF-0001',
|
||||||
|
Currency: '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
Note: 'Vel autem quis aut ab.',
|
||||||
|
'Terms & Conditions': 'Provident illo architecto sit iste in.',
|
||||||
|
Delivered: 'T',
|
||||||
|
'Item Name': 'Hettinger, Schumm and Bartoletti',
|
||||||
|
Quantity: 1000,
|
||||||
|
Rate: 20,
|
||||||
|
'Line Description': 'Rem esse doloremque praesentium harum maiores.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Customer: 'Ambrose Olson',
|
||||||
|
'Estimate Date': '2024-01-02',
|
||||||
|
'Expiration Date': '2025-01-02',
|
||||||
|
'Estimate No.': 'EST-0002',
|
||||||
|
'Reference No.': 'REF-0002',
|
||||||
|
Currency: '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
Note: 'Tempora voluptas odio deleniti rerum vitae consequatur nihil quis sunt.',
|
||||||
|
'Terms & Conditions': 'Ut eum incidunt quibusdam rerum vero.',
|
||||||
|
Delivered: 'T',
|
||||||
|
'Item Name': 'Hettinger, Schumm and Bartoletti',
|
||||||
|
Quantity: 1000,
|
||||||
|
Rate: 20,
|
||||||
|
'Line Description': 'Qui voluptate aliquam maxime aliquam.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Customer: 'Ambrose Olson',
|
||||||
|
'Estimate Date': '2024-01-03',
|
||||||
|
'Expiration Date': '2025-01-03',
|
||||||
|
'Estimate No.': 'EST-0003',
|
||||||
|
'Reference No.': 'REF-0003',
|
||||||
|
Currency: '',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
Note: 'Quia voluptatem delectus doloremque.',
|
||||||
|
'Terms & Conditions': 'Facilis porro vitae ratione.',
|
||||||
|
Delivered: 'T',
|
||||||
|
'Item Name': 'Hettinger, Schumm and Bartoletti',
|
||||||
|
Quantity: 1000,
|
||||||
|
Rate: 20,
|
||||||
|
'Line Description': 'Qui suscipit ducimus qui qui.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ export class CreateSaleInvoice {
|
|||||||
public createSaleInvoice = async (
|
public createSaleInvoice = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoiceDTO: ISaleInvoiceCreateDTO,
|
saleInvoiceDTO: ISaleInvoiceCreateDTO,
|
||||||
authorizedUser: ITenantUser
|
authorizedUser: ITenantUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<ISaleInvoice> => {
|
): Promise<ISaleInvoice> => {
|
||||||
const { SaleInvoice, SaleEstimate, Contact } =
|
const { SaleInvoice, SaleEstimate, Contact } =
|
||||||
this.tenancy.models(tenantId);
|
this.tenancy.models(tenantId);
|
||||||
@@ -96,33 +97,37 @@ export class CreateSaleInvoice {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Creates a new sale invoice and associated transactions under unit of work env.
|
// Creates a new sale invoice and associated transactions under unit of work env.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onSaleInvoiceCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
saleInvoiceDTO,
|
// Triggers `onSaleInvoiceCreating` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, {
|
||||||
trx,
|
saleInvoiceDTO,
|
||||||
} as ISaleInvoiceCreatingPaylaod);
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as ISaleInvoiceCreatingPaylaod);
|
||||||
|
|
||||||
// Create sale invoice graph to the storage.
|
// Create sale invoice graph to the storage.
|
||||||
const saleInvoice = await SaleInvoice.query(trx).upsertGraph(
|
const saleInvoice = await SaleInvoice.query(trx).upsertGraph(
|
||||||
saleInvoiceObj
|
saleInvoiceObj
|
||||||
);
|
);
|
||||||
const eventPayload: ISaleInvoiceCreatedPayload = {
|
const eventPayload: ISaleInvoiceCreatedPayload = {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoice,
|
saleInvoice,
|
||||||
saleInvoiceDTO,
|
saleInvoiceDTO,
|
||||||
saleInvoiceId: saleInvoice.id,
|
saleInvoiceId: saleInvoice.id,
|
||||||
authorizedUser,
|
authorizedUser,
|
||||||
trx,
|
trx,
|
||||||
};
|
};
|
||||||
// Triggers the event `onSaleInvoiceCreated`.
|
// Triggers the event `onSaleInvoiceCreated`.
|
||||||
await this.eventPublisher.emitAsync(
|
await this.eventPublisher.emitAsync(
|
||||||
events.saleInvoice.onCreated,
|
events.saleInvoice.onCreated,
|
||||||
eventPayload
|
eventPayload
|
||||||
);
|
);
|
||||||
return saleInvoice;
|
return saleInvoice;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { ISaleInvoiceCreateDTO } from '@/interfaces';
|
||||||
|
import { CreateSaleInvoice } from './CreateSaleInvoice';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { SaleInvoicesSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class SaleInvoicesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createInvoiceService: CreateSaleInvoice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: ISaleInvoiceCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createInvoiceService.createSaleInvoice(
|
||||||
|
tenantId,
|
||||||
|
createAccountDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return SaleInvoicesSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,3 +109,52 @@ export const DEFAULT_VIEWS = [
|
|||||||
columns: DEFAULT_VIEW_COLUMNS,
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SaleInvoicesSampleData = [
|
||||||
|
{
|
||||||
|
'Invoice No.': 'B-101',
|
||||||
|
'Reference No.': 'REF0',
|
||||||
|
'Invoice Date': '2024-01-01',
|
||||||
|
'Due Date': '2024-03-01',
|
||||||
|
Customer: 'Harley Veum',
|
||||||
|
'Exchange Rate': 1,
|
||||||
|
'Invoice Message': 'Aspernatur doloremque amet quia aut.',
|
||||||
|
'Terms & Conditions': 'Quia illum aut dolores.',
|
||||||
|
Delivered: 'T',
|
||||||
|
Item: 'VonRueden, Ruecker and Hettinger',
|
||||||
|
Quantity: 100,
|
||||||
|
Rate: 100,
|
||||||
|
Description: 'Description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Invoice No.': 'B-102',
|
||||||
|
'Reference No.': 'REF0',
|
||||||
|
'Invoice Date': '2024-01-01',
|
||||||
|
'Due Date': '2024-03-01',
|
||||||
|
Customer: 'Harley Veum',
|
||||||
|
'Exchange Rate': 1,
|
||||||
|
'Invoice Message': 'Est omnis enim vel.',
|
||||||
|
'Terms & Conditions': 'Iusto et sint nobis sit.',
|
||||||
|
Delivered: 'T',
|
||||||
|
Item: 'Thompson - Reichert',
|
||||||
|
Quantity: 200,
|
||||||
|
Rate: 50,
|
||||||
|
Description: 'Description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Invoice No.': 'B-103',
|
||||||
|
'Reference No.': 'REF0',
|
||||||
|
'Invoice Date': '2024-01-01',
|
||||||
|
'Due Date': '2024-03-01',
|
||||||
|
Customer: 'Harley Veum',
|
||||||
|
'Exchange Rate': 1,
|
||||||
|
'Invoice Message':
|
||||||
|
'Repudiandae voluptatibus repellat minima voluptatem rerum veniam.',
|
||||||
|
'Terms & Conditions': 'Id quod inventore ex rerum velit sed.',
|
||||||
|
Delivered: 'T',
|
||||||
|
Item: 'VonRueden, Ruecker and Hettinger',
|
||||||
|
Quantity: 100,
|
||||||
|
Rate: 100,
|
||||||
|
Description: 'Description',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ export class CreatePaymentReceive {
|
|||||||
public async createPaymentReceive(
|
public async createPaymentReceive(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveDTO: IPaymentReceiveCreateDTO,
|
paymentReceiveDTO: IPaymentReceiveCreateDTO,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser,
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { PaymentReceive, Contact } = this.tenancy.models(tenantId);
|
const { PaymentReceive, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -88,31 +89,35 @@ export class CreatePaymentReceive {
|
|||||||
tenantMeta.baseCurrency
|
tenantMeta.baseCurrency
|
||||||
);
|
);
|
||||||
// Creates a payment receive transaction under UOW envirment.
|
// Creates a payment receive transaction under UOW envirment.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onPaymentReceiveCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.paymentReceive.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
trx,
|
// Triggers `onPaymentReceiveCreating` event.
|
||||||
paymentReceiveDTO,
|
await this.eventPublisher.emitAsync(events.paymentReceive.onCreating, {
|
||||||
tenantId,
|
trx,
|
||||||
} as IPaymentReceiveCreatingPayload);
|
paymentReceiveDTO,
|
||||||
|
tenantId,
|
||||||
|
} as IPaymentReceiveCreatingPayload);
|
||||||
|
|
||||||
// Inserts the payment receive transaction.
|
// Inserts the payment receive transaction.
|
||||||
const paymentReceive = await PaymentReceive.query(
|
const paymentReceive = await PaymentReceive.query(
|
||||||
trx
|
trx
|
||||||
).insertGraphAndFetch({
|
).insertGraphAndFetch({
|
||||||
...paymentReceiveObj,
|
...paymentReceiveObj,
|
||||||
});
|
});
|
||||||
// Triggers `onPaymentReceiveCreated` event.
|
// Triggers `onPaymentReceiveCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.paymentReceive.onCreated, {
|
await this.eventPublisher.emitAsync(events.paymentReceive.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
paymentReceiveId: paymentReceive.id,
|
paymentReceiveId: paymentReceive.id,
|
||||||
authorizedUser,
|
authorizedUser,
|
||||||
trx,
|
trx,
|
||||||
} as IPaymentReceiveCreatedPayload);
|
} as IPaymentReceiveCreatedPayload);
|
||||||
|
|
||||||
return paymentReceive;
|
return paymentReceive;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IPaymentReceiveCreateDTO } from '@/interfaces';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { CreatePaymentReceive } from './CreatePaymentReceive';
|
||||||
|
import { PaymentsReceiveSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class PaymentReceivesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createPaymentReceiveService: CreatePaymentReceive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to account service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createPaymentDTO: IPaymentReceiveCreateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createPaymentReceiveService.createPaymentReceive(
|
||||||
|
tenantId,
|
||||||
|
createPaymentDTO,
|
||||||
|
{},
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return PaymentsReceiveSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,3 +31,17 @@ export const ERRORS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_VIEWS = [];
|
export const DEFAULT_VIEWS = [];
|
||||||
|
|
||||||
|
export const PaymentsReceiveSampleData = [
|
||||||
|
{
|
||||||
|
Customer: 'Randall Kohler',
|
||||||
|
'Payment Date': '2024-10-10',
|
||||||
|
'Payment Receive No.': 'PAY-0001',
|
||||||
|
'Reference No.': 'REF-0001',
|
||||||
|
'Deposit Account': 'Petty Cash',
|
||||||
|
'Exchange Rate': '',
|
||||||
|
Statement: 'Totam optio quisquam qui.',
|
||||||
|
Invoice: 'INV-00001',
|
||||||
|
'Payment Amount': 850,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ISaleReceipt,
|
ISaleReceipt,
|
||||||
ISaleReceiptCreatedPayload,
|
ISaleReceiptCreatedPayload,
|
||||||
ISaleReceiptCreatingPayload,
|
ISaleReceiptCreatingPayload,
|
||||||
|
ISaleReceiptDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
@@ -41,7 +42,8 @@ export class CreateSaleReceipt {
|
|||||||
*/
|
*/
|
||||||
public async createSaleReceipt(
|
public async createSaleReceipt(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleReceiptDTO: any
|
saleReceiptDTO: ISaleReceiptDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
): Promise<ISaleReceipt> {
|
): Promise<ISaleReceipt> {
|
||||||
const { SaleReceipt, Contact } = this.tenancy.models(tenantId);
|
const { SaleReceipt, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -80,27 +82,31 @@ export class CreateSaleReceipt {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Creates a sale receipt transaction and associated transactions under UOW env.
|
// Creates a sale receipt transaction and associated transactions under UOW env.
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onSaleReceiptCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.saleReceipt.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
saleReceiptDTO,
|
// Triggers `onSaleReceiptCreating` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.saleReceipt.onCreating, {
|
||||||
trx,
|
saleReceiptDTO,
|
||||||
} as ISaleReceiptCreatingPayload);
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as ISaleReceiptCreatingPayload);
|
||||||
|
|
||||||
// Inserts the sale receipt graph to the storage.
|
// Inserts the sale receipt graph to the storage.
|
||||||
const saleReceipt = await SaleReceipt.query().upsertGraph({
|
const saleReceipt = await SaleReceipt.query().upsertGraph({
|
||||||
...saleReceiptObj,
|
...saleReceiptObj,
|
||||||
});
|
});
|
||||||
// Triggers `onSaleReceiptCreated` event.
|
// Triggers `onSaleReceiptCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.saleReceipt.onCreated, {
|
await this.eventPublisher.emitAsync(events.saleReceipt.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceipt,
|
saleReceipt,
|
||||||
saleReceiptId: saleReceipt.id,
|
saleReceiptId: saleReceipt.id,
|
||||||
trx,
|
trx,
|
||||||
} as ISaleReceiptCreatedPayload);
|
} as ISaleReceiptCreatedPayload);
|
||||||
|
|
||||||
return saleReceipt;
|
return saleReceipt;
|
||||||
});
|
},
|
||||||
|
trx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IAccountCreateDTO, ISaleReceiptDTO } from '@/interfaces';
|
||||||
|
import { CreateSaleReceipt } from './CreateSaleReceipt';
|
||||||
|
import { Importable } from '@/services/Import/Importable';
|
||||||
|
import { SaleReceiptsSampleData } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class SaleReceiptsImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createReceiptService: CreateSaleReceipt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to sale receipts service.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IAccountCreateDTO} createAccountDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: ISaleReceiptDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createReceiptService.createSaleReceipt(
|
||||||
|
tenantId,
|
||||||
|
createAccountDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return SaleReceiptsSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,3 +46,23 @@ export const DEFAULT_VIEWS = [
|
|||||||
columns: DEFAULT_VIEW_COLUMNS,
|
columns: DEFAULT_VIEW_COLUMNS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export const SaleReceiptsSampleData = [
|
||||||
|
{
|
||||||
|
"Receipt Date": "2023-01-01",
|
||||||
|
"Customer": "Randall Kohler",
|
||||||
|
"Deposit Account": "Petty Cash",
|
||||||
|
"Exchange Rate": "",
|
||||||
|
"Receipt Number": "REC-00001",
|
||||||
|
"Reference No.": "REF-0001",
|
||||||
|
"Statement": "Delectus unde aut soluta et accusamus placeat.",
|
||||||
|
"Receipt Message": "Vitae asperiores dicta.",
|
||||||
|
"Closed": "T",
|
||||||
|
"Item": "Schmitt Group",
|
||||||
|
"Quantity": 100,
|
||||||
|
"Rate": 200,
|
||||||
|
"Line Description": "Distinctio distinctio sit veritatis consequatur iste quod veritatis."
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
@@ -7,6 +7,12 @@ exports.up = function (knex) {
|
|||||||
table.json('columns');
|
table.json('columns');
|
||||||
table.json('mapping');
|
table.json('mapping');
|
||||||
table.json('params');
|
table.json('params');
|
||||||
|
table
|
||||||
|
.bigInteger('tenant_id')
|
||||||
|
.unsigned()
|
||||||
|
.index()
|
||||||
|
.references('id')
|
||||||
|
.inTable('tenants');
|
||||||
table.timestamps();
|
table.timestamps();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import TenantModel from 'models/TenantModel';
|
import { Model, ModelObject } from 'objection';
|
||||||
|
import SystemModel from './SystemModel';
|
||||||
|
|
||||||
export default class Import extends TenantModel {
|
export class Import extends SystemModel {
|
||||||
resource!: string;
|
resource: string;
|
||||||
|
tenantId: number;
|
||||||
mapping!: string;
|
mapping!: string;
|
||||||
columns!: string;
|
columns!: string;
|
||||||
params!: Record<string, any>;
|
params!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
@@ -24,14 +26,7 @@ export default class Import extends TenantModel {
|
|||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
get timestamps() {
|
||||||
return [];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relationship mapping.
|
|
||||||
*/
|
|
||||||
static get relationMappings() {
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +45,6 @@ export default class Import extends TenantModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public get paramsParsed() {
|
public get paramsParsed() {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(this.params);
|
return JSON.parse(this.params);
|
||||||
@@ -66,4 +60,27 @@ export default class Import extends TenantModel {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship mapping.
|
||||||
|
*/
|
||||||
|
static get relationMappings() {
|
||||||
|
const Tenant = require('system/models/Tenant');
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* System user may belongs to tenant model.
|
||||||
|
*/
|
||||||
|
tenant: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: Tenant.default,
|
||||||
|
join: {
|
||||||
|
from: 'imports.tenantId',
|
||||||
|
to: 'tenants.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ImportShape = ModelObject<Import>;
|
||||||
@@ -5,6 +5,11 @@ import BaseModel from 'models/Model';
|
|||||||
import TenantMetadata from './TenantMetadata';
|
import TenantMetadata from './TenantMetadata';
|
||||||
|
|
||||||
export default class Tenant extends BaseModel {
|
export default class Tenant extends BaseModel {
|
||||||
|
upgradeJobId: string;
|
||||||
|
buildJobId: string;
|
||||||
|
initializedAt!: Date | null;
|
||||||
|
seededAt!: Date | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -14,6 +19,7 @@ export default class Tenant extends BaseModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
get timestamps() {
|
||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
@@ -21,6 +27,7 @@ export default class Tenant extends BaseModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return ['isReady', 'isBuildRunning', 'isUpgradeRunning'];
|
return ['isReady', 'isBuildRunning', 'isUpgradeRunning'];
|
||||||
@@ -28,6 +35,7 @@ export default class Tenant extends BaseModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tenant is ready.
|
* Tenant is ready.
|
||||||
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
get isReady() {
|
get isReady() {
|
||||||
return !!(this.initializedAt && this.seededAt);
|
return !!(this.initializedAt && this.seededAt);
|
||||||
@@ -35,6 +43,7 @@ export default class Tenant extends BaseModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Detarimes the tenant whether is build currently running.
|
* Detarimes the tenant whether is build currently running.
|
||||||
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
get isBuildRunning() {
|
get isBuildRunning() {
|
||||||
return !!this.buildJobId;
|
return !!this.buildJobId;
|
||||||
@@ -42,6 +51,7 @@ export default class Tenant extends BaseModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Detarmines the tenant whether is upgrade currently running.
|
* Detarmines the tenant whether is upgrade currently running.
|
||||||
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
get isUpgradeRunning() {
|
get isUpgradeRunning() {
|
||||||
return !!this.upgradeJobId;
|
return !!this.upgradeJobId;
|
||||||
@@ -64,6 +74,7 @@ export default class Tenant extends BaseModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new tenant with random organization id.
|
* Creates a new tenant with random organization id.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import SystemUser from './SystemUser';
|
|||||||
import PasswordReset from './PasswordReset';
|
import PasswordReset from './PasswordReset';
|
||||||
import Invite from './Invite';
|
import Invite from './Invite';
|
||||||
import SystemPlaidItem from './SystemPlaidItem';
|
import SystemPlaidItem from './SystemPlaidItem';
|
||||||
|
import { Import } from './Import';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Tenant,
|
Tenant,
|
||||||
@@ -12,4 +13,5 @@ export {
|
|||||||
PasswordReset,
|
PasswordReset,
|
||||||
Invite,
|
Invite,
|
||||||
SystemPlaidItem,
|
SystemPlaidItem,
|
||||||
|
Import,
|
||||||
};
|
};
|
||||||
|
|||||||
51
packages/server/src/utils/multi-number-parse.test.ts
Normal file
51
packages/server/src/utils/multi-number-parse.test.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { assert } from 'chai';
|
||||||
|
import { multiNumberParse } from './multi-number-parse';
|
||||||
|
|
||||||
|
const correctNumbers = [
|
||||||
|
{ actual: '10.5', expected: 10.5 },
|
||||||
|
{ actual: '10,5', expected: 10.5 },
|
||||||
|
{ actual: '1.235,76', expected: 1235.76 },
|
||||||
|
{ actual: '2,543.56', expected: 2543.56 },
|
||||||
|
{ actual: '10 654.1234', expected: 10654.1234 },
|
||||||
|
{ actual: '2.654$10', expected: 2654.1 },
|
||||||
|
{ actual: '5.435.123,645', expected: 5435123.645 },
|
||||||
|
{ actual: '2,566,765.234', expected: 2566765.234 },
|
||||||
|
{ actual: '2,432,123$23', expected: 2432123.23 },
|
||||||
|
{ actual: '2,45EUR', expected: 2.45 },
|
||||||
|
{ actual: '4.78€', expected: 4.78 },
|
||||||
|
{ actual: '28', expected: 28 },
|
||||||
|
{ actual: '-48', expected: -48 },
|
||||||
|
{ actual: '39USD', expected: 39 },
|
||||||
|
|
||||||
|
// Some negative numbers
|
||||||
|
{ actual: '-2,543.56', expected: -2543.56 },
|
||||||
|
{ actual: '-10 654.1234', expected: -10654.1234 },
|
||||||
|
{ actual: '-2.654$10', expected: -2654.1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const incorrectNumbers = [
|
||||||
|
'10 345,234.21', // too many different separators
|
||||||
|
'1.123.234,534,234', // impossible to detect where's the decimal separator
|
||||||
|
'10.4,2', // malformed digit groups
|
||||||
|
'1.123.2', // also malformed digit groups
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Test numbers', () => {
|
||||||
|
correctNumbers.forEach((item) => {
|
||||||
|
it(`"${item.actual}" should return ${item.expected}`, (done) => {
|
||||||
|
const parsed = multiNumberParse(item.actual);
|
||||||
|
assert.isNotNaN(parsed);
|
||||||
|
assert.equal(parsed, item.expected);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
incorrectNumbers.forEach((item) => {
|
||||||
|
it(`"${item}" should return NaN`, (done) => {
|
||||||
|
assert.isNaN(numberParse(item));
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
130
packages/server/src/utils/multi-number-parse.ts
Normal file
130
packages/server/src/utils/multi-number-parse.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
const validGrouping = (integerPart, sep) =>
|
||||||
|
integerPart.split(sep).reduce((acc, group, idx) => {
|
||||||
|
if (idx > 0) {
|
||||||
|
return acc && group.length === 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc && group.length;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
export const multiNumberParse = (number: number | string, standardDecSep = '.') => {
|
||||||
|
// if it's a number already, this is going to be easy...
|
||||||
|
if (typeof number === 'number') {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check validity of parameters
|
||||||
|
if (!number || typeof number !== 'string') {
|
||||||
|
throw new TypeError('number must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof standardDecSep !== 'string' || standardDecSep.length !== 1) {
|
||||||
|
throw new TypeError('standardDecSep must be a single character string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if negative
|
||||||
|
const negative = number[0] === '-';
|
||||||
|
|
||||||
|
// strip unnecessary chars
|
||||||
|
const stripped = number
|
||||||
|
// get rid of trailing non-numbers
|
||||||
|
.replace(/[^\d]+$/, '')
|
||||||
|
// get rid of the signal
|
||||||
|
.slice(negative ? 1 : 0);
|
||||||
|
|
||||||
|
// analyze separators
|
||||||
|
const separators = (stripped.match(/[^\d]/g) || []).reduce(
|
||||||
|
(acc, sep, idx) => {
|
||||||
|
const sepChr = `str_${sep.codePointAt(0)}`;
|
||||||
|
const cnt = ((acc[sepChr] || {}).cnt || 0) + 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[sepChr]: {
|
||||||
|
sep,
|
||||||
|
cnt,
|
||||||
|
lastIdx: idx,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
// check correctness of separators
|
||||||
|
const sepKeys = Object.keys(separators);
|
||||||
|
|
||||||
|
if (!sepKeys.length) {
|
||||||
|
// no separator, that's easy-peasy
|
||||||
|
return parseInt(stripped, 10) * (negative ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sepKeys.length > 2) {
|
||||||
|
// there's more than 2 separators, that's wrong
|
||||||
|
return Number.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sepKeys.length > 1) {
|
||||||
|
// there's two separators, that's ok by now
|
||||||
|
let sep1 = separators[sepKeys[0]];
|
||||||
|
let sep2 = separators[sepKeys[1]];
|
||||||
|
|
||||||
|
if (sep1.lastIdx > sep2.lastIdx) {
|
||||||
|
// swap
|
||||||
|
[sep1, sep2] = [sep2, sep1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if more than one separator appears more than once, that's wrong
|
||||||
|
if (sep1.cnt > 1 && sep2.cnt > 1) {
|
||||||
|
return Number.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the last separator is the single one
|
||||||
|
if (sep2.cnt > 1) {
|
||||||
|
return Number.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the groupings
|
||||||
|
const [integerPart] = stripped.split(sep2.sep);
|
||||||
|
|
||||||
|
if (!validGrouping(integerPart, sep1.sep)) {
|
||||||
|
return Number.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok, we got here! let's handle it
|
||||||
|
return (
|
||||||
|
parseFloat(stripped.split(sep1.sep).join('').replace(sep2.sep, '.')) *
|
||||||
|
(negative ? -1 : 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok, only one separator, which is nice
|
||||||
|
const sep = separators[sepKeys[0]];
|
||||||
|
|
||||||
|
if (sep.cnt > 1) {
|
||||||
|
// there's more than one separator, which means it's integer
|
||||||
|
// let's check the groupings
|
||||||
|
if (!validGrouping(stripped, sep.sep)) {
|
||||||
|
return Number.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's valid, let's return an integer
|
||||||
|
return parseInt(stripped.split(sep.sep).join(''), 10) * (negative ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// just one separator, let's check last group
|
||||||
|
const groups = stripped.split(sep.sep);
|
||||||
|
|
||||||
|
if (groups[groups.length - 1].length === 3) {
|
||||||
|
// ok, we're in ambiguous territory here
|
||||||
|
|
||||||
|
if (sep.sep !== standardDecSep) {
|
||||||
|
// it's an integer
|
||||||
|
return (
|
||||||
|
parseInt(stripped.split(sep.sep).join(''), 10) * (negative ? -1 : 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// well, it looks like it's a simple float
|
||||||
|
return parseFloat(stripped.replace(sep.sep, '.')) * (negative ? -1 : 1);
|
||||||
|
};
|
||||||
@@ -72,6 +72,10 @@ function ManualJournalActionsBar({
|
|||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => {
|
||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
|
// Handle import button click.
|
||||||
|
const handleImportBtnClick = () => {
|
||||||
|
history.push('/manual-journals/import');
|
||||||
|
}
|
||||||
|
|
||||||
// Handle table row size change.
|
// Handle table row size change.
|
||||||
const handleTableRowSizeChange = (size) => {
|
const handleTableRowSizeChange = (size) => {
|
||||||
@@ -130,6 +134,7 @@ function ManualJournalActionsBar({
|
|||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||||
text={<T id={'import'} />}
|
text={<T id={'import'} />}
|
||||||
|
onClick={handleImportBtnClick}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { DashboardInsider } from '@/components';
|
||||||
|
import { ImportView } from '../Import/ImportView';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function ManualJournalsImport() {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleCancelBtnClick = () => {
|
||||||
|
history.push('/manual-journals');
|
||||||
|
};
|
||||||
|
const handleImportSuccess = () => {
|
||||||
|
history.push('/manual-journals');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider name={'import-manual-journals'}>
|
||||||
|
<ImportView
|
||||||
|
resource={'manual-journals'}
|
||||||
|
onCancelClick={handleCancelBtnClick}
|
||||||
|
onImportSuccess={handleImportSuccess}
|
||||||
|
/>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
packages/webapp/src/containers/Expenses/ExpensesImport.tsx
Normal file
25
packages/webapp/src/containers/Expenses/ExpensesImport.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { DashboardInsider } from '@/components';
|
||||||
|
import { ImportView } from '../Import/ImportView';
|
||||||
|
|
||||||
|
export default function ExpensesImport() {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleCancelBtnClick = () => {
|
||||||
|
history.push('/expenses');
|
||||||
|
};
|
||||||
|
const handleImportSuccess = () => {
|
||||||
|
history.push('/expenses');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider name={'import-expenses'}>
|
||||||
|
<ImportView
|
||||||
|
resource={'expenses'}
|
||||||
|
onCancelClick={handleCancelBtnClick}
|
||||||
|
onImportSuccess={handleImportSuccess}
|
||||||
|
/>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -79,6 +79,11 @@ function ExpensesActionsBar({
|
|||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle the import button click.
|
||||||
|
const handleImportBtnClick = () => {
|
||||||
|
history.push('/expenses/import');
|
||||||
|
}
|
||||||
|
|
||||||
// Handle table row size change.
|
// Handle table row size change.
|
||||||
const handleTableRowSizeChange = (size) => {
|
const handleTableRowSizeChange = (size) => {
|
||||||
addSetting('expenses', 'tableSize', size);
|
addSetting('expenses', 'tableSize', size);
|
||||||
@@ -135,6 +140,7 @@ function ExpensesActionsBar({
|
|||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||||
text={<T id={'import'} />}
|
text={<T id={'import'} />}
|
||||||
|
onClick={handleImportBtnClick}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
|
|||||||
53
packages/webapp/src/containers/Import/AlertsManager.tsx
Normal file
53
packages/webapp/src/containers/Import/AlertsManager.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { useState, createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
interface AlertsManagerContextValue {
|
||||||
|
alerts: (string | number)[];
|
||||||
|
showAlert: (alert: string | number) => void;
|
||||||
|
hideAlert: (alert: string | number) => void;
|
||||||
|
|
||||||
|
hideAlerts: () => void;
|
||||||
|
isAlertActive: (alert: string | number) => boolean;
|
||||||
|
findAlert: (alert: string | number) => string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AlertsManagerContext = createContext<AlertsManagerContextValue>(
|
||||||
|
{} as AlertsManagerContextValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
export function AlertsManager({ children }: { children: ReactNode }) {
|
||||||
|
const [alerts, setAlerts] = useState<(string | number)[]>([]);
|
||||||
|
|
||||||
|
const showAlert = (type: string | number): void => {
|
||||||
|
setAlerts([...alerts, type]);
|
||||||
|
};
|
||||||
|
const hideAlert = (type: string | number): void => {
|
||||||
|
alerts.filter((t) => t !== type);
|
||||||
|
};
|
||||||
|
const hideAlerts = (): void => {
|
||||||
|
setAlerts([]);
|
||||||
|
};
|
||||||
|
const isAlertActive = (type: string | number): boolean => {
|
||||||
|
return alerts.some((t) => t === type);
|
||||||
|
};
|
||||||
|
const findAlert = (type: string | number): number | string | undefined => {
|
||||||
|
return alerts.find((t) => t === type);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertsManagerContext.Provider
|
||||||
|
value={{
|
||||||
|
alerts,
|
||||||
|
showAlert,
|
||||||
|
hideAlert,
|
||||||
|
hideAlerts,
|
||||||
|
isAlertActive,
|
||||||
|
findAlert,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AlertsManagerContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAlertsManager = () => useContext(AlertsManagerContext);
|
||||||
@@ -3,8 +3,11 @@ import { Field } from 'formik';
|
|||||||
import { Box, Group, Stack } from '@/components';
|
import { Box, Group, Stack } from '@/components';
|
||||||
import styles from './ImportDropzone.module.css';
|
import styles from './ImportDropzone.module.css';
|
||||||
import { ImportDropzoneField } from './ImportDropzoneFile';
|
import { ImportDropzoneField } from './ImportDropzoneFile';
|
||||||
|
import { useAlertsManager } from './AlertsManager';
|
||||||
|
|
||||||
export function ImportDropzone() {
|
export function ImportDropzone() {
|
||||||
|
const { hideAlerts } = useAlertsManager();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Field id={'file'} name={'file'} type="file">
|
<Field id={'file'} name={'file'} type="file">
|
||||||
@@ -12,6 +15,7 @@ export function ImportDropzone() {
|
|||||||
<ImportDropzoneField
|
<ImportDropzoneField
|
||||||
value={form.file}
|
value={form.file}
|
||||||
onChange={(file) => {
|
onChange={(file) => {
|
||||||
|
hideAlerts();
|
||||||
form.setFieldValue('file', file);
|
form.setFieldValue('file', file);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user