mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
Compare commits
1 Commits
import-fie
...
release/v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2baa667c5d |
1
packages/server/.gitignore
vendored
1
packages/server/.gitignore
vendored
@@ -3,4 +3,3 @@
|
|||||||
stdout.log
|
stdout.log
|
||||||
/dist
|
/dist
|
||||||
/build
|
/build
|
||||||
/public/imports
|
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
"@types/i18n": "^0.8.7",
|
"@types/i18n": "^0.8.7",
|
||||||
"@types/knex": "^0.16.1",
|
"@types/knex": "^0.16.1",
|
||||||
"@types/mathjs": "^6.0.12",
|
"@types/mathjs": "^6.0.12",
|
||||||
"@types/yup": "^0.29.13",
|
|
||||||
"accepts": "^1.3.7",
|
"accepts": "^1.3.7",
|
||||||
"accounting": "^0.4.1",
|
"accounting": "^0.4.1",
|
||||||
"agenda": "^4.2.1",
|
"agenda": "^4.2.1",
|
||||||
@@ -54,6 +53,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-basic-auth": "^1.2.0",
|
"express-basic-auth": "^1.2.0",
|
||||||
"express-boom": "^3.0.0",
|
"express-boom": "^3.0.0",
|
||||||
|
"express-fileupload": "^1.1.7-alpha.3",
|
||||||
"express-oauth-server": "^2.0.0",
|
"express-oauth-server": "^2.0.0",
|
||||||
"express-validator": "^6.12.2",
|
"express-validator": "^6.12.2",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
@@ -77,7 +77,6 @@
|
|||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.43",
|
||||||
"mongodb": "^6.1.0",
|
"mongodb": "^6.1.0",
|
||||||
"mongoose": "^5.10.0",
|
"mongoose": "^5.10.0",
|
||||||
"multer": "1.4.5-lts.1",
|
|
||||||
"mustache": "^3.0.3",
|
"mustache": "^3.0.3",
|
||||||
"mysql": "^2.17.1",
|
"mysql": "^2.17.1",
|
||||||
"mysql2": "^1.6.5",
|
"mysql2": "^1.6.5",
|
||||||
@@ -106,8 +105,7 @@
|
|||||||
"typedi": "^0.8.0",
|
"typedi": "^0.8.0",
|
||||||
"uniqid": "^5.2.0",
|
"uniqid": "^5.2.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5"
|
||||||
"yup": "^0.28.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.158",
|
"@types/lodash": "^4.14.158",
|
||||||
|
|||||||
BIN
packages/server/public/.DS_Store
vendored
BIN
packages/server/public/.DS_Store
vendored
Binary file not shown.
BIN
packages/server/public/imports/.DS_Store
vendored
BIN
packages/server/public/imports/.DS_Store
vendored
Binary file not shown.
@@ -242,7 +242,7 @@
|
|||||||
"account.field.normal.credit": "دائن",
|
"account.field.normal.credit": "دائن",
|
||||||
"account.field.normal.debit": "مدين",
|
"account.field.normal.debit": "مدين",
|
||||||
"account.field.type": "نوع الحساب",
|
"account.field.type": "نوع الحساب",
|
||||||
"account.field.active": "Active",
|
"account.field.active": "Activity",
|
||||||
"account.field.balance": "الرصيد",
|
"account.field.balance": "الرصيد",
|
||||||
"account.field.created_at": "أنشئت في",
|
"account.field.created_at": "أنشئت في",
|
||||||
"item.field.type": "نوع الصنف",
|
"item.field.type": "نوع الصنف",
|
||||||
|
|||||||
@@ -241,31 +241,28 @@
|
|||||||
"account.field.normal.credit": "Credit",
|
"account.field.normal.credit": "Credit",
|
||||||
"account.field.normal.debit": "Debit",
|
"account.field.normal.debit": "Debit",
|
||||||
"account.field.type": "Type",
|
"account.field.type": "Type",
|
||||||
"account.field.active": "Active",
|
"account.field.active": "Activity",
|
||||||
"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": "Item Name",
|
"item.field.name": "Name",
|
||||||
"item.field.code": "Item Code",
|
"item.field.code": "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.sell_price": "Sell Price",
|
"item.field.cost_account": "Cost account",
|
||||||
"item.field.cost_account": "Cost Account",
|
"item.field.sell_account": "Sell account",
|
||||||
"item.field.sell_account": "Sell Account",
|
"item.field.sell_description": "Sell description",
|
||||||
"item.field.sell_description": "Sell Description",
|
"item.field.inventory_account": "Inventory account",
|
||||||
"item.field.inventory_account": "Inventory Account",
|
"item.field.purchase_description": "Purchase description",
|
||||||
"item.field.purchase_description": "Purchase Description",
|
"item.field.quantity_on_hand": "Quantity on hand",
|
||||||
"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",
|
||||||
@@ -278,14 +275,8 @@
|
|||||||
"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",
|
||||||
@@ -294,8 +285,6 @@
|
|||||||
"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",
|
||||||
@@ -310,31 +299,22 @@
|
|||||||
"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 No.",
|
"bill_payment.field.payment_number": "Payment number",
|
||||||
"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",
|
||||||
@@ -362,30 +342,22 @@
|
|||||||
"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 No.",
|
"manual_journal.field.journal_number": "Journal number",
|
||||||
"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",
|
||||||
@@ -404,8 +376,8 @@
|
|||||||
"customer.field.last_name": "Last name",
|
"customer.field.last_name": "Last name",
|
||||||
"customer.field.display_name": "Display name",
|
"customer.field.display_name": "Display name",
|
||||||
"customer.field.email": "Email",
|
"customer.field.email": "Email",
|
||||||
"customer.field.work_phone": "Work Phone Number",
|
"customer.field.work_phone": "Work phone",
|
||||||
"customer.field.personal_phone": "Personal Phone Number",
|
"customer.field.personal_phone": "Personal phone",
|
||||||
"customer.field.company_name": "Company name",
|
"customer.field.company_name": "Company name",
|
||||||
"customer.field.website": "Website",
|
"customer.field.website": "Website",
|
||||||
"customer.field.opening_balance_at": "Opening balance at",
|
"customer.field.opening_balance_at": "Opening balance at",
|
||||||
@@ -413,7 +385,7 @@
|
|||||||
"customer.field.created_at": "Created at",
|
"customer.field.created_at": "Created at",
|
||||||
"customer.field.balance": "Balance",
|
"customer.field.balance": "Balance",
|
||||||
"customer.field.status": "Status",
|
"customer.field.status": "Status",
|
||||||
"customer.field.currency": "Currency",
|
"customer.field.currency": "Curreny",
|
||||||
"customer.field.status.active": "Active",
|
"customer.field.status.active": "Active",
|
||||||
"customer.field.status.inactive": "Inactive",
|
"customer.field.status.inactive": "Inactive",
|
||||||
"customer.field.status.overdue": "Overdue",
|
"customer.field.status.overdue": "Overdue",
|
||||||
@@ -422,8 +394,8 @@
|
|||||||
"vendor.field.last_name": "Last name",
|
"vendor.field.last_name": "Last name",
|
||||||
"vendor.field.display_name": "Display name",
|
"vendor.field.display_name": "Display name",
|
||||||
"vendor.field.email": "Email",
|
"vendor.field.email": "Email",
|
||||||
"vendor.field.work_phone": "Work Phone Number",
|
"vendor.field.work_phone": "Work phone",
|
||||||
"vendor.field.personal_phone": "Personal Phone Number",
|
"vendor.field.personal_phone": "Personal phone",
|
||||||
"vendor.field.company_name": "Company name",
|
"vendor.field.company_name": "Company name",
|
||||||
"vendor.field.website": "Website",
|
"vendor.field.website": "Website",
|
||||||
"vendor.field.opening_balance_at": "Opening balance at",
|
"vendor.field.opening_balance_at": "Opening balance at",
|
||||||
@@ -431,15 +403,13 @@
|
|||||||
"vendor.field.created_at": "Created at",
|
"vendor.field.created_at": "Created at",
|
||||||
"vendor.field.balance": "Balance",
|
"vendor.field.balance": "Balance",
|
||||||
"vendor.field.status": "Status",
|
"vendor.field.status": "Status",
|
||||||
"vendor.field.currency": "Currency",
|
"vendor.field.currency": "Curreny",
|
||||||
"vendor.field.status.active": "Active",
|
"vendor.field.status.active": "Active",
|
||||||
"vendor.field.status.inactive": "Inactive",
|
"vendor.field.status.inactive": "Inactive",
|
||||||
"vendor.field.status.overdue": "Overdue",
|
"vendor.field.status.overdue": "Overdue",
|
||||||
"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",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default class AccountsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Router constructor method.
|
* Router constructor method.
|
||||||
*/
|
*/
|
||||||
public router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
@@ -98,7 +98,7 @@ export default class AccountsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Create account DTO Schema validation.
|
* Create account DTO Schema validation.
|
||||||
*/
|
*/
|
||||||
private get createAccountDTOSchema() {
|
get createAccountDTOSchema() {
|
||||||
return [
|
return [
|
||||||
check('name')
|
check('name')
|
||||||
.exists()
|
.exists()
|
||||||
@@ -131,7 +131,7 @@ export default class AccountsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Account DTO Schema validation.
|
* Account DTO Schema validation.
|
||||||
*/
|
*/
|
||||||
private get editAccountDTOSchema() {
|
get editAccountDTOSchema() {
|
||||||
return [
|
return [
|
||||||
check('name')
|
check('name')
|
||||||
.exists()
|
.exists()
|
||||||
@@ -160,14 +160,14 @@ export default class AccountsController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private get accountParamSchema() {
|
get accountParamSchema() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts list validation schema.
|
* Accounts list validation schema.
|
||||||
*/
|
*/
|
||||||
private get accountsListSchema() {
|
get accountsListSchema() {
|
||||||
return [
|
return [
|
||||||
query('view_slug').optional({ nullable: true }).isString().trim(),
|
query('view_slug').optional({ nullable: true }).isString().trim(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export default class CashflowController {
|
|||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use(Container.get(CommandCashflowTransaction).router());
|
|
||||||
router.use(Container.get(GetCashflowTransaction).router());
|
router.use(Container.get(GetCashflowTransaction).router());
|
||||||
router.use(Container.get(GetCashflowAccounts).router());
|
router.use(Container.get(GetCashflowAccounts).router());
|
||||||
|
router.use(Container.get(CommandCashflowTransaction).router());
|
||||||
router.use(Container.get(DeleteCashflowTransaction).router());
|
router.use(Container.get(DeleteCashflowTransaction).router());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { param } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import DeleteCashflowTransactionService from '../../../services/Cashflow/DeleteCashflowTransactionService';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
|
|
||||||
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class DeleteCashflowTransactionController extends BaseController {
|
export default class DeleteCashflowTransaction extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private cashflowApplication: CashflowApplication;
|
deleteCashflowService: DeleteCashflowTransactionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller router.
|
* Controller router.
|
||||||
@@ -45,7 +44,7 @@ export default class DeleteCashflowTransactionController extends BaseController
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { oldCashflowTransaction } =
|
const { oldCashflowTransaction } =
|
||||||
await this.cashflowApplication.deleteTransaction(
|
await this.deleteCashflowService.deleteCashflowTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
transactionId
|
transactionId
|
||||||
);
|
);
|
||||||
@@ -93,19 +92,6 @@ export default class DeleteCashflowTransactionController extends BaseController
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
error.errorType ===
|
|
||||||
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED'
|
|
||||||
) {
|
|
||||||
return res.boom.badRequest(null, {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: 'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
|
|
||||||
code: 4100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { query } from 'express-validator';
|
import { param, query } from 'express-validator';
|
||||||
|
import GetCashflowAccountsService from '@/services/Cashflow/GetCashflowAccountsService';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
|
import GetCashflowTransactionsService from '@/services/Cashflow/GetCashflowTransactionsService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GetCashflowAccounts extends BaseController {
|
export default class GetCashflowAccounts extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private cashflowApplication: CashflowApplication;
|
getCashflowAccountsService: GetCashflowAccountsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
getCashflowTransactionsService: GetCashflowTransactionsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller router.
|
* Controller router.
|
||||||
@@ -58,7 +62,10 @@ export default class GetCashflowAccounts extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const cashflowAccounts =
|
const cashflowAccounts =
|
||||||
await this.cashflowApplication.getCashflowAccounts(tenantId, filter);
|
await this.getCashflowAccountsService.getCashflowAccounts(
|
||||||
|
tenantId,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
cashflow_accounts: this.transfromToResponse(cashflowAccounts),
|
cashflow_accounts: this.transfromToResponse(cashflowAccounts),
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { Service, Inject } from 'typedi';
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { param } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
|
import GetCashflowTransactionsService from '@/services/Cashflow/GetCashflowTransactionsService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GetCashflowAccounts extends BaseController {
|
export default class GetCashflowAccounts extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private cashflowApplication: CashflowApplication;
|
getCashflowTransactionsService: GetCashflowTransactionsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller router.
|
* Controller router.
|
||||||
@@ -43,10 +43,11 @@ export default class GetCashflowAccounts extends BaseController {
|
|||||||
const { transactionId } = req.params;
|
const { transactionId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cashflowTransaction = await this.cashflowApplication.getTransaction(
|
const cashflowTransaction =
|
||||||
tenantId,
|
await this.getCashflowTransactionsService.getCashflowTransaction(
|
||||||
transactionId
|
tenantId,
|
||||||
);
|
transactionId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
cashflow_transaction: this.transfromToResponse(cashflowTransaction),
|
cashflow_transaction: this.transfromToResponse(cashflowTransaction),
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { ValidationChain, check, param, query } from 'express-validator';
|
import { check } from 'express-validator';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import NewCashflowTransactionService from '@/services/Cashflow/NewCashflowTransactionService';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
import { AbilitySubject, CashflowAction } from '@/interfaces';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class NewCashflowTransactionController extends BaseController {
|
export default class NewCashflowTransactionController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private cashflowApplication: CashflowApplication;
|
private newCashflowTranscationService: NewCashflowTransactionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -18,18 +18,6 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
public router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/transactions/uncategorized/:id',
|
|
||||||
this.asyncMiddleware(this.getUncategorizedCashflowTransaction),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.get(
|
|
||||||
'/transactions/:id/uncategorized',
|
|
||||||
this.getUncategorizedTransactionsValidationSchema,
|
|
||||||
this.validationResult,
|
|
||||||
this.asyncMiddleware(this.getUncategorizedCashflowTransactions),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.post(
|
router.post(
|
||||||
'/transactions',
|
'/transactions',
|
||||||
CheckPolicies(CashflowAction.Create, AbilitySubject.Cashflow),
|
CheckPolicies(CashflowAction.Create, AbilitySubject.Cashflow),
|
||||||
@@ -38,72 +26,13 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
this.asyncMiddleware(this.newCashflowTransaction),
|
this.asyncMiddleware(this.newCashflowTransaction),
|
||||||
this.catchServiceErrors
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
|
||||||
'/transactions/:id/uncategorize',
|
|
||||||
this.revertCategorizedCashflowTransaction,
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.post(
|
|
||||||
'/transactions/:id/categorize',
|
|
||||||
this.categorizeCashflowTransactionValidationSchema,
|
|
||||||
this.validationResult,
|
|
||||||
this.categorizeCashflowTransaction,
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.post(
|
|
||||||
'/transaction/:id/categorize/expense',
|
|
||||||
this.categorizeAsExpenseValidationSchema,
|
|
||||||
this.validationResult,
|
|
||||||
this.categorizesCashflowTransactionAsExpense,
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Getting uncategorized transactions validation schema.
|
|
||||||
* @returns {ValidationChain}
|
|
||||||
*/
|
|
||||||
public get getUncategorizedTransactionsValidationSchema() {
|
|
||||||
return [
|
|
||||||
param('id').exists().isNumeric().toInt(),
|
|
||||||
query('page').optional().isNumeric().toInt(),
|
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize as expense validation schema.
|
|
||||||
*/
|
|
||||||
public get categorizeAsExpenseValidationSchema() {
|
|
||||||
return [
|
|
||||||
check('expense_account_id').exists(),
|
|
||||||
check('date').isISO8601().exists(),
|
|
||||||
check('reference_no').optional(),
|
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize cashflow tranasction validation schema.
|
|
||||||
*/
|
|
||||||
public get categorizeCashflowTransactionValidationSchema() {
|
|
||||||
return [
|
|
||||||
check('date').exists().isISO8601().toDate(),
|
|
||||||
check('credit_account_id').exists().isInt().toInt(),
|
|
||||||
check('transaction_number').optional(),
|
|
||||||
check('transaction_type').exists(),
|
|
||||||
check('reference_no').optional(),
|
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
|
||||||
check('description').optional(),
|
|
||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New cashflow transaction validation schema.
|
* New cashflow transaction validation schema.
|
||||||
*/
|
*/
|
||||||
public get newTransactionValidationSchema() {
|
get newTransactionValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('date').exists().isISO8601().toDate(),
|
check('date').exists().isISO8601().toDate(),
|
||||||
check('reference_no').optional({ nullable: true }).trim().escape(),
|
check('reference_no').optional({ nullable: true }).trim().escape(),
|
||||||
@@ -119,7 +48,9 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
check('credit_account_id').exists().isInt().toInt(),
|
check('credit_account_id').exists().isInt().toInt(),
|
||||||
|
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
|
|
||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
|
||||||
check('publish').default(false).isBoolean().toBoolean(),
|
check('publish').default(false).isBoolean().toBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -139,12 +70,13 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
const ownerContributionDTO = this.matchedBodyData(req);
|
const ownerContributionDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cashflowTransaction =
|
const { cashflowTransaction } =
|
||||||
await this.cashflowApplication.createTransaction(
|
await this.newCashflowTranscationService.newCashflowTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
ownerContributionDTO,
|
ownerContributionDTO,
|
||||||
userId
|
userId
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: cashflowTransaction.id,
|
id: cashflowTransaction.id,
|
||||||
message: 'New cashflow transaction has been created successfully.',
|
message: 'New cashflow transaction has been created successfully.',
|
||||||
@@ -154,147 +86,11 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Revert the categorized cashflow transaction.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private revertCategorizedCashflowTransaction = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) => {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { id: cashflowTransactionId } = req.params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.cashflowApplication.uncategorizeTransaction(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId
|
|
||||||
);
|
|
||||||
return res.status(200).send({ data });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize the cashflow transaction.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private categorizeCashflowTransaction = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) => {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { id: cashflowTransactionId } = req.params;
|
|
||||||
const cashflowTransaction = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.cashflowApplication.categorizeTransaction(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId,
|
|
||||||
cashflowTransaction
|
|
||||||
);
|
|
||||||
return res.status(200).send({
|
|
||||||
message: 'The cashflow transaction has been created successfully.',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize the transaction as expense transaction.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private categorizesCashflowTransactionAsExpense = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) => {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { id: cashflowTransactionId } = req.params;
|
|
||||||
const cashflowTransaction = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.cashflowApplication.categorizeAsExpense(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId,
|
|
||||||
cashflowTransaction
|
|
||||||
);
|
|
||||||
return res.status(200).send({
|
|
||||||
message: 'The cashflow transaction has been created successfully.',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the uncategorized cashflow transactions.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
public getUncategorizedCashflowTransaction = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) => {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { id: transactionId } = req.params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.cashflowApplication.getUncategorizedTransaction(
|
|
||||||
tenantId,
|
|
||||||
transactionId
|
|
||||||
);
|
|
||||||
return res.status(200).send({ data });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the uncategorized cashflow transactions.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
public getUncategorizedCashflowTransactions = async (
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) => {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { id: accountId } = req.params;
|
|
||||||
const query = this.matchedQueryData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.cashflowApplication.getUncategorizedTransactions(
|
|
||||||
tenantId,
|
|
||||||
accountId,
|
|
||||||
query
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send(data);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the service errors.
|
* Handle the service errors.
|
||||||
* @param error
|
* @param error
|
||||||
* @param {Request} req
|
* @param req
|
||||||
* @param {res
|
* @param res
|
||||||
* @param next
|
* @param next
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
@@ -344,16 +140,6 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error.errorType === 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID') {
|
|
||||||
return res.boom.badRequest(null, {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
|
|
||||||
code: 4100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,8 +160,10 @@ export default class CustomersController extends ContactsController {
|
|||||||
try {
|
try {
|
||||||
const contact = await this.customersApplication.createCustomer(
|
const contact = await this.customersApplication.createCustomer(
|
||||||
tenantId,
|
tenantId,
|
||||||
contactDTO
|
contactDTO,
|
||||||
|
user
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: contact.id,
|
id: contact.id,
|
||||||
message: 'The customer has been created successfully.',
|
message: 'The customer has been created successfully.',
|
||||||
|
|||||||
@@ -1,250 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
|
||||||
import { body, param, query } from 'express-validator';
|
|
||||||
import { defaultTo } from 'lodash';
|
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { ImportResourceApplication } from '@/services/Import/ImportResourceApplication';
|
|
||||||
import { uploadImportFile } from './_utils';
|
|
||||||
import { parseJsonSafe } from '@/utils/parse-json-safe';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportController extends BaseController {
|
|
||||||
@Inject()
|
|
||||||
private importResourceApp: ImportResourceApplication;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Router constructor method.
|
|
||||||
*/
|
|
||||||
router() {
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
'/file',
|
|
||||||
uploadImportFile.single('file'),
|
|
||||||
this.importValidationSchema,
|
|
||||||
this.validationResult,
|
|
||||||
this.asyncMiddleware(this.fileUpload.bind(this)),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.post(
|
|
||||||
'/:import_id/import',
|
|
||||||
this.asyncMiddleware(this.import.bind(this)),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.post(
|
|
||||||
'/:import_id/mapping',
|
|
||||||
[
|
|
||||||
param('import_id').exists().isString(),
|
|
||||||
body('mapping').exists().isArray({ min: 1 }),
|
|
||||||
body('mapping.*.group').optional(),
|
|
||||||
body('mapping.*.from').exists(),
|
|
||||||
body('mapping.*.to').exists(),
|
|
||||||
],
|
|
||||||
this.validationResult,
|
|
||||||
this.asyncMiddleware(this.mapping.bind(this)),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.get(
|
|
||||||
'/sample',
|
|
||||||
[query('resource').exists(), query('format').optional()],
|
|
||||||
this.validationResult,
|
|
||||||
this.downloadImportSample.bind(this),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.get(
|
|
||||||
'/:import_id',
|
|
||||||
this.asyncMiddleware(this.getImportFileMeta.bind(this)),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
router.get(
|
|
||||||
'/:import_id/preview',
|
|
||||||
this.asyncMiddleware(this.preview.bind(this)),
|
|
||||||
this.catchServiceErrors
|
|
||||||
);
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import validation schema.
|
|
||||||
* @returns {ValidationSchema[]}
|
|
||||||
*/
|
|
||||||
private get importValidationSchema() {
|
|
||||||
return [body('resource').exists(), body('params').optional()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports xlsx/csv to the given resource type.
|
|
||||||
* @param {Request} req -
|
|
||||||
* @param {Response} res -
|
|
||||||
* @param {NextFunction} next -
|
|
||||||
*/
|
|
||||||
private async fileUpload(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const body = this.matchedBodyData(req);
|
|
||||||
const params = defaultTo(parseJsonSafe(body.params), {});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.importResourceApp.import(
|
|
||||||
tenantId,
|
|
||||||
body.resource,
|
|
||||||
req.file.filename,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
return res.status(200).send(data);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the columns of the imported file.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async mapping(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { import_id: importId } = req.params;
|
|
||||||
const body = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const mapping = await this.importResourceApp.mapping(
|
|
||||||
tenantId,
|
|
||||||
importId,
|
|
||||||
body?.mapping
|
|
||||||
);
|
|
||||||
return res.status(200).send(mapping);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview the imported file before actual importing.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async preview(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { import_id: importId } = req.params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const preview = await this.importResourceApp.preview(tenantId, importId);
|
|
||||||
|
|
||||||
return res.status(200).send(preview);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Importing the imported file to the application storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async import(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { import_id: importId } = req.params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await this.importResourceApp.process(tenantId, importId);
|
|
||||||
|
|
||||||
return res.status(200).send(result);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the csv/xlsx sample sheet of the given resource name.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async downloadImportSample(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { format, resource } = this.matchedQueryData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = this.importResourceApp.sample(tenantId, resource, format);
|
|
||||||
|
|
||||||
return res.status(200).send(result);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the import file meta.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async getImportFileMeta(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { import_id: importId } = req.params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await this.importResourceApp.importMeta(
|
|
||||||
tenantId,
|
|
||||||
importId
|
|
||||||
);
|
|
||||||
return res.status(200).send(result);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms service errors to response.
|
|
||||||
* @param {Error}
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {ServiceError} error
|
|
||||||
*/
|
|
||||||
private catchServiceErrors(
|
|
||||||
error,
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
if (error instanceof ServiceError) {
|
|
||||||
if (error.errorType === 'INVALID_MAP_ATTRS') {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'INVALID_MAP_ATTRS' }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (error.errorType === 'DUPLICATED_FROM_MAP_ATTR') {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'DUPLICATED_FROM_MAP_ATTR' }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (error.errorType === 'DUPLICATED_TO_MAP_ATTR') {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'DUPLICATED_TO_MAP_ATTR' }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (error.errorType === 'IMPORTED_FILE_EXTENSION_INVALID') {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'IMPORTED_FILE_EXTENSION_INVALID' }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: error.errorType }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import Multer from 'multer';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
|
|
||||||
export function allowSheetExtensions(req, file, cb) {
|
|
||||||
if (
|
|
||||||
file.mimetype !== 'text/csv' &&
|
|
||||||
file.mimetype !== 'application/vnd.ms-excel' &&
|
|
||||||
file.mimetype !==
|
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
||||||
) {
|
|
||||||
cb(new ServiceError('IMPORTED_FILE_EXTENSION_INVALID'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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({
|
|
||||||
storage,
|
|
||||||
limits: { fileSize: 5 * 1024 * 1024 },
|
|
||||||
fileFilter: allowSheetExtensions,
|
|
||||||
});
|
|
||||||
@@ -6,7 +6,7 @@ import ItemTransactionsController from './ItemsTransactions';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ItemsBaseController {
|
export default class ItemsBaseController {
|
||||||
public router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/', Container.get(ItemsController).router());
|
router.use('/', Container.get(ItemsController).router());
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import { ProjectsController } from './controllers/Projects/Projects';
|
|||||||
import { ProjectTasksController } from './controllers/Projects/Tasks';
|
import { ProjectTasksController } from './controllers/Projects/Tasks';
|
||||||
import { ProjectTimesController } from './controllers/Projects/Times';
|
import { ProjectTimesController } from './controllers/Projects/Times';
|
||||||
import { TaxRatesController } from './controllers/TaxRates/TaxRates';
|
import { TaxRatesController } from './controllers/TaxRates/TaxRates';
|
||||||
import { ImportController } from './controllers/Import/ImportController';
|
|
||||||
import { BankingController } from './controllers/Banking/BankingController';
|
import { BankingController } from './controllers/Banking/BankingController';
|
||||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||||
|
|
||||||
@@ -136,9 +135,6 @@ export default () => {
|
|||||||
dashboard.use('/warehouses', Container.get(WarehousesController).router());
|
dashboard.use('/warehouses', Container.get(WarehousesController).router());
|
||||||
dashboard.use('/projects', Container.get(ProjectsController).router());
|
dashboard.use('/projects', Container.get(ProjectsController).router());
|
||||||
dashboard.use('/tax-rates', Container.get(TaxRatesController).router());
|
dashboard.use('/tax-rates', Container.get(TaxRatesController).router());
|
||||||
|
|
||||||
dashboard.use('/import', Container.get(ImportController).router());
|
|
||||||
|
|
||||||
dashboard.use('/', Container.get(ProjectTasksController).router());
|
dashboard.use('/', Container.get(ProjectTasksController).router());
|
||||||
dashboard.use('/', Container.get(ProjectTimesController).router());
|
dashboard.use('/', Container.get(ProjectTimesController).router());
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.createTable(
|
|
||||||
'uncategorized_cashflow_transactions',
|
|
||||||
(table) => {
|
|
||||||
table.increments('id');
|
|
||||||
table.date('date').index();
|
|
||||||
table.decimal('amount');
|
|
||||||
table.string('currency_code');
|
|
||||||
table.string('reference_no').index();
|
|
||||||
table.string('payee');
|
|
||||||
table
|
|
||||||
.integer('account_id')
|
|
||||||
.unsigned()
|
|
||||||
.references('id')
|
|
||||||
.inTable('accounts');
|
|
||||||
table.string('description');
|
|
||||||
table.string('categorize_ref_type');
|
|
||||||
table.integer('categorize_ref_id').unsigned();
|
|
||||||
table.boolean('categorized').defaultTo(false);
|
|
||||||
table.string('plaid_transaction_id');
|
|
||||||
table.timestamps();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {
|
|
||||||
return knex.schema.dropTableIfExists('uncategorized_cashflow_transactions');
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.table('accounts', (table) => {
|
|
||||||
table.integer('uncategorized_transactions').defaultTo(0);
|
|
||||||
table.boolean('is_system_account').defaultTo(true);
|
|
||||||
table.boolean('is_feeds_active').defaultTo(false);
|
|
||||||
table.datetime('last_feeds_updated_at').nullable();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.table('cashflow_transactions', (table) => {
|
|
||||||
table
|
|
||||||
.integer('uncategorized_transaction_id')
|
|
||||||
.unsigned()
|
|
||||||
.references('id')
|
|
||||||
.inTable('uncategorized_cashflow_transactions');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = function (knex) {
|
|
||||||
return knex.schema.table('cashflow_transactions', (table) => {
|
|
||||||
table.dropColumn('uncategorized_transaction_id');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -233,38 +233,3 @@ export interface ICashflowTransactionSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
|
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
|
||||||
|
|
||||||
export interface ICategorizeCashflowTransactioDTO {
|
|
||||||
creditAccountId: number;
|
|
||||||
referenceNo: string;
|
|
||||||
transactionNumber: string;
|
|
||||||
transactionType: string;
|
|
||||||
exchangeRate: number;
|
|
||||||
description: string;
|
|
||||||
branchId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUncategorizedCashflowTransaction {
|
|
||||||
id?: number;
|
|
||||||
amount: number;
|
|
||||||
date: Date;
|
|
||||||
currencyCode: string;
|
|
||||||
accountId: number;
|
|
||||||
description: string;
|
|
||||||
referenceNo: string;
|
|
||||||
categorizeRefType: string;
|
|
||||||
categorizeRefId: number;
|
|
||||||
categorized: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface CreateUncategorizedTransactionDTO {
|
|
||||||
date: Date | string;
|
|
||||||
accountId: number;
|
|
||||||
amount: number;
|
|
||||||
currencyCode: string;
|
|
||||||
payee?: string;
|
|
||||||
description?: string;
|
|
||||||
referenceNo?: string | null;
|
|
||||||
plaidTransactionId?: string | null;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { IAccount } from './Account';
|
import { IAccount } from './Account';
|
||||||
import { IUncategorizedCashflowTransaction } from './CashFlow';
|
|
||||||
|
|
||||||
export interface ICashflowAccountTransactionsFilter {
|
export interface ICashflowAccountTransactionsFilter {
|
||||||
page: number;
|
page: number;
|
||||||
@@ -51,7 +50,6 @@ export interface ICashflowCommandDTO {
|
|||||||
|
|
||||||
export interface ICashflowNewCommandDTO extends ICashflowCommandDTO {
|
export interface ICashflowNewCommandDTO extends ICashflowCommandDTO {
|
||||||
plaidAccountId?: string;
|
plaidAccountId?: string;
|
||||||
uncategorizedTransactionId?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICashflowTransaction {
|
export interface ICashflowTransaction {
|
||||||
@@ -84,8 +82,6 @@ export interface ICashflowTransaction {
|
|||||||
|
|
||||||
isCashDebit?: boolean;
|
isCashDebit?: boolean;
|
||||||
isCashCredit?: boolean;
|
isCashCredit?: boolean;
|
||||||
|
|
||||||
uncategorizedTransactionId?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICashflowTransactionLine {
|
export interface ICashflowTransactionLine {
|
||||||
@@ -128,39 +124,8 @@ export interface ICommandCashflowDeletedPayload {
|
|||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICashflowTransactionCategorizedPayload {
|
|
||||||
tenantId: number;
|
|
||||||
cashflowTransactionId: number;
|
|
||||||
cashflowTransaction: ICashflowTransaction;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
export interface ICashflowTransactionUncategorizingPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransaction: IUncategorizedCashflowTransaction;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
export interface ICashflowTransactionUncategorizedPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransaction: IUncategorizedCashflowTransaction;
|
|
||||||
oldUncategorizedTransaction: IUncategorizedCashflowTransaction;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CashflowAction {
|
export enum CashflowAction {
|
||||||
Create = 'Create',
|
Create = 'Create',
|
||||||
Delete = 'Delete',
|
Delete = 'Delete',
|
||||||
View = 'View',
|
View = 'View',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategorizeTransactionAsExpenseDTO {
|
|
||||||
expenseAccountId: number;
|
|
||||||
exchangeRate: number;
|
|
||||||
referenceNo: string;
|
|
||||||
description: string;
|
|
||||||
branchId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetUncategorizedTransactionsQuery {
|
|
||||||
page?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,45 +32,22 @@ export interface IModelMetaFieldCommon {
|
|||||||
name: string;
|
name: string;
|
||||||
column: string;
|
column: string;
|
||||||
columnable?: boolean;
|
columnable?: boolean;
|
||||||
|
fieldType: IModelColumnType;
|
||||||
customQuery?: Function;
|
customQuery?: Function;
|
||||||
required?: boolean;
|
|
||||||
importHint?: string;
|
|
||||||
importableRelationLabel?: string;
|
|
||||||
order?: number;
|
|
||||||
unique?: number;
|
|
||||||
dataTransferObjectKey?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModelMetaFieldText {
|
export interface IModelMetaFieldNumber {
|
||||||
fieldType: 'text';
|
fieldType: 'number';
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
}
|
}
|
||||||
export interface IModelMetaFieldBoolean {
|
|
||||||
fieldType: 'boolean';
|
export interface IModelMetaFieldOther {
|
||||||
}
|
fieldType: 'text' | 'boolean';
|
||||||
export interface IModelMetaFieldNumber {
|
|
||||||
fieldType: 'number';
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
}
|
|
||||||
export interface IModelMetaFieldDate {
|
|
||||||
fieldType: 'date';
|
|
||||||
}
|
|
||||||
export interface IModelMetaFieldUrl {
|
|
||||||
fieldType: 'url';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IModelMetaField = IModelMetaFieldCommon &
|
export type IModelMetaField = IModelMetaFieldCommon &
|
||||||
(
|
(IModelMetaFieldOther | IModelMetaEnumerationField | IModelMetaRelationField);
|
||||||
| IModelMetaFieldText
|
|
||||||
| IModelMetaFieldNumber
|
|
||||||
| IModelMetaFieldBoolean
|
|
||||||
| IModelMetaFieldDate
|
|
||||||
| IModelMetaFieldUrl
|
|
||||||
| IModelMetaEnumerationField
|
|
||||||
| IModelMetaRelationField
|
|
||||||
| IModelMetaCollectionField
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface IModelMetaEnumerationOption {
|
export interface IModelMetaEnumerationOption {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -93,71 +70,12 @@ export interface IModelMetaRelationEnumerationField {
|
|||||||
relationEntityKey: string;
|
relationEntityKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModelMetaFieldWithFields {
|
export type IModelMetaRelationField = IModelMetaRelationFieldCommon & (
|
||||||
fields: IModelMetaFieldCommon2 &
|
IModelMetaRelationEnumerationField
|
||||||
(
|
);
|
||||||
| 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 &
|
|
||||||
IModelMetaRelationEnumerationField;
|
|
||||||
|
|
||||||
export interface IModelMeta {
|
export interface IModelMeta {
|
||||||
defaultFilterField: string;
|
defaultFilterField: string;
|
||||||
defaultSort: IModelMetaDefaultSort;
|
defaultSort: IModelMetaDefaultSort;
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export interface PlaidTransaction {
|
|||||||
iso_currency_code: string;
|
iso_currency_code: string;
|
||||||
transaction_id: string;
|
transaction_id: string;
|
||||||
transaction_type: string;
|
transaction_type: string;
|
||||||
payment_meta: { reference_number: string | null; payee: string | null };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaidFetchedTransactionsUpdates {
|
export interface PlaidFetchedTransactionsUpdates {
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ import { PlaidUpdateTransactionsOnItemCreatedSubscriber } from '@/services/Banki
|
|||||||
import { InvoiceChangeStatusOnMailSentSubscriber } from '@/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber';
|
import { InvoiceChangeStatusOnMailSentSubscriber } from '@/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber';
|
||||||
import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber';
|
import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber';
|
||||||
import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent';
|
import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent';
|
||||||
import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize';
|
|
||||||
import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete'; }
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -214,10 +212,6 @@ export const susbcribers = () => {
|
|||||||
SyncItemTaxRateOnEditTaxSubscriber,
|
SyncItemTaxRateOnEditTaxSubscriber,
|
||||||
|
|
||||||
// Plaid
|
// Plaid
|
||||||
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
PlaidUpdateTransactionsOnItemCreatedSubscriber
|
||||||
|
|
||||||
// Cashflow
|
|
||||||
DeleteCashflowTransactionOnUncategorize,
|
|
||||||
PreventDeleteTransactionOnDelete
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import helmet from 'helmet';
|
|||||||
import boom from 'express-boom';
|
import boom from 'express-boom';
|
||||||
import errorHandler from 'errorhandler';
|
import errorHandler from 'errorhandler';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
|
import fileUpload from 'express-fileupload';
|
||||||
import { Server } from 'socket.io';
|
import { Server } from 'socket.io';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import routes from 'api';
|
import routes from 'api';
|
||||||
@@ -46,6 +47,13 @@ export default ({ app }) => {
|
|||||||
|
|
||||||
app.use('/public', express.static(path.join(global.__storage_dir)));
|
app.use('/public', express.static(path.join(global.__storage_dir)));
|
||||||
|
|
||||||
|
// Handle multi-media requests.
|
||||||
|
app.use(
|
||||||
|
fileUpload({
|
||||||
|
createParentPath: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Logger middleware.
|
// Logger middleware.
|
||||||
app.use(LoggerMiddleware);
|
app.use(LoggerMiddleware);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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);
|
||||||
@@ -26,9 +25,6 @@ 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().then(() => {
|
agenda.start();
|
||||||
agenda.every('1 hours', 'delete-expired-imported-files', {});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ 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 PlaidItem from 'models/PlaidItem';
|
import PlaidItem from 'models/PlaidItem';
|
||||||
import UncategorizedCashflowTransaction from 'models/UncategorizedCashflowTransaction';
|
|
||||||
|
|
||||||
export default (knex) => {
|
export default (knex) => {
|
||||||
const models = {
|
const models = {
|
||||||
@@ -127,8 +126,7 @@ export default (knex) => {
|
|||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRateTransaction,
|
TaxRateTransaction,
|
||||||
Attachment,
|
Attachment,
|
||||||
PlaidItem,
|
PlaidItem
|
||||||
UncategorizedCashflowTransaction
|
|
||||||
};
|
};
|
||||||
return mapValues(models, (model) => model.bindKnex(knex));
|
return mapValues(models, (model) => model.bindKnex(knex));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export default {
|
|||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
},
|
},
|
||||||
importable: true,
|
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
name: 'account.field.name',
|
name: 'account.field.name',
|
||||||
@@ -59,7 +58,7 @@ export default {
|
|||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: ACCOUNT_TYPES.map((accountType) => ({
|
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||||
label: accountType.label,
|
label: accountType.label,
|
||||||
key: accountType.key,
|
key: accountType.key
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
@@ -85,49 +84,6 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ export default class Account extends mixin(TenantModel, [
|
|||||||
const Expense = require('models/Expense');
|
const Expense = require('models/Expense');
|
||||||
const ExpenseEntry = require('models/ExpenseCategory');
|
const ExpenseEntry = require('models/ExpenseCategory');
|
||||||
const ItemEntry = require('models/ItemEntry');
|
const ItemEntry = require('models/ItemEntry');
|
||||||
const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -306,21 +305,6 @@ export default class Account extends mixin(TenantModel, [
|
|||||||
to: 'items_entries.sellAccountId',
|
to: 'items_entries.sellAccountId',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Associated uncategorized transactions.
|
|
||||||
*/
|
|
||||||
uncategorizedTransactions: {
|
|
||||||
relation: Model.HasManyRelation,
|
|
||||||
modelClass: UncategorizedTransaction.default,
|
|
||||||
join: {
|
|
||||||
from: 'accounts.id',
|
|
||||||
to: 'uncategorized_cashflow_transactions.accountId',
|
|
||||||
},
|
|
||||||
filter: (query) => {
|
|
||||||
query.where('categorized', false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
|
|
||||||
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',
|
||||||
@@ -80,77 +77,6 @@ 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,10 +4,6 @@ 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',
|
||||||
@@ -37,7 +33,7 @@ export default {
|
|||||||
|
|
||||||
relationType: 'enumeration',
|
relationType: 'enumeration',
|
||||||
relationKey: 'paymentAccount',
|
relationKey: 'paymentAccount',
|
||||||
|
|
||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
@@ -67,67 +63,4 @@ 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,8 +12,6 @@ export default class CashflowTransaction extends TenantModel {
|
|||||||
transactionType: string;
|
transactionType: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
exchangeRate: number;
|
exchangeRate: number;
|
||||||
uncategorize: boolean;
|
|
||||||
uncategorizedTransaction!: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
@@ -87,14 +85,6 @@ export default class CashflowTransaction extends TenantModel {
|
|||||||
return this.typeMeta?.direction === CASHFLOW_DIRECTION.IN;
|
return this.typeMeta?.direction === CASHFLOW_DIRECTION.IN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the transaction imported from uncategorized transaction.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get isCategroizedTranasction() {
|
|
||||||
return !!this.uncategorizedTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ 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',
|
||||||
@@ -81,72 +77,4 @@ 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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,137 +1,36 @@
|
|||||||
export default {
|
export default {
|
||||||
importable: true,
|
|
||||||
defaultFilterField: 'displayName',
|
|
||||||
defaultSort: {
|
|
||||||
sortOrder: 'DESC',
|
|
||||||
sortField: 'created_at',
|
|
||||||
},
|
|
||||||
fields: {
|
fields: {
|
||||||
first_name: {
|
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: {
|
|
||||||
name: 'Customer Type',
|
|
||||||
fieldType: 'enumeration',
|
|
||||||
options: [
|
|
||||||
{ key: 'business', label: 'Business' },
|
|
||||||
{ key: 'individual', label: 'Individual' },
|
|
||||||
],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
firstName: {
|
|
||||||
name: 'customer.field.first_name',
|
name: 'customer.field.first_name',
|
||||||
column: 'first_name',
|
column: 'first_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
lastName: {
|
last_name: {
|
||||||
name: 'customer.field.last_name',
|
name: 'customer.field.last_name',
|
||||||
column: 'last_name',
|
column: 'last_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
displayName: {
|
display_name: {
|
||||||
name: 'customer.field.display_name',
|
name: 'customer.field.display_name',
|
||||||
column: 'display_name',
|
column: 'display_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
name: 'customer.field.email',
|
name: 'customer.field.email',
|
||||||
column: 'email',
|
column: 'email',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
workPhone: {
|
work_phone: {
|
||||||
name: 'customer.field.work_phone',
|
name: 'customer.field.work_phone',
|
||||||
column: 'work_phone',
|
column: 'work_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
personalPhone: {
|
personal_phone: {
|
||||||
name: 'customer.field.personal_phone',
|
name: 'customer.field.personal_phone',
|
||||||
column: 'personal_phone',
|
column: 'personal_phone',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
companyName: {
|
company_name: {
|
||||||
name: 'customer.field.company_name',
|
name: 'customer.field.company_name',
|
||||||
column: 'company_name',
|
column: 'company_name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
@@ -139,110 +38,44 @@ export default {
|
|||||||
website: {
|
website: {
|
||||||
name: 'customer.field.website',
|
name: 'customer.field.website',
|
||||||
column: 'website',
|
column: 'website',
|
||||||
fieldType: 'url',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
openingBalance: {
|
created_at: {
|
||||||
|
name: 'customer.field.created_at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'customer.field.balance',
|
||||||
|
column: 'balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
opening_balance: {
|
||||||
name: 'customer.field.opening_balance',
|
name: 'customer.field.opening_balance',
|
||||||
column: 'opening_balance',
|
column: 'opening_balance',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
opening_balance_at: {
|
||||||
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',
|
||||||
},
|
},
|
||||||
openingBalanceExchangeRate: {
|
currency_code: {
|
||||||
name: 'Opening Balance Ex. Rate',
|
|
||||||
column: 'opening_balance_exchange_rate',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
currencyCode: {
|
|
||||||
name: 'customer.field.currency',
|
name: 'customer.field.currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
note: {
|
status: {
|
||||||
name: 'Note',
|
name: 'customer.field.status',
|
||||||
column: 'note',
|
fieldType: 'enumeration',
|
||||||
fieldType: 'text',
|
options: [
|
||||||
},
|
{ key: 'active', label: 'customer.field.status.active' },
|
||||||
active: {
|
{ key: 'inactive', label: 'customer.field.status.inactive' },
|
||||||
name: 'Active',
|
{ key: 'overdue', label: 'customer.field.status.overdue' },
|
||||||
column: 'active',
|
{ key: 'unpaid', label: 'customer.field.status.unpaid' },
|
||||||
fieldType: 'boolean',
|
],
|
||||||
},
|
filterCustomQuery: statusFieldFilterQuery,
|
||||||
// Billing Address
|
|
||||||
billingAddress1: {
|
|
||||||
name: 'Billing Address 1',
|
|
||||||
column: 'billing_address1',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddress2: {
|
|
||||||
name: 'Billing Address 2',
|
|
||||||
column: 'billing_address2',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressCity: {
|
|
||||||
name: 'Billing Address City',
|
|
||||||
column: 'billing_address_city',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressCountry: {
|
|
||||||
name: 'Billing Address Country',
|
|
||||||
column: 'billing_address_country',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressPostcode: {
|
|
||||||
name: 'Billing Address Postcode',
|
|
||||||
column: 'billing_address_postcode',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressState: {
|
|
||||||
name: 'Billing Address State',
|
|
||||||
column: 'billing_address_state',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressPhone: {
|
|
||||||
name: 'Billing Address Phone',
|
|
||||||
column: 'billing_address_phone',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
// Shipping Address
|
|
||||||
shippingAddress1: {
|
|
||||||
name: 'Shipping Address 1',
|
|
||||||
column: 'shipping_address1',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddress2: {
|
|
||||||
name: 'Shipping Address 2',
|
|
||||||
column: 'shipping_address2',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressCity: {
|
|
||||||
name: 'Shipping Address City',
|
|
||||||
column: 'shipping_address_city',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressCountry: {
|
|
||||||
name: 'Shipping Address Country',
|
|
||||||
column: 'shipping_address_country',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressPostcode: {
|
|
||||||
name: 'Shipping Address Postcode',
|
|
||||||
column: 'shipping_address_postcode',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressPhone: {
|
|
||||||
name: 'Shipping Address Phone',
|
|
||||||
column: 'shipping_address_phone',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressState: {
|
|
||||||
name: 'Shipping Address State',
|
|
||||||
column: 'shipping_address_state',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ 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',
|
||||||
@@ -25,27 +24,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: [
|
||||||
@@ -55,71 +54,12 @@ 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) {
|
||||||
|
|||||||
@@ -215,10 +215,6 @@ export default class Expense extends mixin(TenantModel, [
|
|||||||
to: 'branches.id',
|
to: 'branches.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
media: {
|
media: {
|
||||||
relation: Model.ManyToManyRelation,
|
relation: Model.ManyToManyRelation,
|
||||||
modelClass: Media.default,
|
modelClass: Media.default,
|
||||||
|
|||||||
@@ -1,52 +1,51 @@
|
|||||||
export default {
|
export default {
|
||||||
importable: true,
|
|
||||||
defaultFilterField: 'name',
|
defaultFilterField: 'name',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
type: {
|
'type': {
|
||||||
name: 'item.field.type',
|
name: 'item.field.type',
|
||||||
column: 'type',
|
column: 'type',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'inventory', label: 'item.field.type.inventory' },
|
{ key: 'inventory', label: 'item.field.type.inventory', },
|
||||||
{ 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', },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
name: {
|
'name': {
|
||||||
name: 'item.field.name',
|
name: 'item.field.name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
code: {
|
'code': {
|
||||||
name: 'item.field.code',
|
name: 'item.field.code',
|
||||||
column: 'code',
|
column: 'code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
sellable: {
|
'sellable': {
|
||||||
name: 'item.field.sellable',
|
name: 'item.field.sellable',
|
||||||
column: 'sellable',
|
column: 'sellable',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
purchasable: {
|
'purchasable': {
|
||||||
name: 'item.field.purchasable',
|
name: 'item.field.purchasable',
|
||||||
column: 'purchasable',
|
column: 'purchasable',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
},
|
},
|
||||||
sell_price: {
|
'sell_price': {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.cost_price',
|
||||||
column: 'sell_price',
|
column: 'sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
cost_price: {
|
'cost_price': {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_account',
|
||||||
column: 'cost_price',
|
column: 'cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
cost_account: {
|
'cost_account': {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.sell_account',
|
||||||
column: 'cost_account_id',
|
column: 'cost_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -57,7 +56,7 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
sell_account: {
|
'sell_account': {
|
||||||
name: 'item.field.sell_description',
|
name: 'item.field.sell_description',
|
||||||
column: 'sell_account_id',
|
column: 'sell_account_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
@@ -68,7 +67,7 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
inventory_account: {
|
'inventory_account': {
|
||||||
name: 'item.field.inventory_account',
|
name: 'item.field.inventory_account',
|
||||||
column: 'inventory_account_id',
|
column: 'inventory_account_id',
|
||||||
|
|
||||||
@@ -78,27 +77,27 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
sell_description: {
|
'sell_description': {
|
||||||
name: 'Sell description',
|
name: 'Sell description',
|
||||||
column: 'sell_description',
|
column: 'sell_description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
purchase_description: {
|
'purchase_description': {
|
||||||
name: 'Purchase description',
|
name: 'Purchase description',
|
||||||
column: 'purchase_description',
|
column: 'purchase_description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
quantity_on_hand: {
|
'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',
|
||||||
},
|
},
|
||||||
note: {
|
'note': {
|
||||||
name: 'item.field.note',
|
name: 'item.field.note',
|
||||||
column: 'note',
|
column: 'note',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
category: {
|
'category': {
|
||||||
name: 'item.field.category',
|
name: 'item.field.category',
|
||||||
column: 'category_id',
|
column: 'category_id',
|
||||||
|
|
||||||
@@ -108,98 +107,17 @@ export default {
|
|||||||
relationEntityLabel: 'name',
|
relationEntityLabel: 'name',
|
||||||
relationEntityKey: 'id',
|
relationEntityKey: 'id',
|
||||||
},
|
},
|
||||||
active: {
|
'active': {
|
||||||
name: 'item.field.active',
|
name: 'item.field.active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
fieldType: 'boolean',
|
fieldType: 'boolean',
|
||||||
filterable: false,
|
filterable: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
'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,7 +4,6 @@ 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',
|
||||||
@@ -28,16 +27,4 @@ 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,130 +4,54 @@ 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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,6 +64,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,54 +1,33 @@
|
|||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import {
|
import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from '@/interfaces';
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the specific model meta.
|
* Retrieve 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.parsedMeta, key) : this.parsedMeta;
|
return key ? get(this.meta, key): this.meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
|
|
||||||
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',
|
||||||
@@ -57,65 +54,4 @@ 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,22 +4,18 @@ 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',
|
||||||
@@ -30,32 +26,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: [
|
||||||
@@ -67,90 +63,12 @@ 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,10 +4,6 @@ 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',
|
||||||
@@ -87,84 +83,6 @@ 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,17 +4,13 @@ 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',
|
||||||
@@ -25,7 +21,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',
|
||||||
@@ -36,37 +32,38 @@ 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: [
|
||||||
@@ -77,82 +74,6 @@ 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) {
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
export default {
|
|
||||||
defaultFilterField: 'createdAt',
|
|
||||||
defaultSort: {
|
|
||||||
sortOrder: 'DESC',
|
|
||||||
sortField: 'created_at',
|
|
||||||
},
|
|
||||||
importable: true,
|
|
||||||
fields: {
|
|
||||||
date: {
|
|
||||||
name: 'Date',
|
|
||||||
column: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
payee: {
|
|
||||||
name: 'Payee',
|
|
||||||
column: 'payee',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
name: 'Description',
|
|
||||||
column: 'description',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
referenceNo: {
|
|
||||||
name: 'Reference No.',
|
|
||||||
column: 'reference_no',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
amount: {
|
|
||||||
name: 'Amount',
|
|
||||||
column: 'Amount',
|
|
||||||
fieldType: 'numeric',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
account: {
|
|
||||||
name: 'Account',
|
|
||||||
column: 'account_id',
|
|
||||||
fieldType: 'relation',
|
|
||||||
to: { model: 'Account', to: 'id' },
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
name: 'Created At',
|
|
||||||
column: 'createdAt',
|
|
||||||
fieldType: 'date',
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/* eslint-disable global-require */
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import { Model, ModelOptions, QueryContext, mixin } from 'objection';
|
|
||||||
import TenantModel from 'models/TenantModel';
|
|
||||||
import ModelSettings from './ModelSetting';
|
|
||||||
import Account from './Account';
|
|
||||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
|
||||||
|
|
||||||
export default class UncategorizedCashflowTransaction extends mixin(
|
|
||||||
TenantModel,
|
|
||||||
[ModelSettings]
|
|
||||||
) {
|
|
||||||
id!: number;
|
|
||||||
amount!: number;
|
|
||||||
categorized!: boolean;
|
|
||||||
accountId!: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Table name.
|
|
||||||
*/
|
|
||||||
static get tableName() {
|
|
||||||
return 'uncategorized_cashflow_transactions';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamps columns.
|
|
||||||
*/
|
|
||||||
static get timestamps() {
|
|
||||||
return ['createdAt', 'updatedAt'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Virtual attributes.
|
|
||||||
*/
|
|
||||||
static get virtualAttributes() {
|
|
||||||
return [
|
|
||||||
'withdrawal',
|
|
||||||
'deposit',
|
|
||||||
'isDepositTransaction',
|
|
||||||
'isWithdrawalTransaction',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static get meta() {
|
|
||||||
return UncategorizedCashflowTransactionMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the withdrawal amount.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
public get withdrawal() {
|
|
||||||
return this.amount < 0 ? Math.abs(this.amount) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the deposit amount.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
public get deposit(): number {
|
|
||||||
return this.amount > 0 ? Math.abs(this.amount) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the transaction is deposit transaction.
|
|
||||||
*/
|
|
||||||
public get isDepositTransaction(): boolean {
|
|
||||||
return 0 < this.deposit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the transaction is withdrawal transaction.
|
|
||||||
*/
|
|
||||||
public get isWithdrawalTransaction(): boolean {
|
|
||||||
return 0 < this.withdrawal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relationship mapping.
|
|
||||||
*/
|
|
||||||
static get relationMappings() {
|
|
||||||
const Account = require('models/Account');
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Transaction may has associated to account.
|
|
||||||
*/
|
|
||||||
account: {
|
|
||||||
relation: Model.BelongsToOneRelation,
|
|
||||||
modelClass: Account.default,
|
|
||||||
join: {
|
|
||||||
from: 'uncategorized_cashflow_transactions.accountId',
|
|
||||||
to: 'accounts.id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the count of uncategorized transactions for the associated account
|
|
||||||
* based on the specified operation.
|
|
||||||
* @param {QueryContext} queryContext - The query context for the transaction.
|
|
||||||
* @param {boolean} increment - Indicates whether to increment or decrement the count.
|
|
||||||
*/
|
|
||||||
private async updateUncategorizedTransactionCount(
|
|
||||||
queryContext: QueryContext,
|
|
||||||
increment: boolean
|
|
||||||
) {
|
|
||||||
const operation = increment ? 'increment' : 'decrement';
|
|
||||||
const amount = increment ? 1 : -1;
|
|
||||||
|
|
||||||
await Account.query(queryContext.transaction)
|
|
||||||
.findById(this.accountId)
|
|
||||||
[operation]('uncategorized_transactions', amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs after insert.
|
|
||||||
* @param {QueryContext} queryContext
|
|
||||||
*/
|
|
||||||
public async $afterInsert(queryContext) {
|
|
||||||
await super.$afterInsert(queryContext);
|
|
||||||
await this.updateUncategorizedTransactionCount(queryContext, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs after update.
|
|
||||||
* @param {ModelOptions} opt
|
|
||||||
* @param {QueryContext} queryContext
|
|
||||||
*/
|
|
||||||
public async $afterUpdate(
|
|
||||||
opt: ModelOptions,
|
|
||||||
queryContext: QueryContext
|
|
||||||
): Promise<any> {
|
|
||||||
await super.$afterUpdate(opt, queryContext);
|
|
||||||
|
|
||||||
if (this.id && this.categorized) {
|
|
||||||
await this.updateUncategorizedTransactionCount(queryContext, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs after delete.
|
|
||||||
* @param {QueryContext} queryContext
|
|
||||||
*/
|
|
||||||
public async $afterDelete(queryContext: QueryContext) {
|
|
||||||
await super.$afterDelete(queryContext);
|
|
||||||
await this.updateUncategorizedTransactionCount(queryContext, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
defaultFilterField: 'displayName',
|
defaultFilterField: 'display_name',
|
||||||
defaultSort: {
|
defaultSort: {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
},
|
},
|
||||||
importable: true,
|
|
||||||
fields: {
|
fields: {
|
||||||
first_name: {
|
first_name: {
|
||||||
name: 'vendor.field.first_name',
|
name: 'vendor.field.first_name',
|
||||||
@@ -90,149 +89,4 @@ 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
|
|
||||||
billingAddress1: {
|
|
||||||
name: 'Billing Address 1',
|
|
||||||
column: 'billing_address1',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddress2: {
|
|
||||||
name: 'Billing Address 2',
|
|
||||||
column: 'billing_address2',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressCity: {
|
|
||||||
name: 'Billing Address City',
|
|
||||||
column: 'billing_address_city',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressCountry: {
|
|
||||||
name: 'Billing Address Country',
|
|
||||||
column: 'billing_address_country',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressPostcode: {
|
|
||||||
name: 'Billing Address Postcode',
|
|
||||||
column: 'billing_address_postcode',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressState: {
|
|
||||||
name: 'Billing Address State',
|
|
||||||
column: 'billing_address_state',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
billingAddressPhone: {
|
|
||||||
name: 'Billing Address Phone',
|
|
||||||
column: 'billing_address_phone',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
// Shipping Address
|
|
||||||
shippingAddress1: {
|
|
||||||
name: 'Shipping Address 1',
|
|
||||||
column: 'shipping_address1',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddress2: {
|
|
||||||
name: 'Shipping Address 2',
|
|
||||||
column: 'shipping_address2',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressCity: {
|
|
||||||
name: 'Shipping Address City',
|
|
||||||
column: 'shipping_address_city',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressCountry: {
|
|
||||||
name: 'Shipping Address Country',
|
|
||||||
column: 'shipping_address_country',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressPostcode: {
|
|
||||||
name: 'Shipping Address Postcode',
|
|
||||||
column: 'shipping_address_postcode',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressState: {
|
|
||||||
name: 'Shipping Address State',
|
|
||||||
column: 'shipping_address_state',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
shippingAddressPhone: {
|
|
||||||
name: 'Shipping Address Phone',
|
|
||||||
column: 'shipping_address_phone',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ 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',
|
||||||
@@ -76,69 +72,4 @@ 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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { ActivateAccount } from './ActivateAccount';
|
|||||||
import { GetAccounts } from './GetAccounts';
|
import { GetAccounts } from './GetAccounts';
|
||||||
import { GetAccount } from './GetAccount';
|
import { GetAccount } from './GetAccount';
|
||||||
import { GetAccountTransactions } from './GetAccountTransactions';
|
import { GetAccountTransactions } from './GetAccountTransactions';
|
||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AccountsApplication {
|
export class AccountsApplication {
|
||||||
@@ -49,10 +48,9 @@ export class AccountsApplication {
|
|||||||
*/
|
*/
|
||||||
public createAccount = (
|
public createAccount = (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
accountDTO: IAccountCreateDTO,
|
accountDTO: IAccountCreateDTO
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<IAccount> => {
|
): Promise<IAccount> => {
|
||||||
return this.createAccountService.createAccount(tenantId, accountDTO, trx);
|
return this.createAccountService.createAccount(tenantId, accountDTO);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
export const AccountsSampleData = [
|
|
||||||
{
|
|
||||||
'Account Name': 'Utilities Expense',
|
|
||||||
'Account Code': 9000,
|
|
||||||
Type: 'Expense',
|
|
||||||
Description: 'Omnis voluptatum consequatur.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Unearned Revenue',
|
|
||||||
'Account Code': 9010,
|
|
||||||
Type: 'Long Term Liability',
|
|
||||||
Description: 'Autem odit voluptas nihil unde.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Long-Term Debt',
|
|
||||||
'Account Code': 9020,
|
|
||||||
Type: 'Long Term Liability',
|
|
||||||
Description: 'In voluptas cumque exercitationem.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Salaries and Wages Expense',
|
|
||||||
'Account Code': 9030,
|
|
||||||
Type: 'Expense',
|
|
||||||
Description: 'Assumenda aspernatur soluta aliquid perspiciatis quasi.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Rental Income',
|
|
||||||
'Account Code': 9040,
|
|
||||||
Type: 'Income',
|
|
||||||
Description: 'Omnis possimus amet occaecati inventore.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Account Name': 'Paypal',
|
|
||||||
'Account Code': 9050,
|
|
||||||
Type: 'Bank',
|
|
||||||
Description: 'In voluptas cumque exercitationem.',
|
|
||||||
Active: 'T',
|
|
||||||
'Currency Code': '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { IAccountCreateDTO } from '@/interfaces';
|
|
||||||
import { CreateAccount } from './CreateAccount';
|
|
||||||
import { Importable } from '../Import/Importable';
|
|
||||||
import { AccountsSampleData } from './AccountsImportable.SampleData';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class AccountsImportable extends Importable {
|
|
||||||
@Inject()
|
|
||||||
private createAccountService: CreateAccount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Importing to account service.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IAccountCreateDTO} createAccountDTO
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public importable(
|
|
||||||
tenantId: number,
|
|
||||||
createAccountDTO: IAccountCreateDTO,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
return this.createAccountService.createAccount(
|
|
||||||
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 AccountsSampleData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -97,11 +97,9 @@ export class CommandAccountValidators {
|
|||||||
query.whereNot('id', notAccountId);
|
query.whereNot('id', notAccountId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (account.length > 0) {
|
if (account.length > 0) {
|
||||||
throw new ServiceError(
|
throw new ServiceError(ERRORS.ACCOUNT_CODE_NOT_UNIQUE);
|
||||||
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
|
|
||||||
'Account code is not unique.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,10 +124,7 @@ export class CommandAccountValidators {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (foundAccount) {
|
if (foundAccount) {
|
||||||
throw new ServiceError(
|
throw new ServiceError(ERRORS.ACCOUNT_NAME_NOT_UNIQUE);
|
||||||
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
|
|
||||||
'Account name is not unique.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,14 +97,13 @@ export class CreateAccount {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new account on the storage.
|
* Creates a new account on the storage.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
* @returns {Promise<IAccount>}
|
* @returns {Promise<IAccount>}
|
||||||
*/
|
*/
|
||||||
public createAccount = async (
|
public createAccount = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
accountDTO: IAccountCreateDTO,
|
accountDTO: IAccountCreateDTO
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<IAccount> => {
|
): Promise<IAccount> => {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -120,31 +119,27 @@ export class CreateAccount {
|
|||||||
tenantMeta.baseCurrency
|
tenantMeta.baseCurrency
|
||||||
);
|
);
|
||||||
// Creates a new account with associated transactions under unit-of-work envirement.
|
// Creates a new account with associated transactions under unit-of-work envirement.
|
||||||
return this.uow.withTransaction(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onAccountCreating` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.accounts.onCreating, {
|
||||||
// Triggers `onAccountCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.accounts.onCreating, {
|
accountDTO,
|
||||||
tenantId,
|
trx,
|
||||||
accountDTO,
|
} as IAccountEventCreatingPayload);
|
||||||
trx,
|
|
||||||
} as IAccountEventCreatingPayload);
|
|
||||||
|
|
||||||
// Inserts account to the storage.
|
// Inserts account to the storage.
|
||||||
const account = await Account.query(trx).insertAndFetch({
|
const account = await Account.query(trx).insertAndFetch({
|
||||||
...accountInputModel,
|
...accountInputModel,
|
||||||
});
|
});
|
||||||
// Triggers `onAccountCreated` event.
|
// Triggers `onAccountCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.accounts.onCreated, {
|
await this.eventPublisher.emitAsync(events.accounts.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
account,
|
account,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
trx,
|
trx,
|
||||||
} as IAccountEventCreatedPayload);
|
} as IAccountEventCreatedPayload);
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
},
|
});
|
||||||
trx
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import {
|
|||||||
transformPlaidAccountToCreateAccount,
|
transformPlaidAccountToCreateAccount,
|
||||||
transformPlaidTrxsToCashflowCreate,
|
transformPlaidTrxsToCashflowCreate,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
import NewCashflowTransactionService from '@/services/Cashflow/NewCashflowTransactionService';
|
||||||
|
import DeleteCashflowTransactionService from '@/services/Cashflow/DeleteCashflowTransactionService';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
|
||||||
|
|
||||||
const CONCURRENCY_ASYNC = 10;
|
const CONCURRENCY_ASYNC = 10;
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ export class PlaidSyncDb {
|
|||||||
private createAccountService: CreateAccount;
|
private createAccountService: CreateAccount;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private cashflowApp: CashflowApplication;
|
private createCashflowTransactionService: NewCashflowTransactionService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
private deleteCashflowTransactionService: DeleteCashflowTransactionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs the plaid accounts to the system accounts.
|
* Syncs the plaid accounts to the system accounts.
|
||||||
@@ -36,14 +36,11 @@ export class PlaidSyncDb {
|
|||||||
*/
|
*/
|
||||||
public async syncBankAccounts(
|
public async syncBankAccounts(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
plaidAccounts: PlaidAccount[],
|
plaidAccounts: PlaidAccount[]
|
||||||
institution: any
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const transformToPlaidAccounts =
|
const accountCreateDTOs = R.map(transformPlaidAccountToCreateAccount)(
|
||||||
transformPlaidAccountToCreateAccount(institution);
|
plaidAccounts
|
||||||
|
);
|
||||||
const accountCreateDTOs = R.map(transformToPlaidAccounts)(plaidAccounts);
|
|
||||||
|
|
||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
accountCreateDTOs,
|
accountCreateDTOs,
|
||||||
(createAccountDTO: any) =>
|
(createAccountDTO: any) =>
|
||||||
@@ -78,18 +75,17 @@ export class PlaidSyncDb {
|
|||||||
cashflowAccount.id,
|
cashflowAccount.id,
|
||||||
openingEquityBalance.id
|
openingEquityBalance.id
|
||||||
);
|
);
|
||||||
const uncategorizedTransDTOs =
|
const accountsCashflowDTO = R.map(transformTransaction)(plaidTranasctions);
|
||||||
R.map(transformTransaction)(plaidTranasctions);
|
|
||||||
|
|
||||||
// Creating account transaction queue.
|
// Creating account transaction queue.
|
||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
uncategorizedTransDTOs,
|
accountsCashflowDTO,
|
||||||
(uncategoriedDTO) =>
|
(cashflowDTO) =>
|
||||||
this.cashflowApp.createUncategorizedTransaction(
|
this.createCashflowTransactionService.newCashflowTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
uncategoriedDTO
|
cashflowDTO
|
||||||
),
|
),
|
||||||
{ concurrency: 1 }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,38 +157,4 @@ export class PlaidSyncDb {
|
|||||||
|
|
||||||
await PlaidItem.query().findOne({ plaidItemId }).patch({ lastCursor });
|
await PlaidItem.query().findOne({ plaidItemId }).patch({ lastCursor });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the last feeds updated at of the given Plaid accounts ids.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {string[]} plaidAccountIds
|
|
||||||
*/
|
|
||||||
public async updateLastFeedsUpdatedAt(
|
|
||||||
tenantId: number,
|
|
||||||
plaidAccountIds: string[]
|
|
||||||
) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
await Account.query().whereIn('plaid_account_id', plaidAccountIds).patch({
|
|
||||||
lastFeedsUpdatedAt: new Date(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the accounts feed active status of the given Plaid accounts ids.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number[]} plaidAccountIds
|
|
||||||
* @param {boolean} isFeedsActive
|
|
||||||
*/
|
|
||||||
public async updateAccountsFeedsActive(
|
|
||||||
tenantId: number,
|
|
||||||
plaidAccountIds: string[],
|
|
||||||
isFeedsActive: boolean = true
|
|
||||||
) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
await Account.query().whereIn('plaid_account_id', plaidAccountIds).patch({
|
|
||||||
isFeedsActive,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,19 +25,11 @@ export class PlaidUpdateTransactions {
|
|||||||
const request = { access_token: accessToken };
|
const request = { access_token: accessToken };
|
||||||
const plaidInstance = new PlaidClientWrapper();
|
const plaidInstance = new PlaidClientWrapper();
|
||||||
const {
|
const {
|
||||||
data: { accounts, item },
|
data: { accounts },
|
||||||
} = await plaidInstance.accountsGet(request);
|
} = await plaidInstance.accountsGet(request);
|
||||||
|
|
||||||
const plaidAccountsIds = accounts.map((a) => a.account_id);
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: { institution },
|
|
||||||
} = await plaidInstance.institutionsGetById({
|
|
||||||
institution_id: item.institution_id,
|
|
||||||
country_codes: ['US', 'UK'],
|
|
||||||
});
|
|
||||||
// Update the DB.
|
// Update the DB.
|
||||||
await this.plaidSync.syncBankAccounts(tenantId, accounts, institution);
|
await this.plaidSync.syncBankAccounts(tenantId, accounts);
|
||||||
await this.plaidSync.syncAccountsTransactions(
|
await this.plaidSync.syncAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
added.concat(modified)
|
added.concat(modified)
|
||||||
@@ -45,12 +37,6 @@ export class PlaidUpdateTransactions {
|
|||||||
await this.plaidSync.syncRemoveTransactions(tenantId, removed);
|
await this.plaidSync.syncRemoveTransactions(tenantId, removed);
|
||||||
await this.plaidSync.syncTransactionsCursor(tenantId, plaidItemId, cursor);
|
await this.plaidSync.syncTransactionsCursor(tenantId, plaidItemId, cursor);
|
||||||
|
|
||||||
// Update the last feeds updated at of the updated accounts.
|
|
||||||
await this.plaidSync.updateLastFeedsUpdatedAt(tenantId, plaidAccountsIds);
|
|
||||||
|
|
||||||
// Turn on the accounts feeds flag.
|
|
||||||
await this.plaidSync.updateAccountsFeedsActive(tenantId, plaidAccountsIds);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addedCount: added.length,
|
addedCount: added.length,
|
||||||
modifiedCount: modified.length,
|
modifiedCount: modified.length,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
CreateUncategorizedTransactionDTO,
|
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
|
ICashflowNewCommandDTO,
|
||||||
PlaidAccount,
|
PlaidAccount,
|
||||||
PlaidTransaction,
|
PlaidTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
@@ -11,44 +11,51 @@ import {
|
|||||||
* @param {PlaidAccount} plaidAccount
|
* @param {PlaidAccount} plaidAccount
|
||||||
* @returns {IAccountCreateDTO}
|
* @returns {IAccountCreateDTO}
|
||||||
*/
|
*/
|
||||||
export const transformPlaidAccountToCreateAccount = R.curry(
|
export const transformPlaidAccountToCreateAccount = (
|
||||||
(institution: any, plaidAccount: PlaidAccount): IAccountCreateDTO => {
|
plaidAccount: PlaidAccount
|
||||||
return {
|
): IAccountCreateDTO => {
|
||||||
name: `${institution.name} - ${plaidAccount.name}`,
|
return {
|
||||||
code: '',
|
name: plaidAccount.name,
|
||||||
description: plaidAccount.official_name,
|
code: '',
|
||||||
currencyCode: plaidAccount.balances.iso_currency_code,
|
description: plaidAccount.official_name,
|
||||||
accountType: 'cash',
|
currencyCode: plaidAccount.balances.iso_currency_code,
|
||||||
active: true,
|
accountType: 'cash',
|
||||||
plaidAccountId: plaidAccount.account_id,
|
active: true,
|
||||||
bankBalance: plaidAccount.balances.current,
|
plaidAccountId: plaidAccount.account_id,
|
||||||
accountMask: plaidAccount.mask,
|
bankBalance: plaidAccount.balances.current,
|
||||||
};
|
accountMask: plaidAccount.mask,
|
||||||
}
|
};
|
||||||
);
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the plaid transaction to cashflow create DTO.
|
* Transformes the plaid transaction to cashflow create DTO.
|
||||||
* @param {number} cashflowAccountId - Cashflow account ID.
|
* @param {number} cashflowAccountId - Cashflow account ID.
|
||||||
* @param {number} creditAccountId - Credit account ID.
|
* @param {number} creditAccountId - Credit account ID.
|
||||||
* @param {PlaidTransaction} plaidTranasction - Plaid transaction.
|
* @param {PlaidTransaction} plaidTranasction - Plaid transaction.
|
||||||
* @returns {CreateUncategorizedTransactionDTO}
|
* @returns {ICashflowNewCommandDTO}
|
||||||
*/
|
*/
|
||||||
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
||||||
(
|
(
|
||||||
cashflowAccountId: number,
|
cashflowAccountId: number,
|
||||||
creditAccountId: number,
|
creditAccountId: number,
|
||||||
plaidTranasction: PlaidTransaction
|
plaidTranasction: PlaidTransaction
|
||||||
): CreateUncategorizedTransactionDTO => {
|
): ICashflowNewCommandDTO => {
|
||||||
return {
|
return {
|
||||||
date: plaidTranasction.date,
|
date: plaidTranasction.date,
|
||||||
amount: plaidTranasction.amount,
|
|
||||||
|
transactionType: 'OwnerContribution',
|
||||||
description: plaidTranasction.name,
|
description: plaidTranasction.name,
|
||||||
payee: plaidTranasction.payment_meta?.payee,
|
|
||||||
|
amount: plaidTranasction.amount,
|
||||||
|
exchangeRate: 1,
|
||||||
currencyCode: plaidTranasction.iso_currency_code,
|
currencyCode: plaidTranasction.iso_currency_code,
|
||||||
accountId: cashflowAccountId,
|
creditAccountId,
|
||||||
referenceNo: plaidTranasction.payment_meta?.reference_number,
|
cashflowAccountId,
|
||||||
|
|
||||||
|
// transactionNumber: string;
|
||||||
|
// referenceNo: string;
|
||||||
plaidTransactionId: plaidTranasction.transaction_id,
|
plaidTransactionId: plaidTranasction.transaction_id,
|
||||||
|
publish: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,213 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { DeleteCashflowTransaction } from './DeleteCashflowTransactionService';
|
|
||||||
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
|
|
||||||
import { CategorizeCashflowTransaction } from './CategorizeCashflowTransaction';
|
|
||||||
import {
|
|
||||||
CategorizeTransactionAsExpenseDTO,
|
|
||||||
CreateUncategorizedTransactionDTO,
|
|
||||||
ICashflowAccountsFilter,
|
|
||||||
ICashflowNewCommandDTO,
|
|
||||||
ICategorizeCashflowTransactioDTO,
|
|
||||||
IGetUncategorizedTransactionsQuery,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { CategorizeTransactionAsExpense } from './CategorizeTransactionAsExpense';
|
|
||||||
import { GetUncategorizedTransactions } from './GetUncategorizedTransactions';
|
|
||||||
import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction';
|
|
||||||
import { GetUncategorizedTransaction } from './GetUncategorizedTransaction';
|
|
||||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
|
||||||
import GetCashflowAccountsService from './GetCashflowAccountsService';
|
|
||||||
import { GetCashflowTransactionService } from './GetCashflowTransactionsService';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class CashflowApplication {
|
|
||||||
@Inject()
|
|
||||||
private createTransactionService: NewCashflowTransactionService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private deleteTransactionService: DeleteCashflowTransaction;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private getCashflowAccountsService: GetCashflowAccountsService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private getCashflowTransactionService: GetCashflowTransactionService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uncategorizeTransactionService: UncategorizeCashflowTransaction;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private categorizeTransactionService: CategorizeCashflowTransaction;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private categorizeAsExpenseService: CategorizeTransactionAsExpense;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private getUncategorizedTransactionsService: GetUncategorizedTransactions;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private getUncategorizedTransactionService: GetUncategorizedTransaction;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private createUncategorizedTransactionService: CreateUncategorizedTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ICashflowNewCommandDTO} transactionDTO
|
|
||||||
* @param {number} userId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public createTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
transactionDTO: ICashflowNewCommandDTO,
|
|
||||||
userId?: number
|
|
||||||
) {
|
|
||||||
return this.createTransactionService.newCashflowTransaction(
|
|
||||||
tenantId,
|
|
||||||
transactionDTO,
|
|
||||||
userId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the given cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public deleteTransaction(tenantId: number, cashflowTransactionId: number) {
|
|
||||||
return this.deleteTransactionService.deleteCashflowTransaction(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves specific cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public getTransaction(tenantId: number, cashflowTransactionId: number) {
|
|
||||||
return this.getCashflowTransactionService.getCashflowTransaction(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow accounts.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ICashflowAccountsFilter} filterDTO
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public getCashflowAccounts(
|
|
||||||
tenantId: number,
|
|
||||||
filterDTO: ICashflowAccountsFilter
|
|
||||||
) {
|
|
||||||
return this.getCashflowAccountsService.getCashflowAccounts(
|
|
||||||
tenantId,
|
|
||||||
filterDTO
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new uncategorized cash transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {CreateUncategorizedTransactionDTO} createUncategorizedTransactionDTO
|
|
||||||
* @returns {IUncategorizedCashflowTransaction}
|
|
||||||
*/
|
|
||||||
public createUncategorizedTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO
|
|
||||||
) {
|
|
||||||
return this.createUncategorizedTransactionService.create(
|
|
||||||
tenantId,
|
|
||||||
createUncategorizedTransactionDTO
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uncategorize the given cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public uncategorizeTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
cashflowTransactionId: number
|
|
||||||
) {
|
|
||||||
return this.uncategorizeTransactionService.uncategorize(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize the given cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public categorizeTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
cashflowTransactionId: number,
|
|
||||||
categorizeDTO: ICategorizeCashflowTransactioDTO
|
|
||||||
) {
|
|
||||||
return this.categorizeTransactionService.categorize(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId,
|
|
||||||
categorizeDTO
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorizes the given cashflow transaction as expense transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
* @param {CategorizeTransactionAsExpenseDTO} transactionDTO
|
|
||||||
*/
|
|
||||||
public categorizeAsExpense(
|
|
||||||
tenantId: number,
|
|
||||||
cashflowTransactionId: number,
|
|
||||||
transactionDTO: CategorizeTransactionAsExpenseDTO
|
|
||||||
) {
|
|
||||||
return this.categorizeAsExpenseService.categorize(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionId,
|
|
||||||
transactionDTO
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the uncategorized cashflow transactions.
|
|
||||||
* @param {number} tenantId
|
|
||||||
*/
|
|
||||||
public getUncategorizedTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
accountId: number,
|
|
||||||
query: IGetUncategorizedTransactionsQuery
|
|
||||||
) {
|
|
||||||
return this.getUncategorizedTransactionsService.getTransactions(
|
|
||||||
tenantId,
|
|
||||||
accountId,
|
|
||||||
query
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves specific uncategorized transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} uncategorizedTransactionId
|
|
||||||
*/
|
|
||||||
public getUncategorizedTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
uncategorizedTransactionId: number
|
|
||||||
) {
|
|
||||||
return this.getUncategorizedTransactionService.getTransaction(
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import { ERRORS } from './constants';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class CashflowDeleteAccount {
|
export default class CashflowDeleteAccount {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the account has no associated cashflow transactions.
|
* Validate the account has no associated cashflow transactions.
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
ILedgerEntry,
|
ILedgerEntry,
|
||||||
ICashflowTransaction,
|
ICashflowTransaction,
|
||||||
AccountNormal,
|
AccountNormal,
|
||||||
|
ICashflowTransactionLine,
|
||||||
} from '../../interfaces';
|
} from '../../interfaces';
|
||||||
import {
|
import {
|
||||||
transformCashflowTransactionType,
|
transformCashflowTransactionType,
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import UnitOfWork from '../UnitOfWork';
|
|
||||||
import {
|
|
||||||
ICashflowTransactionCategorizedPayload,
|
|
||||||
ICashflowTransactionUncategorizingPayload,
|
|
||||||
ICategorizeCashflowTransactioDTO,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { transformCategorizeTransToCashflow } from './utils';
|
|
||||||
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
|
||||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
|
||||||
import { TransferAuthorizationGuaranteeDecision } from 'plaid';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class CategorizeCashflowTransaction {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private commandValidators: CommandCashflowValidator;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private createCashflow: NewCashflowTransactionService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize the given cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
|
||||||
*/
|
|
||||||
public async categorize(
|
|
||||||
tenantId: number,
|
|
||||||
uncategorizedTransactionId: number,
|
|
||||||
categorizeDTO: ICategorizeCashflowTransactioDTO
|
|
||||||
) {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retrieves the uncategorized transaction or throw an error.
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query()
|
|
||||||
.findById(uncategorizedTransactionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Validates the transaction shouldn't be categorized before.
|
|
||||||
this.commandValidators.validateTransactionShouldNotCategorized(transaction);
|
|
||||||
|
|
||||||
// Validate the uncateogirzed transaction if it's deposit the transaction direction
|
|
||||||
// should `IN` and the same thing if it's withdrawal the direction should be OUT.
|
|
||||||
this.commandValidators.validateUncategorizeTransactionType(
|
|
||||||
transaction,
|
|
||||||
categorizeDTO.transactionType
|
|
||||||
);
|
|
||||||
// Edits the cashflow transaction under UOW env.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onTransactionCategorizing` event.
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionCategorizing,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
trx,
|
|
||||||
} as ICashflowTransactionUncategorizingPayload
|
|
||||||
);
|
|
||||||
// Transformes the categorize DTO to the cashflow transaction.
|
|
||||||
const cashflowTransactionDTO = transformCategorizeTransToCashflow(
|
|
||||||
transaction,
|
|
||||||
categorizeDTO
|
|
||||||
);
|
|
||||||
// Creates a new cashflow transaction.
|
|
||||||
const cashflowTransaction =
|
|
||||||
await this.createCashflow.newCashflowTransaction(
|
|
||||||
tenantId,
|
|
||||||
cashflowTransactionDTO
|
|
||||||
);
|
|
||||||
// Updates the uncategorized transaction as categorized.
|
|
||||||
await UncategorizedCashflowTransaction.query(trx).patchAndFetchById(
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
{
|
|
||||||
categorized: true,
|
|
||||||
categorizeRefType: 'CashflowTransaction',
|
|
||||||
categorizeRefId: cashflowTransaction.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Triggers `onCashflowTransactionCategorized` event.
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionCategorized,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
// cashflowTransaction,
|
|
||||||
trx,
|
|
||||||
} as ICashflowTransactionCategorizedPayload
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import {
|
|
||||||
CategorizeTransactionAsExpenseDTO,
|
|
||||||
ICashflowTransactionCategorizedPayload,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import UnitOfWork from '../UnitOfWork';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { CreateExpense } from '../Expenses/CRUD/CreateExpense';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class CategorizeTransactionAsExpense {
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private createExpenseService: CreateExpense;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize the transaction as expense transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
* @param {CategorizeTransactionAsExpenseDTO} transactionDTO
|
|
||||||
*/
|
|
||||||
public async categorize(
|
|
||||||
tenantId: number,
|
|
||||||
cashflowTransactionId: number,
|
|
||||||
transactionDTO: CategorizeTransactionAsExpenseDTO
|
|
||||||
) {
|
|
||||||
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const transaction = await CashflowTransaction.query()
|
|
||||||
.findById(cashflowTransactionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onTransactionUncategorizing` event.
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionCategorizingAsExpense,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
trx,
|
|
||||||
} as ICashflowTransactionCategorizedPayload
|
|
||||||
);
|
|
||||||
// Creates a new expense transaction.
|
|
||||||
const expenseTransaction = await this.createExpenseService.newExpense(
|
|
||||||
tenantId,
|
|
||||||
{
|
|
||||||
|
|
||||||
},
|
|
||||||
1
|
|
||||||
);
|
|
||||||
// Updates the item on the storage and fetches the updated once.
|
|
||||||
const cashflowTransaction = await CashflowTransaction.query(
|
|
||||||
trx
|
|
||||||
).patchAndFetchById(cashflowTransactionId, {
|
|
||||||
categorizeRefType: 'Expense',
|
|
||||||
categorizeRefId: expenseTransaction.id,
|
|
||||||
uncategorized: true,
|
|
||||||
});
|
|
||||||
// Triggers `onTransactionUncategorized` event.
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionCategorizedAsExpense,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
cashflowTransaction,
|
|
||||||
trx,
|
|
||||||
} as ICashflowTransactionUncategorizedPayload
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import { includes, camelCase, upperFirst } from 'lodash';
|
import { includes, camelCase, upperFirst } from 'lodash';
|
||||||
import { IAccount, IUncategorizedCashflowTransaction } from '@/interfaces';
|
import { IAccount } from '@/interfaces';
|
||||||
import { getCashflowTransactionType } from './utils';
|
import { getCashflowTransactionType } from './utils';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import {
|
import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants';
|
||||||
CASHFLOW_DIRECTION,
|
|
||||||
CASHFLOW_TRANSACTION_TYPE,
|
|
||||||
ERRORS,
|
|
||||||
} from './constants';
|
|
||||||
import CashflowTransaction from '@/models/CashflowTransaction';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CommandCashflowValidator {
|
export class CommandCashflowValidator {
|
||||||
@@ -51,52 +46,4 @@ export class CommandCashflowValidator {
|
|||||||
}
|
}
|
||||||
return transformedType;
|
return transformedType;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given transaction should be categorized.
|
|
||||||
* @param {CashflowTransaction} cashflowTransaction
|
|
||||||
*/
|
|
||||||
public validateTransactionShouldCategorized(
|
|
||||||
cashflowTransaction: CashflowTransaction
|
|
||||||
) {
|
|
||||||
if (!cashflowTransaction.uncategorize) {
|
|
||||||
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_CATEGORIZED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given transcation shouldn't be categorized.
|
|
||||||
* @param {CashflowTransaction} cashflowTransaction
|
|
||||||
*/
|
|
||||||
public validateTransactionShouldNotCategorized(
|
|
||||||
cashflowTransaction: CashflowTransaction
|
|
||||||
) {
|
|
||||||
if (cashflowTransaction.uncategorize) {
|
|
||||||
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_CATEGORIZED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {uncategorizeTransaction}
|
|
||||||
* @param {string} transactionType
|
|
||||||
* @throws {ServiceError(ERRORS.UNCATEGORIZED_TRANSACTION_TYPE_INVALID)}
|
|
||||||
*/
|
|
||||||
public validateUncategorizeTransactionType(
|
|
||||||
uncategorizeTransaction: IUncategorizedCashflowTransaction,
|
|
||||||
transactionType: string
|
|
||||||
) {
|
|
||||||
const type = getCashflowTransactionType(
|
|
||||||
upperFirst(camelCase(transactionType)) as CASHFLOW_TRANSACTION_TYPE
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
(type.direction === CASHFLOW_DIRECTION.IN &&
|
|
||||||
uncategorizeTransaction.isDepositTransaction) ||
|
|
||||||
(type.direction === CASHFLOW_DIRECTION.OUT &&
|
|
||||||
uncategorizeTransaction.isWithdrawalTransaction)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new ServiceError(ERRORS.UNCATEGORIZED_TRANSACTION_TYPE_INVALID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import UnitOfWork from '../UnitOfWork';
|
|
||||||
import { CreateUncategorizedTransactionDTO } from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class CreateUncategorizedTransaction {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an uncategorized cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {CreateUncategorizedTransactionDTO} createDTO
|
|
||||||
*/
|
|
||||||
public create(
|
|
||||||
tenantId: number,
|
|
||||||
createDTO: CreateUncategorizedTransactionDTO,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
return this.uow.withTransaction(
|
|
||||||
tenantId,
|
|
||||||
async (trx: Knex.Transaction) => {
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query(
|
|
||||||
trx
|
|
||||||
).insertAndFetch({
|
|
||||||
...createDTO,
|
|
||||||
});
|
|
||||||
return transaction;
|
|
||||||
},
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,15 +13,15 @@ import UnitOfWork from '@/services/UnitOfWork';
|
|||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class DeleteCashflowTransaction {
|
export default class CommandCashflowTransactionService {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private eventPublisher: EventPublisher;
|
eventPublisher: EventPublisher;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private uow: UnitOfWork;
|
uow: UnitOfWork;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the cashflow transaction with associated journal entries.
|
* Deletes the cashflow transaction with associated journal entries.
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import { CashflowTransactionTransformer } from './CashflowTransactionTransformer
|
|||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
import { ICashflowTransaction } from '@/interfaces';
|
import { ICashflowTransaction } from '@/interfaces';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import I18nService from '@/services/I18n/I18nService';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetCashflowTransactionService {
|
export default class GetCashflowTransactionsService {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private i18nService: I18nService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private transfromer: TransformerInjectable;
|
private transfromer: TransformerInjectable;
|
||||||
|
|
||||||
@@ -31,7 +35,6 @@ export class GetCashflowTransactionService {
|
|||||||
.withGraphFetched('entries.cashflowAccount')
|
.withGraphFetched('entries.cashflowAccount')
|
||||||
.withGraphFetched('entries.creditAccount')
|
.withGraphFetched('entries.creditAccount')
|
||||||
.withGraphFetched('transactions.account')
|
.withGraphFetched('transactions.account')
|
||||||
.orderBy('date', 'DESC')
|
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);
|
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
||||||
import { UncategorizedTransactionTransformer } from './UncategorizedTransactionTransformer';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class GetUncategorizedTransaction {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private transformer: TransformerInjectable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves specific uncategorized cashflow transaction.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {number} uncategorizedTransactionId - Uncategorized transaction id.
|
|
||||||
*/
|
|
||||||
public async getTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
uncategorizedTransactionId: number
|
|
||||||
) {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const transaction = await UncategorizedCashflowTransaction.query()
|
|
||||||
.findById(uncategorizedTransactionId)
|
|
||||||
.withGraphFetched('account')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
return this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
transaction,
|
|
||||||
new UncategorizedTransactionTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
||||||
import { UncategorizedTransactionTransformer } from './UncategorizedTransactionTransformer';
|
|
||||||
import { IGetUncategorizedTransactionsQuery } from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class GetUncategorizedTransactions {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private transformer: TransformerInjectable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the uncategorized cashflow transactions.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {number} accountId - Account Id.
|
|
||||||
*/
|
|
||||||
public async getTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
accountId: number,
|
|
||||||
query: IGetUncategorizedTransactionsQuery
|
|
||||||
) {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Parsed query with default values.
|
|
||||||
const _query = {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
...query,
|
|
||||||
};
|
|
||||||
const { results, pagination } =
|
|
||||||
await UncategorizedCashflowTransaction.query()
|
|
||||||
.where('accountId', accountId)
|
|
||||||
.where('categorized', false)
|
|
||||||
.withGraphFetched('account')
|
|
||||||
.orderBy('date', 'DESC')
|
|
||||||
.pagination(_query.page - 1, _query.pageSize);
|
|
||||||
|
|
||||||
const data = await this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
results,
|
|
||||||
new UncategorizedTransactionTransformer()
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
pagination,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { pick } from 'lodash';
|
import { isEmpty, pick } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
ICashflowNewCommandDTO,
|
ICashflowNewCommandDTO,
|
||||||
ICashflowTransaction,
|
ICashflowTransaction,
|
||||||
|
ICashflowTransactionLine,
|
||||||
ICommandCashflowCreatedPayload,
|
ICommandCashflowCreatedPayload,
|
||||||
ICommandCashflowCreatingPayload,
|
ICommandCashflowCreatingPayload,
|
||||||
ICashflowTransactionInput,
|
ICashflowTransactionInput,
|
||||||
@@ -86,7 +87,6 @@ export default class NewCashflowTransactionService {
|
|||||||
'creditAccountId',
|
'creditAccountId',
|
||||||
'branchId',
|
'branchId',
|
||||||
'plaidTransactionId',
|
'plaidTransactionId',
|
||||||
'uncategorizedTransactionId',
|
|
||||||
]);
|
]);
|
||||||
// Retreive the next invoice number.
|
// Retreive the next invoice number.
|
||||||
const autoNextNumber =
|
const autoNextNumber =
|
||||||
@@ -126,7 +126,7 @@ export default class NewCashflowTransactionService {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
newTransactionDTO: ICashflowNewCommandDTO,
|
newTransactionDTO: ICashflowNewCommandDTO,
|
||||||
userId?: number
|
userId?: number
|
||||||
): Promise<ICashflowTransaction> => {
|
): Promise<{ cashflowTransaction: ICashflowTransaction }> => {
|
||||||
const { CashflowTransaction, Account } = this.tenancy.models(tenantId);
|
const { CashflowTransaction, Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Retrieves the cashflow account or throw not found error.
|
// Retrieves the cashflow account or throw not found error.
|
||||||
@@ -175,7 +175,7 @@ export default class NewCashflowTransactionService {
|
|||||||
trx,
|
trx,
|
||||||
} as ICommandCashflowCreatedPayload
|
} as ICommandCashflowCreatedPayload
|
||||||
);
|
);
|
||||||
return cashflowTransaction;
|
return { cashflowTransaction };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import UnitOfWork from '../UnitOfWork';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import {
|
|
||||||
ICashflowTransactionUncategorizedPayload,
|
|
||||||
ICashflowTransactionUncategorizingPayload,
|
|
||||||
} from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class UncategorizeCashflowTransaction {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uncategorizes the given cashflow transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} cashflowTransactionId
|
|
||||||
*/
|
|
||||||
public async uncategorize(
|
|
||||||
tenantId: number,
|
|
||||||
uncategorizedTransactionId: number
|
|
||||||
) {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const oldUncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query()
|
|
||||||
.findById(uncategorizedTransactionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Updates the transaction under UOW.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onTransactionUncategorizing` event.
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionUncategorizing,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
trx,
|
|
||||||
} as ICashflowTransactionUncategorizingPayload
|
|
||||||
);
|
|
||||||
// Removes the ref relation with the related transaction.
|
|
||||||
const uncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query(trx).updateAndFetchById(
|
|
||||||
uncategorizedTransactionId,
|
|
||||||
{
|
|
||||||
categorized: false,
|
|
||||||
categorizeRefId: null,
|
|
||||||
categorizeRefType: null,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Triggers `onTransactionUncategorized` event.
|
|
||||||
await this.eventPublisher.emitAsync(
|
|
||||||
events.cashflow.onTransactionUncategorized,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransaction,
|
|
||||||
oldUncategorizedTransaction,
|
|
||||||
trx,
|
|
||||||
} as ICashflowTransactionUncategorizedPayload
|
|
||||||
);
|
|
||||||
return uncategorizedTransaction;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
|
||||||
import { formatNumber } from '@/utils';
|
|
||||||
|
|
||||||
export class UncategorizedTransactionTransformer extends Transformer {
|
|
||||||
/**
|
|
||||||
* Include these attributes to sale invoice object.
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
public includeAttributes = (): string[] => {
|
|
||||||
return [
|
|
||||||
'formattedAmount',
|
|
||||||
'formattedDate',
|
|
||||||
'formattetDepositAmount',
|
|
||||||
'formattedWithdrawalAmount',
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formattes the transaction date.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public formattedDate(transaction) {
|
|
||||||
return this.formatDate(transaction.date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted amount.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public formattedAmount(transaction) {
|
|
||||||
return formatNumber(transaction.amount, {
|
|
||||||
currencyCode: transaction.currencyCode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted deposit amount.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattetDepositAmount(transaction) {
|
|
||||||
if (transaction.isDepositTransaction) {
|
|
||||||
return formatNumber(transaction.deposit, {
|
|
||||||
currencyCode: transaction.currencyCode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted withdrawal amount.
|
|
||||||
* @param transaction
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedWithdrawalAmount(transaction) {
|
|
||||||
if (transaction.isWithdrawalTransaction) {
|
|
||||||
return formatNumber(transaction.withdrawal, {
|
|
||||||
currencyCode: transaction.currencyCode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import * as yup from 'yup';
|
|
||||||
import { Importable } from '../Import/Importable';
|
|
||||||
import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction';
|
|
||||||
import { CreateUncategorizedTransactionDTO } from '@/interfaces';
|
|
||||||
import { ImportableContext } from '../Import/interfaces';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { BankTransactionsSampleData } from './constants';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class UncategorizedTransactionsImportable extends Importable {
|
|
||||||
@Inject()
|
|
||||||
private createUncategorizedTransaction: CreateUncategorizedTransaction;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
/**
|
|
||||||
* Passing the sheet DTO to create uncategorized transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {any} createDTO
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
*/
|
|
||||||
public async importable(
|
|
||||||
tenantId: number,
|
|
||||||
createDTO: CreateUncategorizedTransactionDTO,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
return this.createUncategorizedTransaction.create(tenantId, createDTO, trx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformes the DTO before validating and importing.
|
|
||||||
* @param {CreateUncategorizedTransactionDTO} createDTO
|
|
||||||
* @param {ImportableContext} context
|
|
||||||
* @returns {CreateUncategorizedTransactionDTO}
|
|
||||||
*/
|
|
||||||
public transform(
|
|
||||||
createDTO: CreateUncategorizedTransactionDTO,
|
|
||||||
context?: ImportableContext
|
|
||||||
): CreateUncategorizedTransactionDTO {
|
|
||||||
return {
|
|
||||||
...createDTO,
|
|
||||||
accountId: context.import.paramsParsed.accountId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sample data used to download sample sheet.
|
|
||||||
* @returns {Record<string, any>[]}
|
|
||||||
*/
|
|
||||||
public sampleData(): Record<string, any>[] {
|
|
||||||
return BankTransactionsSampleData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params validation schema.
|
|
||||||
* @returns {ValidationSchema[]}
|
|
||||||
*/
|
|
||||||
public paramsValidationSchema() {
|
|
||||||
return yup.object().shape({
|
|
||||||
accountId: yup.number().required(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the params existance asyncly.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {Record<string, any>} params -
|
|
||||||
*/
|
|
||||||
public async validateParams(
|
|
||||||
tenantId: number,
|
|
||||||
params: Record<string, any>
|
|
||||||
): Promise<void> {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
if (params.accountId) {
|
|
||||||
await Account.query().findById(params.accountId).throwIfNotFound({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,13 +8,7 @@ export const ERRORS = {
|
|||||||
CREDIT_ACCOUNTS_IDS_NOT_FOUND: 'CREDIT_ACCOUNTS_IDS_NOT_FOUND',
|
CREDIT_ACCOUNTS_IDS_NOT_FOUND: 'CREDIT_ACCOUNTS_IDS_NOT_FOUND',
|
||||||
CREDIT_ACCOUNTS_HAS_INVALID_TYPE: 'CREDIT_ACCOUNTS_HAS_INVALID_TYPE',
|
CREDIT_ACCOUNTS_HAS_INVALID_TYPE: 'CREDIT_ACCOUNTS_HAS_INVALID_TYPE',
|
||||||
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
|
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
|
||||||
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions'
|
||||||
TRANSACTION_ALREADY_CATEGORIZED: 'TRANSACTION_ALREADY_CATEGORIZED',
|
|
||||||
TRANSACTION_ALREADY_UNCATEGORIZED: 'TRANSACTION_ALREADY_UNCATEGORIZED',
|
|
||||||
UNCATEGORIZED_TRANSACTION_TYPE_INVALID:
|
|
||||||
'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
|
|
||||||
CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED:
|
|
||||||
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum CASHFLOW_DIRECTION {
|
export enum CASHFLOW_DIRECTION {
|
||||||
@@ -77,27 +71,3 @@ export interface ICashflowTransactionTypeMeta {
|
|||||||
direction: CASHFLOW_DIRECTION;
|
direction: CASHFLOW_DIRECTION;
|
||||||
creditType: string[];
|
creditType: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BankTransactionsSampleData = [
|
|
||||||
{
|
|
||||||
Amount: '6,410.19',
|
|
||||||
Date: '2024-03-26',
|
|
||||||
Payee: 'MacGyver and Sons',
|
|
||||||
'Reference No.': 'REF-1',
|
|
||||||
Description: 'Commodi quo labore.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Amount: '8,914.17',
|
|
||||||
Date: '2024-01-05',
|
|
||||||
Payee: 'Eichmann - Bergnaum',
|
|
||||||
'Reference No.': 'REF-1',
|
|
||||||
Description: 'Quia enim et.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Amount: '6,200.88',
|
|
||||||
Date: '2024-02-17',
|
|
||||||
Payee: 'Luettgen, Mraz and Legros',
|
|
||||||
'Reference No.': 'REF-1',
|
|
||||||
Description: 'Occaecati consequuntur cum impedit illo.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { ICashflowTransactionUncategorizedPayload } from '@/interfaces';
|
|
||||||
import { DeleteCashflowTransaction } from '../DeleteCashflowTransactionService';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DeleteCashflowTransactionOnUncategorize {
|
|
||||||
@Inject()
|
|
||||||
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches events with handlers.
|
|
||||||
*/
|
|
||||||
public attach = (bus) => {
|
|
||||||
bus.subscribe(
|
|
||||||
events.cashflow.onTransactionUncategorized,
|
|
||||||
this.deleteCashflowTransactionOnUncategorize.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the cashflow transaction on uncategorize transaction.
|
|
||||||
* @param {ICashflowTransactionUncategorizedPayload} payload
|
|
||||||
*/
|
|
||||||
public async deleteCashflowTransactionOnUncategorize({
|
|
||||||
tenantId,
|
|
||||||
oldUncategorizedTransaction,
|
|
||||||
trx,
|
|
||||||
}: ICashflowTransactionUncategorizedPayload) {
|
|
||||||
// Deletes the cashflow transaction.
|
|
||||||
if (
|
|
||||||
oldUncategorizedTransaction.categorizeRefType === 'CashflowTransaction'
|
|
||||||
) {
|
|
||||||
await this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
|
||||||
tenantId,
|
|
||||||
|
|
||||||
oldUncategorizedTransaction.categorizeRefId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { ICommandCashflowDeletingPayload } from '@/interfaces';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { ERRORS } from '../constants';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class PreventDeleteTransactionOnDelete {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches events with handlers.
|
|
||||||
*/
|
|
||||||
public attach = (bus) => {
|
|
||||||
bus.subscribe(
|
|
||||||
events.cashflow.onTransactionDeleting,
|
|
||||||
this.preventDeleteCashflowTransactionHasUncategorizedTransaction.bind(
|
|
||||||
this
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent delete cashflow transaction has converted from uncategorized transaction.
|
|
||||||
* @param {ICommandCashflowDeletingPayload} payload
|
|
||||||
*/
|
|
||||||
public async preventDeleteCashflowTransactionHasUncategorizedTransaction({
|
|
||||||
tenantId,
|
|
||||||
oldCashflowTransaction,
|
|
||||||
trx,
|
|
||||||
}: ICommandCashflowDeletingPayload) {
|
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
|
||||||
if (oldCashflowTransaction.uncategorizedTransactionId) {
|
|
||||||
const foundTransactions = await UncategorizedCashflowTransaction.query(
|
|
||||||
trx
|
|
||||||
).where({
|
|
||||||
categorized: true,
|
|
||||||
categorizeRefId: oldCashflowTransaction.id,
|
|
||||||
categorizeRefType: 'CashflowTransaction',
|
|
||||||
});
|
|
||||||
// Throw the error if the cashflow transaction still linked to uncategorized transaction.
|
|
||||||
if (foundTransactions.length > 0) {
|
|
||||||
throw new ServiceError(
|
|
||||||
ERRORS.CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED,
|
|
||||||
'Cannot delete cashflow transaction converted from uncategorized transaction.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
import { upperFirst, camelCase, omit } from 'lodash';
|
import { upperFirst, camelCase } from 'lodash';
|
||||||
import {
|
import {
|
||||||
CASHFLOW_TRANSACTION_TYPE,
|
CASHFLOW_TRANSACTION_TYPE,
|
||||||
CASHFLOW_TRANSACTION_TYPE_META,
|
CASHFLOW_TRANSACTION_TYPE_META,
|
||||||
ICashflowTransactionTypeMeta,
|
ICashflowTransactionTypeMeta,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
|
||||||
ICashflowNewCommandDTO,
|
|
||||||
ICashflowTransaction,
|
|
||||||
ICategorizeCashflowTransactioDTO,
|
|
||||||
IUncategorizedCashflowTransaction,
|
|
||||||
} from '@/interfaces';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures the given transaction type to transformed to appropriate format.
|
* Ensures the given transaction type to transformed to appropriate format.
|
||||||
* @param {string} type
|
* @param {string} type
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export const transformCashflowTransactionType = (type) => {
|
export const transformCashflowTransactionType = (type) => {
|
||||||
@@ -38,30 +32,3 @@ export function getCashflowTransactionType(
|
|||||||
export const getCashflowAccountTransactionsTypes = () => {
|
export const getCashflowAccountTransactionsTypes = () => {
|
||||||
return Object.values(CASHFLOW_TRANSACTION_TYPE_META).map((meta) => meta.type);
|
return Object.values(CASHFLOW_TRANSACTION_TYPE_META).map((meta) => meta.type);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Tranasformes the given uncategorized transaction and categorized DTO
|
|
||||||
* to cashflow create DTO.
|
|
||||||
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
|
||||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
|
||||||
* @returns {ICashflowNewCommandDTO}
|
|
||||||
*/
|
|
||||||
export const transformCategorizeTransToCashflow = (
|
|
||||||
uncategorizeModel: IUncategorizedCashflowTransaction,
|
|
||||||
categorizeDTO: ICategorizeCashflowTransactioDTO
|
|
||||||
): ICashflowNewCommandDTO => {
|
|
||||||
return {
|
|
||||||
date: uncategorizeModel.date,
|
|
||||||
referenceNo: categorizeDTO.referenceNo || uncategorizeModel.referenceNo,
|
|
||||||
description: categorizeDTO.description || uncategorizeModel.description,
|
|
||||||
cashflowAccountId: uncategorizeModel.accountId,
|
|
||||||
creditAccountId: categorizeDTO.creditAccountId,
|
|
||||||
exchangeRate: categorizeDTO.exchangeRate || 1,
|
|
||||||
currencyCode: uncategorizeModel.currencyCode,
|
|
||||||
amount: uncategorizeModel.amount,
|
|
||||||
transactionNumber: categorizeDTO.transactionNumber,
|
|
||||||
transactionType: categorizeDTO.transactionType,
|
|
||||||
uncategorizedTransactionId: uncategorizeModel.id,
|
|
||||||
publish: true,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class CreateCustomer {
|
|||||||
public async createCustomer(
|
public async createCustomer(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
customerDTO: ICustomerNewDTO,
|
customerDTO: ICustomerNewDTO,
|
||||||
trx?: Knex.Transaction
|
authorizedUser: ISystemUser
|
||||||
): Promise<ICustomer> {
|
): Promise<ICustomer> {
|
||||||
const { Contact } = this.tenancy.models(tenantId);
|
const { Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -46,31 +46,28 @@ export class CreateCustomer {
|
|||||||
customerDTO
|
customerDTO
|
||||||
);
|
);
|
||||||
// Creates a new customer under unit-of-work envirement.
|
// Creates a new customer under unit-of-work envirement.
|
||||||
return this.uow.withTransaction(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onCustomerCreating` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.customers.onCreating, {
|
||||||
// Triggers `onCustomerCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.customers.onCreating, {
|
customerDTO,
|
||||||
tenantId,
|
trx,
|
||||||
customerDTO,
|
} as ICustomerEventCreatingPayload);
|
||||||
trx,
|
|
||||||
} as ICustomerEventCreatingPayload);
|
|
||||||
|
|
||||||
// Creates a new contact as customer.
|
// Creates a new contact as customer.
|
||||||
const customer = await Contact.query(trx).insertAndFetch({
|
const customer = await Contact.query(trx).insertAndFetch({
|
||||||
...customerObj,
|
...customerObj,
|
||||||
});
|
});
|
||||||
// Triggers `onCustomerCreated` event.
|
// Triggers `onCustomerCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.customers.onCreated, {
|
await this.eventPublisher.emitAsync(events.customers.onCreated, {
|
||||||
customer,
|
customer,
|
||||||
tenantId,
|
tenantId,
|
||||||
customerId: customer.id,
|
customerId: customer.id,
|
||||||
trx,
|
authorizedUser,
|
||||||
} as ICustomerEventCreatedPayload);
|
trx,
|
||||||
|
} as ICustomerEventCreatedPayload);
|
||||||
|
|
||||||
return customer;
|
return customer;
|
||||||
},
|
});
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { defaultTo, omit, isEmpty } from 'lodash';
|
import { defaultTo, omit, isEmpty } from 'lodash';
|
||||||
import { Service } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import {
|
import {
|
||||||
ContactService,
|
ContactService,
|
||||||
ICustomer,
|
ICustomer,
|
||||||
@@ -51,10 +51,6 @@ export class CreateEditCustomerDTO {
|
|||||||
).toMySqlDateTime(),
|
).toMySqlDateTime(),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
openingBalanceExchangeRate: defaultTo(
|
|
||||||
customerDTO.openingBalanceExchangeRate,
|
|
||||||
1
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ICustomerEditDTO,
|
ICustomerEditDTO,
|
||||||
ICustomerEventEditedPayload,
|
ICustomerEventEditedPayload,
|
||||||
ICustomerEventEditingPayload,
|
ICustomerEventEditingPayload,
|
||||||
|
ISystemUser,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
|||||||
@@ -53,8 +53,13 @@ export class CustomersApplication {
|
|||||||
public createCustomer = (
|
public createCustomer = (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
customerDTO: ICustomerNewDTO,
|
customerDTO: ICustomerNewDTO,
|
||||||
|
authorizedUser: ISystemUser
|
||||||
) => {
|
) => {
|
||||||
return this.createCustomerService.createCustomer(tenantId, customerDTO);
|
return this.createCustomerService.createCustomer(
|
||||||
|
tenantId,
|
||||||
|
customerDTO,
|
||||||
|
authorizedUser
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { Importable } from '@/services/Import/Importable';
|
|
||||||
import { CreateCustomer } from './CRUD/CreateCustomer';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { ICustomer, ICustomerNewDTO } from '@/interfaces';
|
|
||||||
import { CustomersSampleData } from './_SampleData';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class CustomersImportable extends Importable {
|
|
||||||
@Inject()
|
|
||||||
private createCustomerService: CreateCustomer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapps the imported data to create a new customer service.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ICustomerNewDTO} createDTO
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async importable(
|
|
||||||
tenantId: number,
|
|
||||||
createDTO: ICustomerNewDTO,
|
|
||||||
trx?: Knex.Transaction<any, any[]>
|
|
||||||
): Promise<void> {
|
|
||||||
await this.createCustomerService.createCustomer(tenantId, createDTO, trx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the sample data of customers used to download sample sheet.
|
|
||||||
*/
|
|
||||||
public sampleData(): any[] {
|
|
||||||
return CustomersSampleData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
|
|
||||||
export const CustomersSampleData = [
|
|
||||||
{
|
|
||||||
"Customer Type": "Business",
|
|
||||||
"First Name": "Nicolette",
|
|
||||||
"Last Name": "Schamberger",
|
|
||||||
"Company Name": "Homenick - Hane",
|
|
||||||
"Display Name": "Rowland Rowe",
|
|
||||||
"Email": "cicero86@yahoo.com",
|
|
||||||
"Personal Phone Number": "811-603-2235",
|
|
||||||
"Work Phone Number": "906-993-5190",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "F",
|
|
||||||
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
|
|
||||||
"Billing Address 1": "862 Jessika Well",
|
|
||||||
"Billing Address 2": "1091 Dorthy Mount",
|
|
||||||
"Billing Address City": "Deckowfort",
|
|
||||||
"Billing Address Country": "Ghana",
|
|
||||||
"Billing Address Phone": "825-011-5207",
|
|
||||||
"Billing Address Postcode": "38228",
|
|
||||||
"Billing Address State": "Oregon",
|
|
||||||
"Shipping Address 1": "37626 Thiel Villages",
|
|
||||||
"Shipping Address 2": "132 Batz Avenue",
|
|
||||||
"Shipping Address City": "Pagacburgh",
|
|
||||||
"Shipping Address Country": "Albania",
|
|
||||||
"Shipping Address Phone": "171-546-3701",
|
|
||||||
"Shipping Address Postcode": "13709",
|
|
||||||
"Shipping Address State": "Georgia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Customer Type": "Business",
|
|
||||||
"First Name": "Hermann",
|
|
||||||
"Last Name": "Crooks",
|
|
||||||
"Company Name": "Veum - Schaefer",
|
|
||||||
"Display Name": "Harley Veum",
|
|
||||||
"Email": "immanuel56@hotmail.com",
|
|
||||||
"Personal Phone Number": "449-780-9999",
|
|
||||||
"Work Phone Number": "970-473-5785",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "T",
|
|
||||||
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
|
|
||||||
"Billing Address 1": "532 Simonis Spring",
|
|
||||||
"Billing Address 2": "3122 Nicolas Inlet",
|
|
||||||
"Billing Address City": "East Matteofort",
|
|
||||||
"Billing Address Country": "Holy See (Vatican City State)",
|
|
||||||
"Billing Address Phone": "366-084-8629",
|
|
||||||
"Billing Address Postcode": "41607",
|
|
||||||
"Billing Address State": "Montana",
|
|
||||||
"Shipping Address 1": "2889 Tremblay Plaza",
|
|
||||||
"Shipping Address 2": "71355 Kutch Isle",
|
|
||||||
"Shipping Address City": "D'Amorehaven",
|
|
||||||
"Shipping Address Country": "Monaco",
|
|
||||||
"Shipping Address Phone": "614-189-3328",
|
|
||||||
"Shipping Address Postcode": "09634-0435",
|
|
||||||
"Shipping Address State": "Nevada"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Customer Type": "Business",
|
|
||||||
"First Name": "Nellie",
|
|
||||||
"Last Name": "Gulgowski",
|
|
||||||
"Company Name": "Boyle, Heller and Jones",
|
|
||||||
"Display Name": "Randall Kohler",
|
|
||||||
"Email": "anibal_frami@yahoo.com",
|
|
||||||
"Personal Phone Number": "498-578-0740",
|
|
||||||
"Work Phone Number": "394-550-6827",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "T",
|
|
||||||
"Note": "Vero quibusdam rem fugit aperiam est modi.",
|
|
||||||
"Billing Address 1": "214 Sauer Villages",
|
|
||||||
"Billing Address 2": "30687 Kacey Square",
|
|
||||||
"Billing Address City": "Jayceborough",
|
|
||||||
"Billing Address Country": "Benin",
|
|
||||||
"Billing Address Phone": "332-820-1127",
|
|
||||||
"Billing Address Postcode": "16425-3887",
|
|
||||||
"Billing Address State": "Mississippi",
|
|
||||||
"Shipping Address 1": "562 Diamond Loaf",
|
|
||||||
"Shipping Address 2": "9595 Satterfield Trafficway",
|
|
||||||
"Shipping Address City": "Alexandrinefort",
|
|
||||||
"Shipping Address Country": "Puerto Rico",
|
|
||||||
"Shipping Address Phone": "776-500-8456",
|
|
||||||
"Shipping Address Postcode": "30258",
|
|
||||||
"Shipping Address State": "South Dakota"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Customer Type": "Business",
|
|
||||||
"First Name": "Stone",
|
|
||||||
"Last Name": "Jerde",
|
|
||||||
"Company Name": "Cassin, Casper and Maggio",
|
|
||||||
"Display Name": "Clint McLaughlin",
|
|
||||||
"Email": "nathanael22@yahoo.com",
|
|
||||||
"Personal Phone Number": "562-790-6059",
|
|
||||||
"Work Phone Number": "686-838-0027",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "F",
|
|
||||||
"Note": "Quis cumque molestias rerum.",
|
|
||||||
"Billing Address 1": "22590 Cathy Harbor",
|
|
||||||
"Billing Address 2": "24493 Brycen Brooks",
|
|
||||||
"Billing Address City": "Elnorashire",
|
|
||||||
"Billing Address Country": "Andorra",
|
|
||||||
"Billing Address Phone": "701-852-8005",
|
|
||||||
"Billing Address Postcode": "5680",
|
|
||||||
"Billing Address State": "Nevada",
|
|
||||||
"Shipping Address 1": "5355 Erdman Bridge",
|
|
||||||
"Shipping Address 2": "421 Jeanette Camp",
|
|
||||||
"Shipping Address City": "East Philip",
|
|
||||||
"Shipping Address Country": "Venezuela",
|
|
||||||
"Shipping Address Phone": "426-119-0858",
|
|
||||||
"Shipping Address Postcode": "34929-0501",
|
|
||||||
"Shipping Address State": "Tennessee"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Customer Type": "Individual",
|
|
||||||
"First Name": "Lempi",
|
|
||||||
"Last Name": "Kling",
|
|
||||||
"Company Name": "Schamberger, O'Connell and Bechtelar",
|
|
||||||
"Display Name": "Alexie Barton",
|
|
||||||
"Email": "eulah.kreiger@hotmail.com",
|
|
||||||
"Personal Phone Number": "745-756-1063",
|
|
||||||
"Work Phone Number": "965-150-1945",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "F",
|
|
||||||
"Note": "Maxime laboriosam hic voluptate maiores est officia.",
|
|
||||||
"Billing Address 1": "0851 Jones Flat",
|
|
||||||
"Billing Address 2": "845 Bailee Drives",
|
|
||||||
"Billing Address City": "Kamrenport",
|
|
||||||
"Billing Address Country": "Niger",
|
|
||||||
"Billing Address Phone": "220-125-0608",
|
|
||||||
"Billing Address Postcode": "30311",
|
|
||||||
"Billing Address State": "Delaware",
|
|
||||||
"Shipping Address 1": "929 Ferry Row",
|
|
||||||
"Shipping Address 2": "020 Adam Plaza",
|
|
||||||
"Shipping Address City": "West Carmellaside",
|
|
||||||
"Shipping Address Country": "Ghana",
|
|
||||||
"Shipping Address Phone": "053-333-6679",
|
|
||||||
"Shipping Address Postcode": "79221-4681",
|
|
||||||
"Shipping Address State": "Illinois"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -49,10 +49,6 @@ export class CreateEditVendorDTO {
|
|||||||
).toMySqlDateTime(),
|
).toMySqlDateTime(),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
openingBalanceExchangeRate: defaultTo(
|
|
||||||
vendorDTO.openingBalanceExchangeRate,
|
|
||||||
1
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class CreateVendor {
|
|||||||
public async createVendor(
|
public async createVendor(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
vendorDTO: IVendorNewDTO,
|
vendorDTO: IVendorNewDTO,
|
||||||
trx?: Knex.Transaction
|
authorizedUser: ISystemUser
|
||||||
) {
|
) {
|
||||||
const { Contact } = this.tenancy.models(tenantId);
|
const { Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -45,31 +45,28 @@ export class CreateVendor {
|
|||||||
vendorDTO
|
vendorDTO
|
||||||
);
|
);
|
||||||
// Creates vendor contact under unit-of-work evnirement.
|
// Creates vendor contact under unit-of-work evnirement.
|
||||||
return this.uow.withTransaction(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onVendorCreating` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.vendors.onCreating, {
|
||||||
// Triggers `onVendorCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.vendors.onCreating, {
|
vendorDTO,
|
||||||
tenantId,
|
trx,
|
||||||
vendorDTO,
|
} as IVendorEventCreatingPayload);
|
||||||
trx,
|
|
||||||
} as IVendorEventCreatingPayload);
|
|
||||||
|
|
||||||
// Creates a new contact as vendor.
|
// Creates a new contact as vendor.
|
||||||
const vendor = await Contact.query(trx).insertAndFetch({
|
const vendor = await Contact.query(trx).insertAndFetch({
|
||||||
...vendorObject,
|
...vendorObject,
|
||||||
});
|
});
|
||||||
// Triggers `onVendorCreated` event.
|
// Triggers `onVendorCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.vendors.onCreated, {
|
await this.eventPublisher.emitAsync(events.vendors.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
vendorId: vendor.id,
|
vendorId: vendor.id,
|
||||||
vendor,
|
vendor,
|
||||||
trx,
|
authorizedUser,
|
||||||
} as IVendorEventCreatedPayload);
|
trx,
|
||||||
|
} as IVendorEventCreatedPayload);
|
||||||
|
|
||||||
return vendor;
|
return vendor;
|
||||||
},
|
});
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Importable } from '@/services/Import/Importable';
|
|
||||||
import { CreateVendor } from './CRUD/CreateVendor';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { VendorsSampleData } from './_SampleData';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class VendorsImportable extends Importable {
|
|
||||||
@Inject()
|
|
||||||
private createVendorService: CreateVendor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the imported data to create a new vendor service.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {} createDTO
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
*/
|
|
||||||
public async importable(
|
|
||||||
tenantId: number,
|
|
||||||
createDTO: any,
|
|
||||||
trx?: Knex.Transaction<any, any[]>
|
|
||||||
): Promise<void> {
|
|
||||||
await this.createVendorService.createVendor(tenantId, createDTO, trx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the sample data of vendors sample sheet.
|
|
||||||
*/
|
|
||||||
public sampleData(): any[] {
|
|
||||||
return VendorsSampleData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
export const VendorsSampleData = [
|
|
||||||
{
|
|
||||||
"First Name": "Nicolette",
|
|
||||||
"Last Name": "Schamberger",
|
|
||||||
"Company Name": "Homenick - Hane",
|
|
||||||
"Display Name": "Rowland Rowe",
|
|
||||||
"Email": "cicero86@yahoo.com",
|
|
||||||
"Personal Phone Number": "811-603-2235",
|
|
||||||
"Work Phone Number": "906-993-5190",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "T",
|
|
||||||
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
|
|
||||||
"Billing Address 1": "862 Jessika Well",
|
|
||||||
"Billing Address 2": "1091 Dorthy Mount",
|
|
||||||
"Billing Address City": "Deckowfort",
|
|
||||||
"Billing Address Country": "Ghana",
|
|
||||||
"Billing Address Phone": "825-011-5207",
|
|
||||||
"Billing Address Postcode": "38228",
|
|
||||||
"Billing Address State": "Oregon",
|
|
||||||
"Shipping Address 1": "37626 Thiel Villages",
|
|
||||||
"Shipping Address 2": "132 Batz Avenue",
|
|
||||||
"Shipping Address City": "Pagacburgh",
|
|
||||||
"Shipping Address Country": "Albania",
|
|
||||||
"Shipping Address Phone": "171-546-3701",
|
|
||||||
"Shipping Address Postcode": "13709",
|
|
||||||
"Shipping Address State": "Georgia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"First Name": "Hermann",
|
|
||||||
"Last Name": "Crooks",
|
|
||||||
"Company Name": "Veum - Schaefer",
|
|
||||||
"Display Name": "Harley Veum",
|
|
||||||
"Email": "immanuel56@hotmail.com",
|
|
||||||
"Personal Phone Number": "449-780-9999",
|
|
||||||
"Work Phone Number": "970-473-5785",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "T",
|
|
||||||
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
|
|
||||||
"Billing Address 1": "532 Simonis Spring",
|
|
||||||
"Billing Address 2": "3122 Nicolas Inlet",
|
|
||||||
"Billing Address City": "East Matteofort",
|
|
||||||
"Billing Address Country": "Holy See (Vatican City State)",
|
|
||||||
"Billing Address Phone": "366-084-8629",
|
|
||||||
"Billing Address Postcode": "41607",
|
|
||||||
"Billing Address State": "Montana",
|
|
||||||
"Shipping Address 1": "2889 Tremblay Plaza",
|
|
||||||
"Shipping Address 2": "71355 Kutch Isle",
|
|
||||||
"Shipping Address City": "D'Amorehaven",
|
|
||||||
"Shipping Address Country": "Monaco",
|
|
||||||
"Shipping Address Phone": "614-189-3328",
|
|
||||||
"Shipping Address Postcode": "09634-0435",
|
|
||||||
"Shipping Address State": "Nevada"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"First Name": "Nellie",
|
|
||||||
"Last Name": "Gulgowski",
|
|
||||||
"Company Name": "Boyle, Heller and Jones",
|
|
||||||
"Display Name": "Randall Kohler",
|
|
||||||
"Email": "anibal_frami@yahoo.com",
|
|
||||||
"Personal Phone Number": "498-578-0740",
|
|
||||||
"Work Phone Number": "394-550-6827",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "T",
|
|
||||||
"Note": "Vero quibusdam rem fugit aperiam est modi.",
|
|
||||||
"Billing Address 1": "214 Sauer Villages",
|
|
||||||
"Billing Address 2": "30687 Kacey Square",
|
|
||||||
"Billing Address City": "Jayceborough",
|
|
||||||
"Billing Address Country": "Benin",
|
|
||||||
"Billing Address Phone": "332-820-1127",
|
|
||||||
"Billing Address Postcode": "16425-3887",
|
|
||||||
"Billing Address State": "Mississippi",
|
|
||||||
"Shipping Address 1": "562 Diamond Loaf",
|
|
||||||
"Shipping Address 2": "9595 Satterfield Trafficway",
|
|
||||||
"Shipping Address City": "Alexandrinefort",
|
|
||||||
"Shipping Address Country": "Puerto Rico",
|
|
||||||
"Shipping Address Phone": "776-500-8456",
|
|
||||||
"Shipping Address Postcode": "30258",
|
|
||||||
"Shipping Address State": "South Dakota"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"First Name": "Stone",
|
|
||||||
"Last Name": "Jerde",
|
|
||||||
"Company Name": "Cassin, Casper and Maggio",
|
|
||||||
"Display Name": "Clint McLaughlin",
|
|
||||||
"Email": "nathanael22@yahoo.com",
|
|
||||||
"Personal Phone Number": "562-790-6059",
|
|
||||||
"Work Phone Number": "686-838-0027",
|
|
||||||
"Website": "http://google.com",
|
|
||||||
"Opening Balance": 54302.23,
|
|
||||||
"Opening Balance At": "2022-02-02",
|
|
||||||
"Opening Balance Ex. Rate": 2,
|
|
||||||
"Currency": "LYD",
|
|
||||||
"Active": "T",
|
|
||||||
"Note": "Quis cumque molestias rerum.",
|
|
||||||
"Billing Address 1": "22590 Cathy Harbor",
|
|
||||||
"Billing Address 2": "24493 Brycen Brooks",
|
|
||||||
"Billing Address City": "Elnorashire",
|
|
||||||
"Billing Address Country": "Andorra",
|
|
||||||
"Billing Address Phone": "701-852-8005",
|
|
||||||
"Billing Address Postcode": "5680",
|
|
||||||
"Billing Address State": "Nevada",
|
|
||||||
"Shipping Address 1": "5355 Erdman Bridge",
|
|
||||||
"Shipping Address 2": "421 Jeanette Camp",
|
|
||||||
"Shipping Address City": "East Philip",
|
|
||||||
"Shipping Address Country": "Venezuela",
|
|
||||||
"Shipping Address Phone": "426-119-0858",
|
|
||||||
"Shipping Address Postcode": "34929-0501",
|
|
||||||
"Shipping Address State": "Tennessee"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -34,7 +34,7 @@ export default class CreateCreditNote extends BaseCreditNotes {
|
|||||||
public newCreditNote = async (
|
public newCreditNote = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
creditNoteDTO: ICreditNoteNewDTO,
|
creditNoteDTO: ICreditNoteNewDTO,
|
||||||
trx?: Knex.Transaction
|
authorizedUser: ISystemUser
|
||||||
) => {
|
) => {
|
||||||
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -66,32 +66,28 @@ 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(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onCreditNoteCreating` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
||||||
// Triggers `onCreditNoteCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
creditNoteDTO,
|
||||||
tenantId,
|
trx,
|
||||||
creditNoteDTO,
|
} as ICreditNoteCreatingPayload);
|
||||||
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
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
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,8 +88,7 @@ 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);
|
||||||
|
|
||||||
@@ -104,32 +103,28 @@ 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(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onExpenseCreating` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
||||||
// Triggers `onExpenseCreating` event.
|
trx,
|
||||||
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
tenantId,
|
||||||
trx,
|
expenseDTO,
|
||||||
tenantId,
|
} as IExpenseCreatingPayload);
|
||||||
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
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
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,43 +36,3 @@ 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,196 +0,0 @@
|
|||||||
import XLSX from 'xlsx';
|
|
||||||
import bluebird from 'bluebird';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { first } from 'lodash';
|
|
||||||
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import {
|
|
||||||
ImportInsertError,
|
|
||||||
ImportOperError,
|
|
||||||
ImportOperSuccess,
|
|
||||||
ImportableContext,
|
|
||||||
} from './interfaces';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { getUniqueImportableValue, trimObject } from './_utils';
|
|
||||||
import { ImportableResources } from './ImportableResources';
|
|
||||||
import ResourceService from '../Resource/ResourceService';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { Import } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileCommon {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importFileValidator: ImportFileDataValidator;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importable: ImportableResources;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private resource: ResourceService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the columns of the imported data based on the provided mapping attributes.
|
|
||||||
* @param {Record<string, any>[]} body - The array of data objects to map.
|
|
||||||
* @param {ImportMappingAttr[]} map - The mapping attributes.
|
|
||||||
* @returns {Record<string, any>[]} - The mapped data objects.
|
|
||||||
*/
|
|
||||||
public parseXlsxSheet(buffer: Buffer): Record<string, unknown>[] {
|
|
||||||
const workbook = XLSX.read(buffer, { type: 'buffer', raw: true });
|
|
||||||
|
|
||||||
const firstSheetName = workbook.SheetNames[0];
|
|
||||||
const worksheet = workbook.Sheets[firstSheetName];
|
|
||||||
|
|
||||||
return XLSX.utils.sheet_to_json(worksheet, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports the given parsed data to the resource storage through registered importable service.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {string} resourceName - Resource name.
|
|
||||||
* @param {Record<string, any>} parsedData - Parsed data.
|
|
||||||
* @param {Knex.Transaction} trx - Knex transaction.
|
|
||||||
* @returns {Promise<[ImportOperSuccess[], ImportOperError[]]>}
|
|
||||||
*/
|
|
||||||
public async import(
|
|
||||||
tenantId: number,
|
|
||||||
importFile: Import,
|
|
||||||
parsedData: Record<string, any>[],
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<[ImportOperSuccess[], ImportOperError[]]> {
|
|
||||||
const resourceFields = this.resource.getResourceFields2(
|
|
||||||
tenantId,
|
|
||||||
importFile.resource
|
|
||||||
);
|
|
||||||
const ImportableRegistry = this.importable.registry;
|
|
||||||
const importable = ImportableRegistry.getImportable(importFile.resource);
|
|
||||||
|
|
||||||
const concurrency = importable.concurrency || 10;
|
|
||||||
|
|
||||||
const success: ImportOperSuccess[] = [];
|
|
||||||
const failed: ImportOperError[] = [];
|
|
||||||
|
|
||||||
const importAsync = async (objectDTO, index: number): Promise<void> => {
|
|
||||||
const context: ImportableContext = {
|
|
||||||
rowIndex: index,
|
|
||||||
import: importFile,
|
|
||||||
};
|
|
||||||
const transformedDTO = importable.transform(objectDTO, context);
|
|
||||||
const rowNumber = index + 1;
|
|
||||||
const uniqueValue = getUniqueImportableValue(resourceFields, objectDTO);
|
|
||||||
const errorContext = {
|
|
||||||
rowNumber,
|
|
||||||
uniqueValue,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
// Validate the DTO object before passing it to the service layer.
|
|
||||||
await this.importFileValidator.validateData(
|
|
||||||
resourceFields,
|
|
||||||
transformedDTO
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
// Run the importable function and listen to the errors.
|
|
||||||
const data = await importable.importable(
|
|
||||||
tenantId,
|
|
||||||
transformedDTO,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
success.push({ index, data });
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof ServiceError) {
|
|
||||||
const error: ImportInsertError[] = [
|
|
||||||
{
|
|
||||||
errorCode: 'ServiceError',
|
|
||||||
errorMessage: err.message || err.errorType,
|
|
||||||
...errorContext,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
failed.push({ index, error });
|
|
||||||
} else {
|
|
||||||
const error: ImportInsertError[] = [
|
|
||||||
{
|
|
||||||
errorCode: 'UnknownError',
|
|
||||||
errorMessage: 'Unknown error occurred',
|
|
||||||
...errorContext,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
failed.push({ index, error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (errors) {
|
|
||||||
const error = errors.map((er) => ({ ...er, ...errorContext }));
|
|
||||||
failed.push({ index, error });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await bluebird.map(parsedData, importAsync, { concurrency });
|
|
||||||
|
|
||||||
return [success, failed];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} resourceName
|
|
||||||
* @param {Record<string, any>} params
|
|
||||||
*/
|
|
||||||
public async validateParamsSchema(
|
|
||||||
resourceName: string,
|
|
||||||
params: Record<string, any>
|
|
||||||
) {
|
|
||||||
const ImportableRegistry = this.importable.registry;
|
|
||||||
const importable = ImportableRegistry.getImportable(resourceName);
|
|
||||||
|
|
||||||
const yupSchema = importable.paramsValidationSchema();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await yupSchema.validate(params, { abortEarly: false });
|
|
||||||
} catch (validationError) {
|
|
||||||
const errors = validationError.inner.map((error) => ({
|
|
||||||
errorCode: 'ParamsValidationError',
|
|
||||||
errorMessage: error.errors,
|
|
||||||
}));
|
|
||||||
throw errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} resourceName
|
|
||||||
* @param {Record<string, any>} params
|
|
||||||
*/
|
|
||||||
public async validateParams(
|
|
||||||
tenantId: number,
|
|
||||||
resourceName: string,
|
|
||||||
params: Record<string, any>
|
|
||||||
) {
|
|
||||||
const ImportableRegistry = this.importable.registry;
|
|
||||||
const importable = ImportableRegistry.getImportable(resourceName);
|
|
||||||
|
|
||||||
await importable.validateParams(tenantId, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} resourceName
|
|
||||||
* @param {Record<string, any>} params
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public transformParams(resourceName: string, params: Record<string, any>) {
|
|
||||||
const ImportableRegistry = this.importable.registry;
|
|
||||||
const importable = ImportableRegistry.getImportable(resourceName);
|
|
||||||
|
|
||||||
return importable.transformParams(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the sheet columns from the given sheet data.
|
|
||||||
* @param {unknown[]} json
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
public parseSheetColumns(json: unknown[]): string[] {
|
|
||||||
return R.compose(Object.keys, trimObject, first)(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import bluebird from 'bluebird';
|
|
||||||
import { isUndefined, pickBy, set } from 'lodash';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { ImportMappingAttr, ResourceMetaFieldsMap } from './interfaces';
|
|
||||||
import {
|
|
||||||
valueParser,
|
|
||||||
parseKey,
|
|
||||||
getFieldKey,
|
|
||||||
aggregate,
|
|
||||||
sanitizeSheetData,
|
|
||||||
getMapToPath,
|
|
||||||
} from './_utils';
|
|
||||||
import ResourceService from '../Resource/ResourceService';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { CurrencyParsingDTOs } from './_constants';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
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 {}
|
|
||||||
*/
|
|
||||||
public async parseSheetData(
|
|
||||||
tenantId: number,
|
|
||||||
importFile: any,
|
|
||||||
importableFields: ResourceMetaFieldsMap,
|
|
||||||
data: Record<string, unknown>[],
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<Record<string, any>[]> {
|
|
||||||
// Sanitize the sheet data.
|
|
||||||
const sanitizedData = sanitizeSheetData(data);
|
|
||||||
|
|
||||||
// Map the sheet columns key with the given map.
|
|
||||||
const mappedDTOs = this.mapSheetColumns(
|
|
||||||
sanitizedData,
|
|
||||||
importFile.mappingParsed
|
|
||||||
);
|
|
||||||
// Parse the mapped sheet values.
|
|
||||||
const parsedValues = await this.parseExcelValues(
|
|
||||||
tenantId,
|
|
||||||
importableFields,
|
|
||||||
mappedDTOs,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
const aggregateValues = this.aggregateParsedValues(
|
|
||||||
tenantId,
|
|
||||||
importFile.resource,
|
|
||||||
parsedValues
|
|
||||||
);
|
|
||||||
return aggregateValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aggregates parsed data based on resource metadata configuration.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {string} resourceName
|
|
||||||
* @param {Record<string, any>} parsedData
|
|
||||||
* @returns {Record<string, any>[]}
|
|
||||||
*/
|
|
||||||
public aggregateParsedValues = (
|
|
||||||
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.
|
|
||||||
* @param {Record<string, any>[]} body - The array of data objects to map.
|
|
||||||
* @param {ImportMappingAttr[]} map - The mapping attributes.
|
|
||||||
* @returns {Record<string, any>[]} - The mapped data objects.
|
|
||||||
*/
|
|
||||||
public mapSheetColumns(
|
|
||||||
body: Record<string, any>[],
|
|
||||||
map: ImportMappingAttr[]
|
|
||||||
): Record<string, any>[] {
|
|
||||||
return body.map((item) => {
|
|
||||||
const newItem = {};
|
|
||||||
map
|
|
||||||
.filter((mapping) => !isUndefined(item[mapping.from]))
|
|
||||||
.forEach((mapping) => {
|
|
||||||
const toPath = getMapToPath(mapping.to, mapping.group);
|
|
||||||
newItem[toPath] = item[mapping.from];
|
|
||||||
});
|
|
||||||
return newItem;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses sheet values before passing to the service layer.
|
|
||||||
* @param {ResourceMetaFieldsMap} fields -
|
|
||||||
* @param {Record<string, any>} valueDTOS -
|
|
||||||
* @returns {Record<string, any>}
|
|
||||||
*/
|
|
||||||
public async parseExcelValues(
|
|
||||||
tenantId: number,
|
|
||||||
fields: ResourceMetaFieldsMap,
|
|
||||||
valueDTOs: Record<string, any>[],
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<Record<string, any>[]> {
|
|
||||||
const tenantModels = this.tenancy.models(tenantId);
|
|
||||||
const _valueParser = valueParser(fields, tenantModels, trx);
|
|
||||||
const _keyParser = parseKey(fields);
|
|
||||||
|
|
||||||
const parseAsync = async (valueDTO) => {
|
|
||||||
// Clean up the undefined keys that not exist in resource fields.
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Map the object values.
|
|
||||||
return bluebird.reduce(
|
|
||||||
keys,
|
|
||||||
async (acc, key) => {
|
|
||||||
const parsedValue = await _valueParser(_valueDTO[key], key);
|
|
||||||
const parsedKey = await _keyParser(key);
|
|
||||||
|
|
||||||
set(acc, parsedKey, parsedValue);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return bluebird.map(valueDTOs, parseAsync, {
|
|
||||||
concurrency: CurrencyParsingDTOs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Service } from 'typedi';
|
|
||||||
import { ImportInsertError, ResourceMetaFieldsMap } from './interfaces';
|
|
||||||
import { ERRORS, convertFieldsToYupValidation } from './_utils';
|
|
||||||
import { IModelMeta } from '@/interfaces';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileDataValidator {
|
|
||||||
/**
|
|
||||||
* Validates the given resource is importable.
|
|
||||||
* @param {IModelMeta} resourceMeta
|
|
||||||
*/
|
|
||||||
public validateResourceImportable(resourceMeta: IModelMeta) {
|
|
||||||
// Throw service error if the resource does not support importing.
|
|
||||||
if (!resourceMeta.importable) {
|
|
||||||
throw new ServiceError(ERRORS.RESOURCE_NOT_IMPORTABLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the given mapped DTOs and returns errors with their index.
|
|
||||||
* @param {Record<string, any>} mappedDTOs
|
|
||||||
* @returns {Promise<void | ImportInsertError[]>}
|
|
||||||
*/
|
|
||||||
public async validateData(
|
|
||||||
importableFields: ResourceMetaFieldsMap,
|
|
||||||
data: Record<string, any>
|
|
||||||
): Promise<void | ImportInsertError[]> {
|
|
||||||
const YupSchema = convertFieldsToYupValidation(importableFields);
|
|
||||||
const _data = { ...data };
|
|
||||||
|
|
||||||
try {
|
|
||||||
await YupSchema.validate(_data, { abortEarly: false });
|
|
||||||
} catch (validationError) {
|
|
||||||
const errors = validationError.inner.reduce((errors, error) => {
|
|
||||||
const newErrors = error.errors.map((errMsg) => ({
|
|
||||||
errorCode: 'ValidationError',
|
|
||||||
errorMessage: errMsg,
|
|
||||||
}));
|
|
||||||
return [...errors, ...newErrors];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
throw errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { fromPairs, isUndefined } from 'lodash';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import {
|
|
||||||
ImportDateFormats,
|
|
||||||
ImportFileMapPOJO,
|
|
||||||
ImportMappingAttr,
|
|
||||||
} from './interfaces';
|
|
||||||
import ResourceService from '../Resource/ResourceService';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { ERRORS } from './_utils';
|
|
||||||
import { Import } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileMapping {
|
|
||||||
@Inject()
|
|
||||||
private resource: ResourceService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping the excel sheet columns with resource columns.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} importId
|
|
||||||
* @param {ImportMappingAttr} maps
|
|
||||||
*/
|
|
||||||
public async mapping(
|
|
||||||
tenantId: number,
|
|
||||||
importId: number,
|
|
||||||
maps: ImportMappingAttr[]
|
|
||||||
): Promise<ImportFileMapPOJO> {
|
|
||||||
const importFile = await Import.query()
|
|
||||||
.findOne('filename', importId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Invalidate the from/to map attributes.
|
|
||||||
this.validateMapsAttrs(tenantId, importFile, maps);
|
|
||||||
|
|
||||||
// Validate the diplicated relations of map attrs.
|
|
||||||
this.validateDuplicatedMapAttrs(maps);
|
|
||||||
|
|
||||||
// Validate the date format mapping.
|
|
||||||
this.validateDateFormatMapping(tenantId, importFile.resource, maps);
|
|
||||||
|
|
||||||
const mappingStringified = JSON.stringify(maps);
|
|
||||||
|
|
||||||
await Import.query().findById(importFile.id).patch({
|
|
||||||
mapping: mappingStringified,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
import: {
|
|
||||||
importId: importFile.importId,
|
|
||||||
resource: importFile.resource,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the mapping attributes.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {} importFile -
|
|
||||||
* @param {ImportMappingAttr[]} maps
|
|
||||||
* @throws {ServiceError(ERRORS.INVALID_MAP_ATTRS)}
|
|
||||||
*/
|
|
||||||
private validateMapsAttrs(
|
|
||||||
tenantId: number,
|
|
||||||
importFile: any,
|
|
||||||
maps: ImportMappingAttr[]
|
|
||||||
) {
|
|
||||||
const fields = this.resource.getResourceFields2(
|
|
||||||
tenantId,
|
|
||||||
importFile.resource
|
|
||||||
);
|
|
||||||
const columnsMap = fromPairs(
|
|
||||||
importFile.columnsParsed.map((field) => [field, ''])
|
|
||||||
);
|
|
||||||
const invalid = [];
|
|
||||||
|
|
||||||
// is not empty, is not undefined or map.group
|
|
||||||
maps.forEach((map) => {
|
|
||||||
let _invalid = true;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (invalid.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.INVALID_MAP_ATTRS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the map attrs relation should be one-to-one relation only.
|
|
||||||
* @param {ImportMappingAttr[]} maps
|
|
||||||
*/
|
|
||||||
private validateDuplicatedMapAttrs(maps: ImportMappingAttr[]) {
|
|
||||||
const fromMap = {};
|
|
||||||
const toMap = {};
|
|
||||||
|
|
||||||
maps.forEach((map) => {
|
|
||||||
if (fromMap[map.from]) {
|
|
||||||
throw new ServiceError(ERRORS.DUPLICATED_FROM_MAP_ATTR);
|
|
||||||
} else {
|
|
||||||
fromMap[map.from] = true;
|
|
||||||
}
|
|
||||||
const toPath = !isUndefined(map?.group)
|
|
||||||
? `${map.group}.${map.to}`
|
|
||||||
: map.to;
|
|
||||||
|
|
||||||
if (toMap[toPath]) {
|
|
||||||
throw new ServiceError(ERRORS.DUPLICATED_TO_MAP_ATTR);
|
|
||||||
} else {
|
|
||||||
toMap[toPath] = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the date format mapping.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {string} resource
|
|
||||||
* @param {ImportMappingAttr[]} maps
|
|
||||||
*/
|
|
||||||
private validateDateFormatMapping(
|
|
||||||
tenantId: number,
|
|
||||||
resource: string,
|
|
||||||
maps: ImportMappingAttr[]
|
|
||||||
) {
|
|
||||||
const fields = this.resource.getResourceImportableFields(
|
|
||||||
tenantId,
|
|
||||||
resource
|
|
||||||
);
|
|
||||||
// @todo Validate date type of the nested fields.
|
|
||||||
maps.forEach((map) => {
|
|
||||||
if (
|
|
||||||
typeof fields[map.to] !== 'undefined' &&
|
|
||||||
fields[map.to].fieldType === 'date'
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
typeof map.dateFormat !== 'undefined' &&
|
|
||||||
ImportDateFormats.indexOf(map.dateFormat) === -1
|
|
||||||
) {
|
|
||||||
throw new ServiceError(ERRORS.INVALID_MAP_DATE_FORMAT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
||||||
import { ImportFileMetaTransformer } from './ImportFileMetaTransformer';
|
|
||||||
import { Import } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileMeta {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private transformer: TransformerInjectable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the import meta of the given import model id.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} importId
|
|
||||||
* @returns {}
|
|
||||||
*/
|
|
||||||
async getImportMeta(tenantId: number, importId: string) {
|
|
||||||
const importFile = await Import.query()
|
|
||||||
.where('tenantId', tenantId)
|
|
||||||
.findOne('importId', importId);
|
|
||||||
|
|
||||||
// Retrieves the transformed accounts collection.
|
|
||||||
return this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
importFile,
|
|
||||||
new ImportFileMetaTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
|
||||||
|
|
||||||
export class ImportFileMetaTransformer extends Transformer {
|
|
||||||
/**
|
|
||||||
* Include these attributes to sale invoice object.
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
public includeAttributes = (): string[] => {
|
|
||||||
return ['map'];
|
|
||||||
};
|
|
||||||
|
|
||||||
public excludeAttributes = (): string[] => {
|
|
||||||
return ['id', 'filename', 'columns', 'mappingParsed', 'mapping'];
|
|
||||||
}
|
|
||||||
|
|
||||||
map(importFile) {
|
|
||||||
return importFile.mappingParsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
|
||||||
import { ImportFileProcess } from './ImportFileProcess';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFilePreview {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importFile: ImportFileProcess;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview the imported file results before commiting the transactions.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} importId
|
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
|
||||||
*/
|
|
||||||
public async preview(
|
|
||||||
tenantId: number,
|
|
||||||
importId: number
|
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
|
||||||
|
|
||||||
const meta = await this.importFile.import(tenantId, importId, trx);
|
|
||||||
|
|
||||||
// Rollback the successed transaction.
|
|
||||||
await trx.rollback();
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { chain } from 'lodash';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import {
|
|
||||||
ERRORS,
|
|
||||||
getSheetColumns,
|
|
||||||
getUnmappedSheetColumns,
|
|
||||||
readImportFile,
|
|
||||||
} from './_utils';
|
|
||||||
import { ImportFileCommon } from './ImportFileCommon';
|
|
||||||
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
|
|
||||||
import ResourceService from '../Resource/ResourceService';
|
|
||||||
import UnitOfWork from '../UnitOfWork';
|
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
|
||||||
import { Import } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileProcess {
|
|
||||||
@Inject()
|
|
||||||
private resource: ResourceService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importCommon: ImportFileCommon;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importParser: ImportFileDataTransformer;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview the imported file results before commiting the transactions.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} importId
|
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
|
||||||
*/
|
|
||||||
public async import(
|
|
||||||
tenantId: number,
|
|
||||||
importId: number,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
|
||||||
const importFile = await Import.query()
|
|
||||||
.findOne('importId', importId)
|
|
||||||
.where('tenantId', tenantId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Throw error if the import file is not mapped yet.
|
|
||||||
if (!importFile.isMapped) {
|
|
||||||
throw new ServiceError(ERRORS.IMPORT_FILE_NOT_MAPPED);
|
|
||||||
}
|
|
||||||
// Read the imported file.
|
|
||||||
const buffer = await readImportFile(importFile.filename);
|
|
||||||
const sheetData = this.importCommon.parseXlsxSheet(buffer);
|
|
||||||
const header = getSheetColumns(sheetData);
|
|
||||||
|
|
||||||
const resource = importFile.resource;
|
|
||||||
const resourceFields = this.resource.getResourceFields2(tenantId, resource);
|
|
||||||
|
|
||||||
// Runs the importing operation with ability to return errors that will happen.
|
|
||||||
const [successedImport, failedImport, allData] =
|
|
||||||
await this.uow.withTransaction(
|
|
||||||
tenantId,
|
|
||||||
async (trx: Knex.Transaction) => {
|
|
||||||
// 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 errors = chain(failedImport)
|
|
||||||
.map((oper) => oper.error)
|
|
||||||
.flatten()
|
|
||||||
.value();
|
|
||||||
|
|
||||||
const unmappedColumns = getUnmappedSheetColumns(header, mapping);
|
|
||||||
const totalCount = allData.length;
|
|
||||||
|
|
||||||
const createdCount = successedImport.length;
|
|
||||||
const errorsCount = failedImport.length;
|
|
||||||
const skippedCount = errorsCount;
|
|
||||||
|
|
||||||
return {
|
|
||||||
resource,
|
|
||||||
createdCount,
|
|
||||||
skippedCount,
|
|
||||||
totalCount,
|
|
||||||
errorsCount,
|
|
||||||
errors,
|
|
||||||
unmappedColumns: unmappedColumns,
|
|
||||||
unmappedColumnsCount: unmappedColumns.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import {
|
|
||||||
deleteImportFile,
|
|
||||||
getResourceColumns,
|
|
||||||
readImportFile,
|
|
||||||
sanitizeResourceName,
|
|
||||||
validateSheetEmpty,
|
|
||||||
} from './_utils';
|
|
||||||
import ResourceService from '../Resource/ResourceService';
|
|
||||||
import { ImportFileCommon } from './ImportFileCommon';
|
|
||||||
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
|
||||||
import { ImportFileUploadPOJO } from './interfaces';
|
|
||||||
import { Import } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportFileUploadService {
|
|
||||||
@Inject()
|
|
||||||
private resourceService: ResourceService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importFileCommon: ImportFileCommon;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importValidator: ImportFileDataValidator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports the specified file for the given resource.
|
|
||||||
* Deletes the file if an error occurs during the import process.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {string} resourceName
|
|
||||||
* @param {string} filename
|
|
||||||
* @param {Record<string, number | string>} params
|
|
||||||
* @returns {Promise<ImportFileUploadPOJO>}
|
|
||||||
*/
|
|
||||||
public async import(
|
|
||||||
tenantId: number,
|
|
||||||
resourceName: string,
|
|
||||||
filename: string,
|
|
||||||
params: Record<string, number | string>
|
|
||||||
): Promise<ImportFileUploadPOJO> {
|
|
||||||
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 resourceMeta = this.resourceService.getResourceMeta(
|
|
||||||
tenantId,
|
|
||||||
resource
|
|
||||||
);
|
|
||||||
// Throw service error if the resource does not support importing.
|
|
||||||
this.importValidator.validateResourceImportable(resourceMeta);
|
|
||||||
|
|
||||||
// Reads the imported file into buffer.
|
|
||||||
const buffer = await readImportFile(filename);
|
|
||||||
|
|
||||||
// Parse the buffer file to array data.
|
|
||||||
const sheetData = this.importFileCommon.parseXlsxSheet(buffer);
|
|
||||||
|
|
||||||
// Throws service error if the sheet data is empty.
|
|
||||||
validateSheetEmpty(sheetData);
|
|
||||||
|
|
||||||
const sheetColumns = this.importFileCommon.parseSheetColumns(sheetData);
|
|
||||||
const coumnsStringified = JSON.stringify(sheetColumns);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Validates the params Yup schema.
|
|
||||||
await this.importFileCommon.validateParamsSchema(resource, params);
|
|
||||||
|
|
||||||
// Validates importable params asyncly.
|
|
||||||
await this.importFileCommon.validateParams(tenantId, resource, params);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
const _params = this.importFileCommon.transformParams(resource, params);
|
|
||||||
const paramsStringified = JSON.stringify(_params);
|
|
||||||
|
|
||||||
// Store the import model with related metadata.
|
|
||||||
const importFile = await Import.query().insert({
|
|
||||||
filename,
|
|
||||||
resource,
|
|
||||||
tenantId,
|
|
||||||
importId: filename,
|
|
||||||
columns: coumnsStringified,
|
|
||||||
params: paramsStringified,
|
|
||||||
});
|
|
||||||
const resourceColumnsMap = this.resourceService.getResourceFields2(
|
|
||||||
tenantId,
|
|
||||||
resource
|
|
||||||
);
|
|
||||||
const resourceColumns = getResourceColumns(resourceColumnsMap);
|
|
||||||
|
|
||||||
return {
|
|
||||||
import: {
|
|
||||||
importId: importFile.importId,
|
|
||||||
resource: importFile.resource,
|
|
||||||
},
|
|
||||||
sheetColumns,
|
|
||||||
resourceColumns,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user