Compare commits

..

2 Commits

Author SHA1 Message Date
allcontributors[bot]
d793e79475 docs: update .all-contributorsrc [skip ci] 2023-12-26 13:45:13 +00:00
allcontributors[bot]
855462a185 docs: update README.md [skip ci] 2023-12-26 13:45:12 +00:00
904 changed files with 5200 additions and 30817 deletions

View File

@@ -78,33 +78,6 @@
"contributions": [ "contributions": [
"bug" "bug"
] ]
},
{
"login": "ANasouf",
"name": "ANasouf",
"avatar_url": "https://avatars.githubusercontent.com/u/19536487?v=4",
"profile": "https://github.com/ANasouf",
"contributions": [
"code"
]
},
{
"login": "xprnio",
"name": "Ragnar Laud",
"avatar_url": "https://avatars.githubusercontent.com/u/3042904?v=4",
"profile": "https://ragnarlaud.dev",
"contributions": [
"bug"
]
},
{
"login": "asenawritescode",
"name": "Asena",
"avatar_url": "https://avatars.githubusercontent.com/u/67445192?v=4",
"profile": "https://github.com/asenawritescode",
"contributions": [
"bug"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -56,42 +56,5 @@ GOTENBERG_URL=http://gotenberg:3000
GOTENBERG_DOCS_URL=http://server:3000/public/ GOTENBERG_DOCS_URL=http://server:3000/public/
# Gotenberg API - (development) # Gotenberg API - (development)
# GOTENBERG_URL=http://localhost:9000 # GOTENBERG_URL=http://gotenberg:3000
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/ # GOTENBERG_DOCS_URL=http://server:3000/public/
# Exchange Rate Service
EXCHANGE_RATE_SERVICE=open-exchange-rate
# Open Exchange Rate
OPEN_EXCHANGE_RATE_APP_ID=
# The Plaid environment to use ('sandbox' or 'development').
# https://plaid.com/docs/#api-host
PLAID_ENV=sandbox
# Your Plaid keys, which can be found in the Plaid Dashboard.
# https://dashboard.plaid.com/account/keys
PLAID_CLIENT_ID=
PLAID_SECRET_DEVELOPMENT=
PLAID_SECRET_SANDBOX=
PLAID_LINK_WEBHOOK=
# (Optional) Redirect URI settings section
# Only required for OAuth redirect URI testing (not common on desktop):
# Sandbox Mode:
# Set the PLAID_SANDBOX_REDIRECT_URI below to 'http://localhost:3001/oauth-link'.
# The OAuth redirect flow requires an endpoint on the developer's website
# that the bank website should redirect to. You will also need to configure
# this redirect URI for your client ID through the Plaid developer dashboard
# at https://dashboard.plaid.com/team/api.
# Development mode:
# When running in development mode, you must use an https:// url.
# You will need to configure this https:// redirect URI in the Plaid developer dashboard.
# Instructions to create a self-signed certificate for localhost can be found at
# https://github.com/plaid/pattern/blob/master/README.md#testing-oauth.
# If your system is not set up to run localhost with https://, you will be unable to test
# the OAuth in development and should leave the PLAID_DEVELOPMENT_REDIRECT_URI blank.
PLAID_SANDBOX_REDIRECT_URI=
PLAID_DEVELOPMENT_REDIRECT_URI=

View File

@@ -2,40 +2,6 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
## [0.14.0] - 30-01-2024
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
* fix: expense amounts should not be rounded by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/339
* feat: get latest exchange rate from third party services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/340
* fix(webapp): inconsistency in currency of universal search items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/335
* hotfix: editing sales and expense transactions don't reflect GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/342
## [0.13.3] - 22-01-2024
* hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329
## [0.13.2] - 21-01-2024
* feat: show customer / vendor balance. by @asenawritescode in https://github.com/bigcapitalhq/bigcapital/pull/311
* feat: inventory valuation csv and xlsx export by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/308
* feat: sales by items export csv & xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/310
* fix(server): the invoice and payment receipt printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/315
* fix: get cashflow transaction broken cause transaction type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/318
* fix: `AccountActivateAlert` import by @xprnio in https://github.com/bigcapitalhq/bigcapital/pull/322
## [0.13.1] - 15-01-2024
* feat(webapp): add approve/reject to action bar of estimate details dr… by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/304
* docs: add ANasouf as a contributor for code by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/305
* feat: Export general ledger & Journal to CSV and XLSX by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/303
* feat: Auto re-calculate the items rate once changing the invoice exchange rate. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/270
## [0.13.0] - 31-12-2023
* feat: Send an invoice mail the customer email by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/292
* fix: Allow non-numeric postal codes by @cschuijt in https://github.com/bigcapitalhq/bigcapital/pull/294
* docs: add cschuijt as a contributor for bug by @allcontributors in https://github.com/bigcapitalhq/bigcapital/pull/295
## [0.12.1] - 17-11-2023 ## [0.12.1] - 17-11-2023
* feat: Add default customer message and terms conditions to the transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/291 * feat: Add default customer message and terms conditions to the transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/291

View File

@@ -62,12 +62,6 @@ To get started locally, we have a [guide to help you](https://github.com/bigcapi
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/new/#https://github.com/bigcapitalhq/bigcapital) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/new/#https://github.com/bigcapitalhq/bigcapital)
## Headless Accounting
You can integrate Bigcapital API with your system to organize your transactions in double-entry system to get the best financial reports.
[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/bigcapital/workspace/bigcapital-api)
# Resources # Resources
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use. - [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
@@ -115,9 +109,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr> </tr>
<tr> <tr>
<td align="center" valign="top" width="14.28%"><a href="http://cschuijt.nl"><img src="https://avatars.githubusercontent.com/u/5460015?v=4?s=100" width="100px;" alt="Casper Schuijt"/><br /><sub><b>Casper Schuijt</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Acschuijt" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="http://cschuijt.nl"><img src="https://avatars.githubusercontent.com/u/5460015?v=4?s=100" width="100px;" alt="Casper Schuijt"/><br /><sub><b>Casper Schuijt</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Acschuijt" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ANasouf"><img src="https://avatars.githubusercontent.com/u/19536487?v=4?s=100" width="100px;" alt="ANasouf"/><br /><sub><b>ANasouf</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=ANasouf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ragnarlaud.dev"><img src="https://avatars.githubusercontent.com/u/3042904?v=4?s=100" width="100px;" alt="Ragnar Laud"/><br /><sub><b>Ragnar Laud</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Axprnio" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asenawritescode"><img src="https://avatars.githubusercontent.com/u/67445192?v=4?s=100" width="100px;" alt="Asena"/><br /><sub><b>Asena</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aasenawritescode" title="Bug reports">🐛</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -3,4 +3,3 @@
stdout.log stdout.log
/dist /dist
/build /build
/public/imports

View File

@@ -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",
@@ -92,22 +91,19 @@
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
"puppeteer": "^10.2.0", "puppeteer": "^10.2.0",
"plaid": "^10.3.0",
"qim": "0.0.52", "qim": "0.0.52",
"ramda": "^0.27.1", "ramda": "^0.27.1",
"rate-limiter-flexible": "^2.1.14", "rate-limiter-flexible": "^2.1.14",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4", "rtl-detect": "^1.0.4",
"source-map-loader": "^4.0.1", "source-map-loader": "^4.0.1",
"socket.io": "^4.7.4",
"tmp-promise": "^3.0.3", "tmp-promise": "^3.0.3",
"ts-transformer-keys": "^0.4.2", "ts-transformer-keys": "^0.4.2",
"tsyringe": "^4.3.0", "tsyringe": "^4.3.0",
"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",

Binary file not shown.

Binary file not shown.

View File

@@ -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": "نوع الصنف",

View File

@@ -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",

View File

@@ -1,57 +0,0 @@
@import "../base.scss";
html,
body {
font-size: 14px;
}
body{
font-weight: 400;
letter-spacing: 0;
line-height: 1.28581;
text-transform: none;
color: #000;
font-family: Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif;
}
.sheet{
padding: 20px;
}
.sheet__company-name{
margin: 0;
font-size: 1.4rem;
}
.sheet__sheet-type {
margin: 0
}
.sheet__sheet-date {
margin-top: 0.35rem;
}
.sheet__header {
text-align: center;
margin-bottom: 1rem;
}
.sheet__table {
border-top: 1px solid #000;
table-layout: fixed;
border-spacing: 0;
text-align: left;
font-size: inherit;
width: 100%;
}
.sheet__table thead th {
color: #000;
border-bottom: 1px solid #000000;
padding: 0.5rem;
}
.sheet__table tbody td {
border-bottom: 0;
padding-top: 0.28rem;
padding-bottom: 0.28rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
color: #252A31;
border-bottom: 1px solid transparent;
}

View File

@@ -1,25 +0,0 @@
block head
style
include ../../css/modules/financial-sheet.css
style.
!{customCSS}
block content
.sheet
.sheet__header
.sheet__company-name=organizationName
.sheet__sheet-type=sheetName
.sheet__sheet-date=sheetDate
table.sheet__table
thead
tr
each column in table.columns
th(style=column.style class='column--' + column.key)= column.label
tbody
each row in table.rows
tr(class=row.classNames)
each cell in row.cells
td(class='cell--' + cell.key)
span!= cell.value

View File

@@ -66,10 +66,12 @@ module.exports = {
// sourcemaps: true, // Allow to enable/disable sourcemaps or pass object to configure it. // sourcemaps: true, // Allow to enable/disable sourcemaps or pass object to configure it.
// minify: true, // Allow to enable/disable minify the source. // minify: true, // Allow to enable/disable minify the source.
}, },
{ // {
src: `${RESOURCES_PATH}/scss/modules/financial-sheet.scss`, // src: './assets/sass/editor-style.scss',
dest: `${RESOURCES_PATH}/css/modules`, // dest: './assets/css',
}, // sourcemaps: true,
// minify: true,
// },
], ],
// RTL builds. // RTL builds.
rtl: [ rtl: [
@@ -112,7 +114,7 @@ module.exports = {
// SASS Configuration for all builds. // SASS Configuration for all builds.
sass: { sass: {
errLogToConsole: true, errLogToConsole: true,
// outputStyle: 'compact', // outputStyle: 'compact',
}, },
// CSS MQ Packer configuration for all builds and style tasks. // CSS MQ Packer configuration for all builds and style tasks.

View File

@@ -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(),

View File

@@ -1,18 +0,0 @@
import Container, { Inject, Service } from 'typedi';
import { Router } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { PlaidBankingController } from './PlaidBankingController';
@Service()
export class BankingController extends BaseController {
/**
* Router constructor.
*/
router() {
const router = Router();
router.use('/plaid', Container.get(PlaidBankingController).router());
return router;
}
}

View File

@@ -1,53 +0,0 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express';
import BaseController from '@/api/controllers/BaseController';
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
@Service()
export class PlaidBankingController extends BaseController {
@Inject()
private plaidApp: PlaidApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post('/link-token', this.linkToken.bind(this));
router.post('/exchange-token', this.exchangeToken.bind(this));
return router;
}
/**
* Retrieves the Plaid link token.
* @param {Request} req
* @param {response} res
* @returns {Response}
*/
private async linkToken(req: Request, res: Response) {
const { tenantId } = req;
const linkToken = await this.plaidApp.getLinkToken(tenantId);
return res.status(200).send(linkToken);
}
/**
* Exchanges the given public token.
* @param {Request} req
* @param {response} res
* @returns {Response}
*/
public async exchangeToken(req: Request, res: Response) {
const { tenantId } = req;
const { public_token, institution_id } = req.body;
await this.plaidApp.exchangeToken(tenantId, {
institutionId: institution_id,
publicToken: public_token,
});
return res.status(200).send({});
}
}

View File

@@ -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;

View File

@@ -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);
} }

View File

@@ -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),

View File

@@ -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),

View File

@@ -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);
} }

View File

@@ -26,27 +26,27 @@ export default class ContactsController extends BaseController {
[...this.autocompleteQuerySchema], [...this.autocompleteQuerySchema],
this.validationResult, this.validationResult,
this.asyncMiddleware(this.autocompleteContacts.bind(this)), this.asyncMiddleware(this.autocompleteContacts.bind(this)),
this.dynamicListService.handlerErrorsToResponse, this.dynamicListService.handlerErrorsToResponse
); );
router.get( router.get(
'/:id', '/:id',
[param('id').exists().isNumeric().toInt()], [param('id').exists().isNumeric().toInt()],
this.validationResult, this.validationResult,
this.asyncMiddleware(this.getContact.bind(this)), this.asyncMiddleware(this.getContact.bind(this))
); );
router.post( router.post(
'/:id/inactivate', '/:id/inactivate',
[param('id').exists().isNumeric().toInt()], [param('id').exists().isNumeric().toInt()],
this.validationResult, this.validationResult,
this.asyncMiddleware(this.inactivateContact.bind(this)), this.asyncMiddleware(this.inactivateContact.bind(this)),
this.handlerServiceErrors, this.handlerServiceErrors
); );
router.post( router.post(
'/:id/activate', '/:id/activate',
[param('id').exists().isNumeric().toInt()], [param('id').exists().isNumeric().toInt()],
this.validationResult, this.validationResult,
this.asyncMiddleware(this.activateContact.bind(this)), this.asyncMiddleware(this.activateContact.bind(this)),
this.handlerServiceErrors, this.handlerServiceErrors
); );
return router; return router;
} }
@@ -77,7 +77,7 @@ export default class ContactsController extends BaseController {
try { try {
const contact = await this.contactsService.getContact( const contact = await this.contactsService.getContact(
tenantId, tenantId,
contactId, contactId
); );
return res.status(200).send({ return res.status(200).send({
customer: this.transfromToResponse(contact), customer: this.transfromToResponse(contact),
@@ -105,7 +105,7 @@ export default class ContactsController extends BaseController {
try { try {
const contacts = await this.contactsService.autocompleteContacts( const contacts = await this.contactsService.autocompleteContacts(
tenantId, tenantId,
filter, filter
); );
return res.status(200).send({ contacts }); return res.status(200).send({ contacts });
} catch (error) { } catch (error) {
@@ -153,6 +153,7 @@ export default class ContactsController extends BaseController {
check('email') check('email')
.optional({ nullable: true }) .optional({ nullable: true })
.isString() .isString()
.normalizeEmail()
.isEmail() .isEmail()
.isLength({ max: DATATYPES_LENGTH.STRING }), .isLength({ max: DATATYPES_LENGTH.STRING }),
check('website') check('website')
@@ -379,7 +380,7 @@ export default class ContactsController extends BaseController {
error: Error, error: Error,
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction, next: NextFunction
) { ) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (error.errorType === 'contact_not_found') { if (error.errorType === 'contact_not_found') {

View File

@@ -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.',

View File

@@ -1,16 +1,19 @@
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, oneOf } from 'express-validator'; import { check, param, query } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from './BaseController'; import BaseController from './BaseController';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { EchangeRateErrors } from '@/lib/ExchangeRate/types'; import ExchangeRatesService from '@/services/ExchangeRates/ExchangeRatesService';
import { ExchangeRateApplication } from '@/services/ExchangeRates/ExchangeRateApplication'; import DynamicListingService from '@/services/DynamicListing/DynamicListService';
@Service() @Service()
export default class ExchangeRatesController extends BaseController { export default class ExchangeRatesController extends BaseController {
@Inject() @Inject()
private exchangeRatesApp: ExchangeRateApplication; exchangeRatesService: ExchangeRatesService;
@Inject()
dynamicListService: DynamicListingService;
/** /**
* Constructor method. * Constructor method.
@@ -19,40 +22,164 @@ export default class ExchangeRatesController extends BaseController {
const router = Router(); const router = Router();
router.get( router.get(
'/latest', '/',
[ [...this.exchangeRatesListSchema],
oneOf([
query('to_currency').exists().isString().isISO4217(),
query('from_currency').exists().isString().isISO4217(),
]),
],
this.validationResult, this.validationResult,
asyncMiddleware(this.latestExchangeRate.bind(this)), asyncMiddleware(this.exchangeRates.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.handleServiceError,
);
router.post(
'/',
[...this.exchangeRateDTOSchema],
this.validationResult,
asyncMiddleware(this.addExchangeRate.bind(this)),
this.handleServiceError
);
router.post(
'/:id',
[...this.exchangeRateEditDTOSchema, ...this.exchangeRateIdSchema],
this.validationResult,
asyncMiddleware(this.editExchangeRate.bind(this)),
this.handleServiceError
);
router.delete(
'/:id',
[...this.exchangeRateIdSchema],
this.validationResult,
asyncMiddleware(this.deleteExchangeRate.bind(this)),
this.handleServiceError this.handleServiceError
); );
return router; return router;
} }
get exchangeRatesListSchema() {
return [
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']),
];
}
get exchangeRateDTOSchema() {
return [
check('exchange_rate').exists().isNumeric().toFloat(),
check('currency_code').exists().trim().escape(),
check('date').exists().isISO8601(),
];
}
get exchangeRateEditDTOSchema() {
return [check('exchange_rate').exists().isNumeric().toFloat()];
}
get exchangeRateIdSchema() {
return [param('id').isNumeric().toInt()];
}
get exchangeRatesIdsSchema() {
return [
query('ids').isArray({ min: 2 }),
query('ids.*').isNumeric().toInt(),
];
}
/** /**
* Retrieve exchange rates. * Retrieve exchange rates.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
private async latestExchangeRate( async exchangeRates(req: Request, res: Response, next: NextFunction) {
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const exchangeRateQuery = this.matchedQueryData(req); const filter = {
page: 1,
pageSize: 12,
filterRoles: [],
columnSortBy: 'created_at',
sortOrder: 'asc',
...this.matchedQueryData(req),
};
if (filter.stringifiedFilterRoles) {
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
}
try {
const exchangeRates = await this.exchangeRatesService.listExchangeRates(
tenantId,
filter
);
return res.status(200).send({ exchange_rates: exchangeRates });
} catch (error) {
next(error);
}
}
/**
* Adds a new exchange rate on the given date.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async addExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const exchangeRateDTO = this.matchedBodyData(req);
try { try {
const exchangeRate = await this.exchangeRatesApp.latest( const exchangeRate = await this.exchangeRatesService.newExchangeRate(
tenantId, tenantId,
exchangeRateQuery exchangeRateDTO
); );
return res.status(200).send(exchangeRate); return res.status(200).send({ id: exchangeRate.id });
} catch (error) {
next(error);
}
}
/**
* Edit the given exchange rate.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async editExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: exchangeRateId } = req.params;
const exchangeRateDTO = this.matchedBodyData(req);
try {
const exchangeRate = await this.exchangeRatesService.editExchangeRate(
tenantId,
exchangeRateId,
exchangeRateDTO
);
return res.status(200).send({
id: exchangeRateId,
message: 'The exchange rate has been edited successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Delete the given exchange rate from the storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async deleteExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: exchangeRateId } = req.params;
try {
await this.exchangeRatesService.deleteExchangeRate(
tenantId,
exchangeRateId
);
return res.status(200).send({ id: exchangeRateId });
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -65,56 +192,26 @@ export default class ExchangeRatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
private handleServiceError( handleServiceError(
error: Error, error: Error,
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY === error.errorType) { if (error.errorType === 'EXCHANGE_RATE_NOT_FOUND') {
return res.status(400).send({ return res.status(404).send({
errors: [ errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
{
type: EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
code: 100,
message: 'The given base currency is invalid.',
},
],
}); });
} else if ( }
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED === error.errorType if (error.errorType === 'NOT_FOUND_EXCHANGE_RATES') {
) {
return res.status(400).send({ return res.status(400).send({
errors: [ errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 100 }],
{
type: EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
code: 200,
message: 'The service is not allowed',
},
],
}); });
} else if ( }
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED === error.errorType if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') {
) {
return res.status(400).send({ return res.status(400).send({
errors: [ errors: [{ type: 'EXCHANGE.RATE.PERIOD.EXISTS', code: 300 }],
{
type: EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
code: 300,
message: 'The API key is required',
},
],
});
} else if (EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED === error.errorType) {
return res.status(400).send({
errors: [
{
type: EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED,
code: 400,
message: 'The API rate limit has been exceeded',
},
],
}); });
} }
} }

View File

@@ -71,7 +71,6 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF
]); ]);
// Retrieves the json table format. // Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
@@ -99,15 +98,6 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer); return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.APAgingSummaryApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format. // Retrieves the json format.
} else { } else {
const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter); const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter);

View File

@@ -11,7 +11,7 @@ import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class ARAgingSummaryReportController extends BaseFinancialReportController { export default class ARAgingSummaryReportController extends BaseFinancialReportController {
@Inject() @Inject()
private ARAgingSummaryApp: ARAgingSummaryApplication; ARAgingSummaryApp: ARAgingSummaryApplication;
/** /**
* Router constructor. * Router constructor.
@@ -69,7 +69,6 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF
]); ]);
// Retrieves the xlsx format. // Retrieves the xlsx format.
if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
@@ -97,15 +96,6 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Type', 'text/csv');
return res.send(buffer); return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.ARAgingSummaryApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format. // Retrieves the json format.
} else { } else {
const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter); const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter);

View File

@@ -101,7 +101,6 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves the json table format. // Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE == acceptType) { if (ACCEPT_TYPE.APPLICATION_JSON_TABLE == acceptType) {
@@ -129,15 +128,6 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer); return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.balanceSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} else { } else {
const sheet = await this.balanceSheetApp.sheet(tenantId, filter); const sheet = await this.balanceSheetApp.sheet(tenantId, filter);

View File

@@ -79,7 +79,6 @@ export default class CashFlowController extends BaseFinancialReportController {
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF
]); ]);
// Retrieves the json table format. // Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
@@ -107,15 +106,6 @@ export default class CashFlowController extends BaseFinancialReportController {
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer); return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.cashflowSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format. // Retrieves the json format.
} else { } else {
const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter); const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter);

View File

@@ -75,7 +75,6 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves the xlsx format. // Retrieves the xlsx format.
@@ -110,19 +109,6 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia
filter filter
); );
return res.status(200).send(table); return res.status(200).send(table);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const buffer = await this.customerBalanceSummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': buffer.length,
});
return res.send(buffer);
// Retrieves the json format.
} else { } else {
const sheet = await this.customerBalanceSummaryApp.sheet( const sheet = await this.customerBalanceSummaryApp.sheet(
tenantId, tenantId,

View File

@@ -2,21 +2,20 @@ import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import GeneralLedgerService from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerService';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { GeneralLedgerApplication } from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication';
@Service() @Service()
export default class GeneralLedgerReportController extends BaseFinancialReportController { export default class GeneralLedgerReportController extends BaseFinancialReportController {
@Inject() @Inject()
private generalLedgerApplication: GeneralLedgerApplication; generalLedgetService: GeneralLedgerService;
/** /**
* Router constructor. * Router constructor.
*/ */
public router() { router() {
const router = Router(); const router = Router();
router.get( router.get(
@@ -32,7 +31,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
/** /**
* Validation schema. * Validation schema.
*/ */
private get validationSchema(): ValidationChain[] { get validationSchema(): ValidationChain[] {
return [ return [
query('from_date').optional().isISO8601(), query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(), query('to_date').optional().isISO8601(),
@@ -61,56 +60,21 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
private async generalLedger(req: Request, res: Response, next: NextFunction) { async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, settings } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([ try {
ACCEPT_TYPE.APPLICATION_JSON, const { data, query, meta } =
ACCEPT_TYPE.APPLICATION_JSON_TABLE, await this.generalLedgetService.generalLedger(tenantId, filter);
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.generalLedgerApplication.table(tenantId, filter);
return res.status(200).send(table); return res.status(200).send({
// Retrieves the csv format. meta: this.transfromToResponse(meta),
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { data: this.transfromToResponse(data),
const buffer = await this.generalLedgerApplication.csv(tenantId, filter); query: this.transfromToResponse(query),
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.generalLedgerApplication.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.generalLedgerApplication.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
}); });
return res.send(pdfContent); } catch (error) {
// Retrieves the json format. next(error);
} else {
const sheet = await this.generalLedgerApplication.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} }
} }

View File

@@ -96,7 +96,6 @@ export default class InventoryDetailsController extends BaseController {
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves the csv format. // Retrieves the csv format.
if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) { if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) {
@@ -128,15 +127,6 @@ export default class InventoryDetailsController extends BaseController {
filter filter
); );
return res.status(200).send(table); return res.status(200).send(table);
// Retrieves the pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const buffer = await this.inventoryItemDetailsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': buffer.length,
});
return res.send(buffer);
} else { } else {
const sheet = await this.inventoryItemDetailsApp.sheet( const sheet = await this.inventoryItemDetailsApp.sheet(
tenantId, tenantId,

View File

@@ -3,15 +3,14 @@ import { query, ValidationChain } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import InventoryValuationService from '@/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { InventoryValuationSheetApplication } from '@/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class InventoryValuationReportController extends BaseFinancialReportController { export default class InventoryValuationReportController extends BaseFinancialReportController {
@Inject() @Inject()
private inventoryValuationApp: InventoryValuationSheetApplication; inventoryValuationService: InventoryValuationService;
/** /**
* Router constructor. * Router constructor.
@@ -72,55 +71,19 @@ export default class InventoryValuationReportController extends BaseFinancialRep
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req); try {
const { data, query, meta } =
const acceptType = accept.types([ await this.inventoryValuationService.inventoryValuationSheet(
ACCEPT_TYPE.APPLICATION_JSON, tenantId,
ACCEPT_TYPE.APPLICATION_JSON_TABLE, filter
ACCEPT_TYPE.APPLICATION_XLSX, );
ACCEPT_TYPE.APPLICATION_CSV, return res.status(200).send({
ACCEPT_TYPE.APPLICATION_PDF, meta: this.transfromToResponse(meta),
]); data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
// Retrieves the json table format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.inventoryValuationApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV == acceptType) {
const buffer = await this.inventoryValuationApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xslx buffer format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.inventoryValuationApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.inventoryValuationApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
}); });
return res.status(200).send(pdfContent); } catch (error) {
// Retrieves the json format. next(error);
} else {
const { data, query, meta } = await this.inventoryValuationApp.sheet(
tenantId,
filter
);
return res.status(200).send({ meta, data, query });
} }
} }
} }

View File

@@ -3,15 +3,14 @@ import { Request, Response, Router, NextFunction } from 'express';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import { query, oneOf } from 'express-validator'; import { query, oneOf } from 'express-validator';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import JournalSheetService from '@/services/FinancialStatements/JournalSheet/JournalSheetService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { JournalSheetApplication } from '@/services/FinancialStatements/JournalSheet/JournalSheetApplication';
@Service() @Service()
export default class JournalSheetController extends BaseFinancialReportController { export default class JournalSheetController extends BaseFinancialReportController {
@Inject() @Inject()
private journalSheetApp: JournalSheetApplication; journalService: JournalSheetService;
/** /**
* Router constructor. * Router constructor.
@@ -58,58 +57,28 @@ export default class JournalSheetController extends BaseFinancialReportControlle
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
private async journal(req: Request, res: Response, next: NextFunction) { async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId, settings } = req;
let filter = this.matchedQueryData(req); let filter = this.matchedQueryData(req);
filter = { filter = {
...filter, ...filter,
accountsIds: castArray(filter.accountsIds), accountsIds: castArray(filter.accountsIds),
}; };
const accept = this.accepts(req);
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the json table format. try {
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { const { data, query, meta } = await this.journalService.journalSheet(
const table = await this.journalSheetApp.table(tenantId, filter); tenantId,
return res.status(200).send(table); filter
// Retrieves the csv format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.journalSheetApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.journalSheetApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer);
// Retrieves the json format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.journalSheetApp.pdf(tenantId, filter);
res.set({ return res.status(200).send({
'Content-Type': 'application/pdf', data: this.transfromToResponse(data),
'Content-Length': pdfContent.length, query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
}); });
res.send(pdfContent); } catch (error) {
} else { next(error);
const sheet = await this.journalSheetApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} }
} }

View File

@@ -96,7 +96,6 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
try { try {
// Retrieves the csv format. // Retrieves the csv format.
@@ -126,14 +125,6 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
); );
return res.send(sheet); return res.send(sheet);
// Retrieves the json format. // Retrieves the json format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.profitLossSheetApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
} else { } else {
const sheet = await this.profitLossSheetApp.sheet(tenantId, filter); const sheet = await this.profitLossSheetApp.sheet(tenantId, filter);

View File

@@ -1,18 +1,17 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import moment from 'moment';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { PurchasesByItemsService } from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsService'; import PurchasesByItemsService from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { PurcahsesByItemsApplication } from '@/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication';
@Service() @Service()
export default class PurchasesByItemReportController extends BaseFinancialReportController { export default class PurchasesByItemReportController extends BaseFinancialReportController {
@Inject() @Inject()
private purchasesByItemsApp: PurcahsesByItemsApplication; purchasesByItemsService: PurchasesByItemsService;
/** /**
* Router constructor. * Router constructor.
@@ -64,56 +63,20 @@ export default class PurchasesByItemReportController extends BaseFinancialReport
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
public async purchasesByItems(req: Request, res: Response) { async purchasesByItems(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req); try {
const { data, query, meta } =
const acceptType = accept.types([ await this.purchasesByItemsService.purchasesByItems(tenantId, filter);
ACCEPT_TYPE.APPLICATION_JSON, return res.status(200).send({
ACCEPT_TYPE.APPLICATION_JSON_TABLE, meta: this.transfromToResponse(meta),
ACCEPT_TYPE.APPLICATION_XLSX, data: this.transfromToResponse(data),
ACCEPT_TYPE.APPLICATION_CSV, query: this.transfromToResponse(query),
ACCEPT_TYPE.APPLICATION_PDF,
]);
// JSON table response format.
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.purchasesByItemsApp.table(tenantId, filter);
return res.status(200).send(table);
// CSV response format.
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.purchasesByItemsApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Xlsx response format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = await this.purchasesByItemsApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.send(buffer);
// PDF response format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.purchasesByItemsApp.pdf(tenantId, filter);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
}); });
return res.send(pdfContent); } catch (error) {
// Json response format. next(error);
} else {
const sheet = await this.purchasesByItemsApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} }
} }

View File

@@ -1,17 +1,17 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain, ValidationSchema } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import moment from 'moment';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import SalesByItemsReportService from '@/services/FinancialStatements/SalesByItems/SalesByItemsService';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ACCEPT_TYPE } from '@/interfaces/Http';
import { SalesByItemsApplication } from '@/services/FinancialStatements/SalesByItems/SalesByItemsApplication';
@Service() @Service()
export default class SalesByItemsReportController extends BaseFinancialReportController { export default class SalesByItemsReportController extends BaseFinancialReportController {
@Inject() @Inject()
private salesByItemsApp: SalesByItemsApplication; salesByItemsService: SalesByItemsReportService;
/** /**
* Router constructor. * Router constructor.
@@ -24,14 +24,13 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon
CheckPolicies(ReportsAction.READ_SALES_BY_ITEMS, AbilitySubject.Report), CheckPolicies(ReportsAction.READ_SALES_BY_ITEMS, AbilitySubject.Report),
this.validationSchema, this.validationSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.salesByItems.bind(this)) asyncMiddleware(this.purchasesByItems.bind(this))
); );
return router; return router;
} }
/** /**
* Validation schema. * Validation schema.
* @returns {ValidationChain[]}
*/ */
private get validationSchema(): ValidationChain[] { private get validationSchema(): ValidationChain[] {
return [ return [
@@ -61,53 +60,26 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
private async salesByItems(req: Request, res: Response, next: NextFunction) { private async purchasesByItems(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const accept = this.accepts(req);
const acceptType = accept.types([ try {
ACCEPT_TYPE.APPLICATION_JSON, const { data, query, meta } = await this.salesByItemsService.salesByItems(
ACCEPT_TYPE.APPLICATION_JSON_TABLE, tenantId,
ACCEPT_TYPE.APPLICATION_CSV, filter
ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves the csv format.
if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
const buffer = await this.salesByItemsApp.csv(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the json table format.
} else if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
const table = await this.salesByItemsApp.table(tenantId, filter);
return res.status(200).send(table);
// Retrieves the xlsx format.
} else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) {
const buffer = this.salesByItemsApp.xlsx(tenantId, filter);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
); );
return res.send(buffer); return res.status(200).send({
// Retrieves the json format. meta: this.transfromToResponse(meta),
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { data: this.transfromToResponse(data),
const pdfContent = await this.salesByItemsApp.pdf(tenantId, filter); query: this.transfromToResponse(query),
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
}); });
return res.send(pdfContent); } catch (error) {
} else { next(error);
const sheet = await this.salesByItemsApp.sheet(tenantId, filter);
return res.status(200).send(sheet);
} }
} }
} }

View File

@@ -62,7 +62,6 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves the json table format. // Retrieves the json table format.
@@ -98,16 +97,6 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl
return res.send(buffer); return res.send(buffer);
// Retrieves the json format. // Retrieves the json format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.salesTaxLiabilitySummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.status(200).send(pdfContent);
} else { } else {
const sheet = await this.salesTaxLiabilitySummaryApp.sheet( const sheet = await this.salesTaxLiabilitySummaryApp.sheet(
tenantId, tenantId,

View File

@@ -70,7 +70,6 @@ export default class TransactionsByCustomersReportController extends BaseFinanci
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
try { try {
// Retrieves the json table format. // Retrieves the json table format.
@@ -104,16 +103,6 @@ export default class TransactionsByCustomersReportController extends BaseFinanci
); );
return res.send(buffer); return res.send(buffer);
// Retrieve the json format. // Retrieve the json format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.transactionsByCustomersApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
} else { } else {
const sheet = await this.transactionsByCustomersApp.sheet( const sheet = await this.transactionsByCustomersApp.sheet(
tenantId, tenantId,

View File

@@ -71,7 +71,6 @@ export default class TransactionsByVendorsReportController extends BaseFinancial
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves the xlsx format. // Retrieves the xlsx format.
@@ -102,17 +101,6 @@ export default class TransactionsByVendorsReportController extends BaseFinancial
filter filter
); );
return res.status(200).send(table); return res.status(200).send(table);
// Retrieves the pdf format.
} else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.transactionsByVendorsApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format. // Retrieves the json format.
} else { } else {
const sheet = await this.transactionsByVendorsApp.sheet( const sheet = await this.transactionsByVendorsApp.sheet(

View File

@@ -3,6 +3,7 @@ import { Request, Response, Router, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetInjectable';
import BaseFinancialReportController from './BaseFinancialReportController'; import BaseFinancialReportController from './BaseFinancialReportController';
import { AbilitySubject, ReportsAction } from '@/interfaces'; import { AbilitySubject, ReportsAction } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
@@ -80,7 +81,6 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves in json table format. // Retrieves in json table format.
if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) { if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) {
@@ -109,17 +109,6 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Type', 'text/csv');
return res.send(buffer); return res.send(buffer);
// Retrieves in pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.trialBalanceSheetApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves in json format. // Retrieves in json format.
} else { } else {
const { data, query, meta } = await this.trialBalanceSheetApp.sheet( const { data, query, meta } = await this.trialBalanceSheetApp.sheet(

View File

@@ -72,7 +72,6 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_JSON_TABLE,
ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_CSV,
ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_XLSX,
ACCEPT_TYPE.APPLICATION_PDF,
]); ]);
// Retrieves the csv format. // Retrieves the csv format.
@@ -101,17 +100,6 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
filter filter
); );
return res.status(200).send(table); return res.status(200).send(table);
// Retrieves the pdf format.
} else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) {
const pdfContent = await this.vendorBalanceSummaryApp.pdf(
tenantId,
filter
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
return res.send(pdfContent);
// Retrieves the json format. // Retrieves the json format.
} else { } else {
const sheet = await this.vendorBalanceSummaryApp.sheet( const sheet = await this.vendorBalanceSummaryApp.sheet(

View File

@@ -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);
}
}

View File

@@ -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,
});

View File

@@ -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());

View File

@@ -303,7 +303,7 @@ export default class BillsController extends BaseController {
try { try {
const bill = await this.billsApplication.getBill(tenantId, billId); const bill = await this.billsApplication.getBill(tenantId, billId);
return res.status(200).send({ bill }); return res.status(200).send(this.transfromToResponse({ bill }));
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -348,11 +348,14 @@ export default class BillsController extends BaseController {
}; };
try { try {
const billsWithPagination = await this.billsApplication.getBills( const { bills, pagination, filterMeta } =
tenantId, await this.billsApplication.getBills(tenantId, filter);
filter
); return res.status(200).send({
return res.status(200).send(billsWithPagination); bills: this.transfromToResponse(bills),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -560,16 +563,6 @@ export default class BillsController extends BaseController {
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }], errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }],
}); });
} }
if (error.errorType === 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT') {
return res.boom.badRequest(null, {
errors: [
{
type: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT',
code: 2000,
},
],
});
}
} }
next(error); next(error);
} }

View File

@@ -158,11 +158,15 @@ export default class BillsPayments extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req); const { vendorId } = this.matchedQueryData(req);
const entries = await this.billPaymentsPages.getNewPageEntries( try {
tenantId, const entries = await this.billPaymentsPages.getNewPageEntries(
vendorId tenantId,
); vendorId
return res.status(200).send({ entries }); );
return res.status(200).send({
entries: this.transfromToResponse(entries),
});
} catch (error) {}
} }
/** /**
@@ -179,12 +183,16 @@ export default class BillsPayments extends BaseController {
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
try { try {
const billPaymentsWithEditEntries = const { billPayment, entries } =
await this.billPaymentsPages.getBillPaymentEditPage( await this.billPaymentsPages.getBillPaymentEditPage(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
); );
return res.status(200).send(billPaymentsWithEditEntries);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
entries: this.transfromToResponse(entries),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -296,7 +304,9 @@ export default class BillsPayments extends BaseController {
tenantId, tenantId,
billPaymentId billPaymentId
); );
return res.status(200).send({ billPayment }); return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -349,12 +359,17 @@ export default class BillsPayments extends BaseController {
}; };
try { try {
const billPaymentsWithPagination = const { billPayments, pagination, filterMeta } =
await this.billPaymentsApplication.getBillPayments( await this.billPaymentsApplication.getBillPayments(
tenantId, tenantId,
billPaymentsFilter billPaymentsFilter
); );
return res.status(200).send(billPaymentsWithPagination);
return res.status(200).send({
bill_payments: this.transfromToResponse(billPayments),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }

View File

@@ -320,19 +320,20 @@ export default class VendorCreditController extends BaseController {
res: Response, res: Response,
next: NextFunction next: NextFunction
) => { ) => {
const { id: vendorCreditId } = req.params; const { id: billId } = req.params;
const { tenantId, user } = req; const { tenantId, user } = req;
const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req); const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req);
try { try {
await this.editVendorCreditService.editVendorCredit( await this.editVendorCreditService.editVendorCredit(
tenantId, tenantId,
vendorCreditId, billId,
vendorCreditEditDTO vendorCreditEditDTO,
user
); );
return res.status(200).send({ return res.status(200).send({
id: vendorCreditId, id: billId,
message: 'The vendor credit has been edited successfully.', message: 'The vendor credit has been edited successfully.',
}); });
} catch (error) { } catch (error) {

View File

@@ -26,7 +26,6 @@ import GetCreditNoteAssociatedInvoicesToApply from '@/services/CreditNotes/GetCr
import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCreditNoteAssociatedAppliedInvoices'; import GetCreditNoteAssociatedAppliedInvoices from '@/services/CreditNotes/GetCreditNoteAssociatedAppliedInvoices';
import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction'; import GetRefundCreditTransaction from '@/services/CreditNotes/GetRefundCreditNoteTransaction';
import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf'; import GetCreditNotePdf from '../../../services/CreditNotes/GetCreditNotePdf';
import { ACCEPT_TYPE } from '@/interfaces/Http';
/** /**
* Credit notes controller. * Credit notes controller.
* @service * @service
@@ -294,7 +293,7 @@ export default class PaymentReceivesController extends BaseController {
return [ return [
check('from_account_id').exists().isNumeric().toInt(), check('from_account_id').exists().isNumeric().toInt(),
check('description').optional(), check('description').optional(),
check('amount').exists().isNumeric().toFloat(), check('amount').exists().isNumeric().toFloat(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
@@ -439,7 +438,7 @@ export default class PaymentReceivesController extends BaseController {
}; };
/** /**
* Retrieve the credit note details. * Retrieve the payment receive details.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
@@ -452,28 +451,38 @@ export default class PaymentReceivesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const { id: creditNoteId } = req.params; const { id: creditNoteId } = req.params;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
tenantId,
creditNoteId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} else {
const creditNote = await this.getCreditNoteService.getCreditNote( const creditNote = await this.getCreditNoteService.getCreditNote(
tenantId, tenantId,
creditNoteId creditNoteId
); );
return res.status(200).send({ creditNote }); const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
// Response formatter.
res.format({
// Json content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res
.status(200)
.send({ credit_note: this.transfromToResponse(creditNote) });
},
// Pdf content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent = await this.creditNotePdf.getCreditNotePdf(
tenantId,
creditNote
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
}; };

View File

@@ -1,11 +1,10 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query, ValidationChain } from 'express-validator'; import { check, param, query, ValidationChain } from 'express-validator';
import { import {
AbilitySubject, AbilitySubject,
IPaymentReceiveDTO, IPaymentReceiveDTO,
PaymentReceiveAction, PaymentReceiveAction,
PaymentReceiveMailOptsDTO,
} from '@/interfaces'; } from '@/interfaces';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -14,7 +13,6 @@ import DynamicListingService from '@/services/DynamicListing/DynamicListService'
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication'; import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class PaymentReceivesController extends BaseController { export default class PaymentReceivesController extends BaseController {
@@ -119,25 +117,6 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.deletePaymentReceive.bind(this)), asyncMiddleware(this.deletePaymentReceive.bind(this)),
this.handleServiceErrors this.handleServiceErrors
); );
router.post(
'/:id/mail',
[
...this.paymentReceiveValidation,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.sendPaymentReceiveByMail.bind(this),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.paymentReceiveValidation],
asyncMiddleware(this.getPaymentDefaultMail.bind(this)),
this.handleServiceErrors
);
return router; return router;
} }
@@ -349,12 +328,17 @@ export default class PaymentReceivesController extends BaseController {
}; };
try { try {
const paymentsReceivedWithPagination = const { paymentReceives, pagination, filterMeta } =
await this.paymentReceiveApplication.getPaymentReceives( await this.paymentReceiveApplication.getPaymentReceives(
tenantId, tenantId,
filter filter
); );
return res.status(200).send(paymentsReceivedWithPagination);
return res.status(200).send({
payment_receives: this.transfromToResponse(paymentReceives),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -431,34 +415,38 @@ export default class PaymentReceivesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Response in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceiveId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Response in json format.
} else {
const paymentReceive = const paymentReceive =
await this.paymentReceiveApplication.getPaymentReceive( await this.paymentReceiveApplication.getPaymentReceive(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
); );
return res.status(200).send({
payment_receive: paymentReceive, const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send({
payment_receive: this.transfromToResponse(paymentReceive),
});
},
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.paymentReceiveApplication.getPaymentReceivePdf(
tenantId,
paymentReceive
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
}); });
} catch (error) {
next(error);
} }
} }
@@ -492,7 +480,7 @@ export default class PaymentReceivesController extends BaseController {
}; };
/** /**
* Retrieves the sms details of the given payment receive. *
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
@@ -519,74 +507,14 @@ export default class PaymentReceivesController extends BaseController {
} }
}; };
/**
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public sendPaymentReceiveByMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
const paymentMailDTO: PaymentReceiveMailOptsDTO = this.matchedBodyData(
req,
{
includeOptionals: false,
}
);
try {
await this.paymentReceiveApplication.notifyPaymentByMail(
tenantId,
paymentReceiveId,
paymentMailDTO
);
return res.status(200).send({
code: 200,
message: 'The payment notification has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given payment transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getPaymentDefaultMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
try {
const data = await this.paymentReceiveApplication.getPaymentMailOptions(
tenantId,
paymentReceiveId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param error
* @param {Request} req * @param req
* @param {Response} res * @param res
* @param {NextFunction} next * @param next
*/ */
private handleServiceErrors( handleServiceErrors(
error: Error, error: Error,
req: Request, req: Request,
res: Response, res: Response,

View File

@@ -1,11 +1,10 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { import {
AbilitySubject, AbilitySubject,
ISaleEstimateDTO, ISaleEstimateDTO,
SaleEstimateAction, SaleEstimateAction,
SaleEstimateMailOptionsDTO,
} from '@/interfaces'; } from '@/interfaces';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -122,27 +121,6 @@ export default class SalesEstimatesController extends BaseController {
this.handleServiceErrors, this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse this.dynamicListService.handlerErrorsToResponse
); );
router.post(
'/:id/mail',
[
...this.validateSpecificEstimateSchema,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleEstimateMail.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.getSaleEstimateMail.bind(this)),
this.handleServiceErrors
);
return router; return router;
} }
@@ -334,6 +312,7 @@ export default class SalesEstimatesController extends BaseController {
tenantId, tenantId,
estimateId estimateId
); );
return res.status(200).send({ return res.status(200).send({
id: estimateId, id: estimateId,
message: 'The sale estimate has been approved successfully.', message: 'The sale estimate has been approved successfully.',
@@ -362,6 +341,7 @@ export default class SalesEstimatesController extends BaseController {
tenantId, tenantId,
estimateId estimateId
); );
return res.status(200).send({ return res.status(200).send({
id: estimateId, id: estimateId,
message: 'The sale estimate has been rejected successfully.', message: 'The sale estimate has been rejected successfully.',
@@ -381,30 +361,33 @@ export default class SalesEstimatesController extends BaseController {
const { id: estimateId } = req.params; const { id: estimateId } = req.params;
const { tenantId } = req; const { tenantId } = req;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves estimate in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimateId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves estimates in json format.
} else {
const estimate = await this.saleEstimatesApplication.getSaleEstimate( const estimate = await this.saleEstimatesApplication.getSaleEstimate(
tenantId, tenantId,
estimateId estimateId
); );
return res.status(200).send({ estimate }); // Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(this.transfromToResponse({ estimate }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent =
await this.saleEstimatesApplication.getSaleEstimatePdf(
tenantId,
estimate
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
} }
@@ -422,11 +405,22 @@ export default class SalesEstimatesController extends BaseController {
pageSize: 12, pageSize: 12,
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const salesEstimatesWithPagination = const { salesEstimates, pagination, filterMeta } =
await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter); await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter);
return res.status(200).send(salesEstimatesWithPagination); res.format({
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res.status(200).send(
this.transfromToResponse({
salesEstimates,
pagination,
filterMeta,
})
);
},
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -484,65 +478,6 @@ export default class SalesEstimatesController extends BaseController {
} }
}; };
/**
* Send the sale estimate mail.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private sendSaleEstimateMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const saleEstimateDTO: SaleEstimateMailOptionsDTO = this.matchedBodyData(
req,
{
includeOptionals: false,
}
);
try {
await this.saleEstimatesApplication.sendSaleEstimateMail(
tenantId,
invoiceId,
saleEstimateDTO
);
return res.status(200).send({
code: 200,
message: 'The sale estimate mail has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given sale estimate.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private getSaleEstimateMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: invoiceId } = req.params;
try {
const data = await this.saleEstimatesApplication.getSaleEstimateMail(
tenantId,
invoiceId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error

View File

@@ -1,5 +1,5 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
@@ -10,12 +10,14 @@ import {
ISaleInvoiceCreateDTO, ISaleInvoiceCreateDTO,
SaleInvoiceAction, SaleInvoiceAction,
AbilitySubject, AbilitySubject,
SendInvoiceMailDTO,
} from '@/interfaces'; } from '@/interfaces';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication'; import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
};
@Service() @Service()
export default class SaleInvoicesController extends BaseController { export default class SaleInvoicesController extends BaseController {
@Inject() @Inject()
@@ -143,48 +145,6 @@ export default class SaleInvoicesController extends BaseController {
this.handleServiceErrors, this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse this.dynamicListService.handlerErrorsToResponse
); );
router.get(
'/:id/mail-reminder',
this.specificSaleInvoiceValidation,
this.validationResult,
asyncMiddleware(this.getSaleInvoiceMailReminder.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id/mail-reminder',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleInvoiceMailReminder.bind(this)),
this.handleServiceErrors
);
router.post(
'/:id/mail',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleInvoiceMail.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.specificSaleInvoiceValidation],
this.validationResult,
asyncMiddleware(this.getSaleInvoiceMail.bind(this)),
this.handleServiceErrors
);
return router; return router;
} }
@@ -400,6 +360,7 @@ export default class SaleInvoicesController extends BaseController {
saleInvoiceId, saleInvoiceId,
user user
); );
return res.status(200).send({ return res.status(200).send({
id: saleInvoiceId, id: saleInvoiceId,
message: 'The sale invoice has been deleted successfully.', message: 'The sale invoice has been deleted successfully.',
@@ -414,35 +375,43 @@ export default class SaleInvoicesController extends BaseController {
* @param {Request} req - Request object. * @param {Request} req - Request object.
* @param {Response} res - Response object. * @param {Response} res - Response object.
*/ */
private async getSaleInvoice(req: Request, res: Response) { private async getSaleInvoice(
req: Request,
res: Response,
next: NextFunction
) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const { tenantId, user } = req; const { tenantId, user } = req;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves invoice in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves invoice in json format.
} else {
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice( const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
tenantId, tenantId,
saleInvoiceId, saleInvoiceId,
user user
); );
return res.status(200).send({ saleInvoice }); // Response formatter.
res.format({
// JSON content type.
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
return res
.status(200)
.send(this.transfromToResponse({ saleInvoice }));
},
// PDF content type.
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoice
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
} }
/** /**
@@ -465,10 +434,14 @@ export default class SaleInvoicesController extends BaseController {
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const salesInvoicesWithPagination = const { salesInvoices, filterMeta, pagination } =
await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter); await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter);
return res.status(200).send(salesInvoicesWithPagination); return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -495,7 +468,9 @@ export default class SaleInvoicesController extends BaseController {
tenantId, tenantId,
customerId customerId
); );
return res.status(200).send({ salesInvoices }); return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -523,6 +498,7 @@ export default class SaleInvoicesController extends BaseController {
invoiceId, invoiceId,
writeoffDTO writeoffDTO
); );
return res.status(200).send({ return res.status(200).send({
id: saleInvoice.id, id: saleInvoice.id,
message: 'The given sale invoice has been written-off successfully.', message: 'The given sale invoice has been written-off successfully.',
@@ -654,119 +630,6 @@ export default class SaleInvoicesController extends BaseController {
} }
}; };
/**
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async sendSaleInvoiceMail(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleInvoiceApplication.sendSaleInvoiceMail(
tenantId,
invoiceId,
invoiceMailDTO
);
return res.status(200).send({
code: 200,
message: 'The sale invoice mail has been sent successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retreivers the sale invoice reminder options.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleInvoiceMailReminder(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceMailReminder(
tenantId,
invoiceId
);
return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
* Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async sendSaleInvoiceMailReminder(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleInvoiceApplication.sendSaleInvoiceMailReminder(
tenantId,
invoiceId,
invoiceMailDTO
);
return res.status(200).send({
code: 200,
message: 'The sale invoice mail reminder has been sent successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the default mail options of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getSaleInvoiceMail(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: invoiceId } = req.params;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceMail(
tenantId,
invoiceId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
}
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error

View File

@@ -1,19 +1,14 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
ISaleReceiptDTO,
SaleReceiptMailOpts,
SaleReceiptMailOptsDTO,
} from '@/interfaces/SaleReceipt';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService'; import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, SaleReceiptAction } from '@/interfaces'; import { AbilitySubject, SaleReceiptAction } from '@/interfaces';
import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication'; import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication';
import { ACCEPT_TYPE } from '@/interfaces/Http';
@Service() @Service()
export default class SalesReceiptsController extends BaseController { export default class SalesReceiptsController extends BaseController {
@@ -51,27 +46,6 @@ export default class SalesReceiptsController extends BaseController {
this.saleReceiptSmsDetails, this.saleReceiptSmsDetails,
this.handleServiceErrors this.handleServiceErrors
); );
router.post(
'/:id/mail',
[
...this.specificReceiptValidationSchema,
body('subject').isString().optional(),
body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(),
body('attach_receipt').optional().isBoolean().toBoolean(),
],
this.validationResult,
asyncMiddleware(this.sendSaleReceiptMail.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id/mail',
[...this.specificReceiptValidationSchema],
this.validationResult,
asyncMiddleware(this.getSaleReceiptMail.bind(this)),
this.handleServiceErrors
);
router.post( router.post(
'/:id', '/:id',
CheckPolicies(SaleReceiptAction.Edit, AbilitySubject.SaleReceipt), CheckPolicies(SaleReceiptAction.Edit, AbilitySubject.SaleReceipt),
@@ -231,6 +205,7 @@ export default class SalesReceiptsController extends BaseController {
tenantId, tenantId,
saleReceiptId saleReceiptId
); );
return res.status(200).send({ return res.status(200).send({
id: saleReceiptId, id: saleReceiptId,
message: 'Sale receipt has been deleted successfully.', message: 'Sale receipt has been deleted successfully.',
@@ -319,10 +294,15 @@ export default class SalesReceiptsController extends BaseController {
...this.matchedQueryData(req), ...this.matchedQueryData(req),
}; };
try { try {
const salesReceiptsWithPagination = const { data, pagination, filterMeta } =
await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter); await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter);
return res.status(200).send(salesReceiptsWithPagination); const response = this.transfromToResponse({
data,
pagination,
filterMeta,
});
return res.status(200).send(response);
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -334,34 +314,36 @@ export default class SalesReceiptsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
public async getSaleReceipt(req: Request, res: Response) { async getSaleReceipt(req: Request, res: Response, next: NextFunction) {
const { id: saleReceiptId } = req.params; const { id: saleReceiptId } = req.params;
const { tenantId } = req; const { tenantId } = req;
const accept = this.accepts(req); try {
const acceptType = accept.types([
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves receipt in pdf format.
if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceiptId
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
// Retrieves receipt in json format.
} else {
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt( const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
tenantId, tenantId,
saleReceiptId saleReceiptId
); );
return res.status(200).send({ saleReceipt }); res.format({
'application/json': () => {
return res
.status(200)
.send(this.transfromToResponse({ saleReceipt }));
},
'application/pdf': async () => {
const pdfContent =
await this.saleReceiptsApplication.getSaleReceiptPdf(
tenantId,
saleReceipt
);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
},
});
} catch (error) {
next(error);
} }
} }
@@ -423,64 +405,6 @@ export default class SalesReceiptsController extends BaseController {
} }
}; };
/**
* Sends mail notification of the given sale receipt.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public sendSaleReceiptMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: receiptId } = req.params;
const receiptMailDTO: SaleReceiptMailOptsDTO = this.matchedBodyData(req, {
includeOptionals: false,
});
try {
await this.saleReceiptsApplication.sendSaleReceiptMail(
tenantId,
receiptId,
receiptMailDTO
);
return res.status(200).send({
code: 200,
message:
'The sale receipt notification via sms has been sent successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieves the default mail options of the given sale receipt.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getSaleReceiptMail = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: receiptId } = req.params;
try {
const data = await this.saleReceiptsApplication.getSaleReceiptMail(
tenantId,
receiptId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error

View File

@@ -1,47 +0,0 @@
import { Router } from 'express';
import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication';
import { Request, Response } from 'express';
import { Inject, Service } from 'typedi';
import BaseController from '../BaseController';
import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware';
@Service()
export class Webhooks extends BaseController {
@Inject()
private plaidApp: PlaidApplication;
/**
* Router constructor.
*/
router() {
const router = Router();
router.use(PlaidWebhookTenantBootMiddleware);
router.post('/plaid', this.plaidWebhooks.bind(this));
return router;
}
/**
* Listens to Plaid webhooks.
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
public async plaidWebhooks(req: Request, res: Response) {
const { tenantId } = req;
const {
webhook_type: webhookType,
webhook_code: webhookCode,
item_id: plaidItemId,
} = req.body;
await this.plaidApp.webhooks(
tenantId,
plaidItemId,
webhookType,
webhookCode
);
return res.status(200).send({ code: 200, message: 'ok' });
}
}

View File

@@ -56,9 +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 { Webhooks } from './controllers/Webhooks/Webhooks';
export default () => { export default () => {
const app = Router(); const app = Router();
@@ -74,7 +71,6 @@ export default () => {
app.use('/ping', Container.get(Ping).router()); app.use('/ping', Container.get(Ping).router());
app.use('/jobs', Container.get(Jobs).router()); app.use('/jobs', Container.get(Jobs).router());
app.use('/account', Container.get(Account).router()); app.use('/account', Container.get(Account).router());
app.use('/webhooks', Container.get(Webhooks).router());
// - Dashboard routes. // - Dashboard routes.
// --------------------------- // ---------------------------
@@ -122,7 +118,6 @@ export default () => {
Container.get(InventoryItemsCostController).router() Container.get(InventoryItemsCostController).router()
); );
dashboard.use('/cashflow', Container.get(CashflowController).router()); dashboard.use('/cashflow', Container.get(CashflowController).router());
dashboard.use('/banking', Container.get(BankingController).router());
dashboard.use('/roles', Container.get(RolesController).router()); dashboard.use('/roles', Container.get(RolesController).router());
dashboard.use( dashboard.use(
'/transactions-locking', '/transactions-locking',
@@ -136,9 +131,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());

View File

@@ -58,7 +58,6 @@ module.exports = {
secure: !!parseInt(process.env.MAIL_SECURE, 10), secure: !!parseInt(process.env.MAIL_SECURE, 10),
username: process.env.MAIL_USERNAME, username: process.env.MAIL_USERNAME,
password: process.env.MAIL_PASSWORD, password: process.env.MAIL_PASSWORD,
from: process.env.MAIL_FROM_ADDRESS,
}, },
/** /**
@@ -169,27 +168,4 @@ module.exports = {
* to application detarmines to upgrade. * to application detarmines to upgrade.
*/ */
databaseBatch: 4, databaseBatch: 4,
/**
* Exchange rate.
*/
exchangeRate: {
service: 'open-exchange-rate',
openExchangeRate: {
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
},
},
/**
* Plaid.
*/
plaid: {
env: process.env.PLAID_ENV || 'sandbox',
clientId: process.env.PLAID_CLIENT_ID,
secretDevelopment: process.env.PLAID_SECRET_DEVELOPMENT,
secretSandbox: process.env.PLAID_SECRET_SANDBOX,
redirectSandBox: process.env.PLAID_SANDBOX_REDIRECT_URI,
redirectDevelopment: process.env.PLAID_DEVELOPMENT_REDIRECT_URI,
linkWebhook: process.env.PLAID_LINK_WEBHOOK
},
}; };

View File

@@ -136,11 +136,6 @@ export default {
type: 'number', type: 'number',
}, },
}, },
inventory: {
cost_compute_running: {
type: 'boolean',
},
},
accounts: { accounts: {
account_code_required: { account_code_required: {
type: 'boolean', type: 'boolean',

View File

@@ -1,14 +0,0 @@
exports.up = function (knex) {
return knex.schema.createTable('plaid_items', (table) => {
table.increments('id');
table.integer('tenant_id').unsigned();
table.string('plaid_item_id');
table.string('plaid_institution_id');
table.string('plaid_access_token');
table.string('last_cursor');
table.string('status');
table.timestamps();
});
};
exports.down = function (knex) {};

View File

@@ -1,9 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('accounts', (table) => {
table.string('plaid_account_id');
table.string('account_mask').nullable();
table.decimal('bank_balance', 15, 5);
});
};
exports.down = function (knex) {};

View File

@@ -1,7 +0,0 @@
exports.up = function (knex) {
return knex.schema.table('cashflow_transactions', (table) => {
table.string('plaid_transaction_id');
});
};
exports.down = function (knex) {};

View File

@@ -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');
};

View File

@@ -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) {};

View File

@@ -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');
});
};

View File

@@ -1,11 +1,13 @@
import { import {
IAgingPeriod, IAgingPeriod,
IAgingPeriodTotal,
IAgingAmount,
IAgingSummaryQuery, IAgingSummaryQuery,
IAgingSummaryTotal, IAgingSummaryTotal,
IAgingSummaryContact, IAgingSummaryContact,
IAgingSummaryData, IAgingSummaryData,
} from './AgingReport'; } from './AgingReport';
import { IFinancialSheetCommonMeta } from './FinancialStatements'; import { INumberFormatQuery } from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
export interface IAPAgingSummaryQuery extends IAgingSummaryQuery { export interface IAPAgingSummaryQuery extends IAgingSummaryQuery {
@@ -24,22 +26,17 @@ export interface IAPAgingSummaryData extends IAgingSummaryData {
export type IAPAgingSummaryColumns = IAgingPeriod[]; export type IAPAgingSummaryColumns = IAgingPeriod[];
export interface IARAgingSummaryMeta extends IFinancialSheetCommonMeta { export interface IARAgingSummaryMeta {
formattedAsDate: string; baseCurrency: string;
organizationName: string;
} }
export interface IAPAgingSummaryMeta extends IFinancialSheetCommonMeta { export interface IAPAgingSummaryMeta {
formattedAsDate: string; baseCurrency: string;
organizationName: string;
} }
export interface IAPAgingSummaryTable extends IFinancialTable { export interface IAPAgingSummaryTable extends IFinancialTable {
query: IAPAgingSummaryQuery; query: IAPAgingSummaryQuery;
meta: IAPAgingSummaryMeta; meta: IAPAgingSummaryMeta;
} }
export interface IAPAgingSummarySheet {
data: IAPAgingSummaryData;
meta: IAPAgingSummaryMeta;
query: IAPAgingSummaryQuery;
columns: any;
}

View File

@@ -32,11 +32,3 @@ export interface IARAgingSummaryTable extends IFinancialTable {
meta: IARAgingSummaryMeta; meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery; query: IARAgingSummaryQuery;
} }
export interface IARAgingSummarySheet {
data: IARAgingSummaryData;
meta: IARAgingSummaryMeta;
query: IARAgingSummaryQuery;
columns: IARAgingSummaryColumns;
}

View File

@@ -6,15 +6,12 @@ export interface IAccountDTO {
code: string; code: string;
description: string; description: string;
accountType: string; accountType: string;
parentAccountId?: number; parentAccountId: number;
active: boolean; active: boolean;
bankBalance?: number;
accountMask?: string;
} }
export interface IAccountCreateDTO extends IAccountDTO { export interface IAccountCreateDTO extends IAccountDTO {
currencyCode?: string; currencyCode?: string;
plaidAccountId?: string;
} }
export interface IAccountEditDTO extends IAccountDTO {} export interface IAccountEditDTO extends IAccountDTO {}
@@ -36,7 +33,6 @@ export interface IAccount {
type?: any[]; type?: any[];
accountNormal: string; accountNormal: string;
accountParentType: string; accountParentType: string;
bankBalance: string;
} }
export enum AccountNormal { export enum AccountNormal {
@@ -158,9 +154,10 @@ export enum AccountAction {
TransactionsLocking = 'TransactionsLocking', TransactionsLocking = 'TransactionsLocking',
} }
export enum TaxRateAction { export enum TaxRateAction {
CREATE = 'Create', CREATE = 'Create',
EDIT = 'Edit', EDIT = 'Edit',
DELETE = 'Delete', DELETE = 'Delete',
VIEW = 'View', VIEW = 'View',
} }

View File

@@ -1,7 +1,5 @@
import {
IFinancialSheetCommonMeta, import { INumberFormatQuery } from './FinancialStatements';
INumberFormatQuery,
} from './FinancialStatements';
export interface IAgingPeriodTotal extends IAgingPeriod { export interface IAgingPeriodTotal extends IAgingPeriod {
total: IAgingAmount; total: IAgingAmount;
@@ -44,8 +42,3 @@ export interface IAgingSummaryTotal {
export interface IAgingSummaryData { export interface IAgingSummaryData {
total: IAgingSummaryTotal; total: IAgingSummaryTotal;
} }
export interface IAgingSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}

View File

@@ -2,7 +2,6 @@ import {
INumberFormatQuery, INumberFormatQuery,
IFormatNumberSettings, IFormatNumberSettings,
IFinancialSheetBranchesQuery, IFinancialSheetBranchesQuery,
IFinancialSheetCommonMeta,
} from './FinancialStatements'; } from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
@@ -64,9 +63,10 @@ export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
} }
// Balance sheet meta. // Balance sheet meta.
export interface IBalanceSheetMeta extends IFinancialSheetCommonMeta { export interface IBalanceSheetMeta {
formattedAsDate: string; isCostComputeRunning: boolean;
formattedDateRange: string; organizationName: string;
baseCurrency: string;
} }
export interface IBalanceSheetFormatNumberSettings export interface IBalanceSheetFormatNumberSettings

View File

@@ -1,7 +1,4 @@
import { import { INumberFormatQuery } from './FinancialStatements';
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
import { IAccount } from './Account'; import { IAccount } from './Account';
import { ILedger } from './Ledger'; import { ILedger } from './Ledger';
import { IFinancialTable, ITableRow } from './Table'; import { IFinancialTable, ITableRow } from './Table';
@@ -82,8 +79,8 @@ export interface ICashFlowStatementAggregateSection
export interface ICashFlowCashBeginningNode export interface ICashFlowCashBeginningNode
extends ICashFlowStatementCommonSection { extends ICashFlowStatementCommonSection {
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING; sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING;
} }
export type ICashFlowStatementSection = export type ICashFlowStatementSection =
| ICashFlowStatementAccountSection | ICashFlowStatementAccountSection
@@ -92,10 +89,10 @@ export type ICashFlowStatementSection =
| ICashFlowStatementCommonSection; | ICashFlowStatementCommonSection;
export interface ICashFlowStatementColumn {} export interface ICashFlowStatementColumn {}
export interface ICashFlowStatementMeta extends IFinancialSheetCommonMeta { export interface ICashFlowStatementMeta {
formattedToDate: string; isCostComputeRunning: boolean;
formattedFromDate: string; organizationName: string;
formattedDateRange: string; baseCurrency: string;
} }
export interface ICashFlowStatementDOO { export interface ICashFlowStatementDOO {
@@ -233,38 +230,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;
}

View File

@@ -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;
@@ -46,13 +45,9 @@ export interface ICashflowCommandDTO {
publish: boolean; publish: boolean;
branchId?: number; branchId?: number;
plaidTransactionId?: string;
} }
export interface ICashflowNewCommandDTO extends ICashflowCommandDTO { export interface ICashflowNewCommandDTO extends ICashflowCommandDTO {}
plaidAccountId?: string;
uncategorizedTransactionId?: number;
}
export interface ICashflowTransaction { export interface ICashflowTransaction {
id?: number; id?: number;
@@ -84,8 +79,6 @@ export interface ICashflowTransaction {
isCashDebit?: boolean; isCashDebit?: boolean;
isCashCredit?: boolean; isCashCredit?: boolean;
uncategorizedTransactionId?: number;
} }
export interface ICashflowTransactionLine { export interface ICashflowTransactionLine {
@@ -128,39 +121,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;
}

View File

@@ -4,7 +4,6 @@ import {
IContactBalanceSummaryPercentage, IContactBalanceSummaryPercentage,
IContactBalanceSummaryTotal, IContactBalanceSummaryTotal,
} from './ContactBalanceSummary'; } from './ContactBalanceSummary';
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
export interface ICustomerBalanceSummaryQuery export interface ICustomerBalanceSummaryQuery
@@ -36,15 +35,9 @@ export interface ICustomerBalanceSummaryData {
total: ICustomerBalanceSummaryTotal; total: ICustomerBalanceSummaryTotal;
} }
export interface ICustomerBalanceSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}
export interface ICustomerBalanceSummaryStatement { export interface ICustomerBalanceSummaryStatement {
data: ICustomerBalanceSummaryData; data: ICustomerBalanceSummaryData;
query: ICustomerBalanceSummaryQuery; query: ICustomerBalanceSummaryQuery;
meta: ICustomerBalanceSummaryMeta;
} }
export interface ICustomerBalanceSummaryService { export interface ICustomerBalanceSummaryService {
@@ -56,5 +49,4 @@ export interface ICustomerBalanceSummaryService {
export interface ICustomerBalanceSummaryTable extends IFinancialTable { export interface ICustomerBalanceSummaryTable extends IFinancialTable {
query: ICustomerBalanceSummaryQuery; query: ICustomerBalanceSummaryQuery;
meta: ICustomerBalanceSummaryMeta;
} }

View File

@@ -1,10 +1,36 @@
export interface ExchangeRateLatestDTO { import { IFilterRole } from './DynamicFilter';
toCurrency: string;
fromCurrency: string;
}
export interface EchangeRateLatestPOJO { export interface IExchangeRate {
baseCurrency: string; id: number,
toCurrency: string; currencyCode: string,
exchangeRate: number; exchangeRate: number,
} date: Date,
createdAt: Date,
updatedAt: Date,
};
export interface IExchangeRateDTO {
currencyCode: string,
exchangeRate: number,
date: Date,
};
export interface IExchangeRateEditDTO {
exchangeRate: number,
};
export interface IExchangeRateFilter {
page: number,
pageSize: number,
filterRoles?: IFilterRole[];
columnSortBy: string;
sortOrder: string;
};
export interface IExchangeRatesService {
newExchangeRate(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise<IExchangeRate>;
editExchangeRate(tenantId: number, exchangeRateId: number, editExRateDTO: IExchangeRateEditDTO): Promise<void>;
deleteExchangeRate(tenantId: number, exchangeRateId: number): Promise<void>;
listExchangeRates(tenantId: number, exchangeRateFilter: IExchangeRateFilter): Promise<void>;
};

View File

@@ -42,13 +42,4 @@ export enum ReportsAction {
export interface IFinancialSheetBranchesQuery { export interface IFinancialSheetBranchesQuery {
branchesIds?: number[]; branchesIds?: number[];
} }
export interface IFinancialSheetCommonMeta {
organizationName: string;
baseCurrency: string;
dateFormat: string;
isCostComputeRunning: boolean;
sheetName: string;
}

View File

@@ -1,90 +1,81 @@
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IGeneralLedgerSheetQuery { export interface IGeneralLedgerSheetQuery {
fromDate: Date | string; fromDate: Date | string,
toDate: Date | string; toDate: Date | string,
basis: string; basis: string,
numberFormat: { numberFormat: {
noCents: boolean; noCents: boolean,
divideOn1000: boolean; divideOn1000: boolean,
}; },
noneTransactions: boolean; noneTransactions: boolean,
accountsIds: number[]; accountsIds: number[],
branchesIds?: number[]; branchesIds?: number[];
} };
export interface IGeneralLedgerSheetAccountTransaction { export interface IGeneralLedgerSheetAccountTransaction {
id: number; id: number,
amount: number; amount: number,
runningBalance: number; runningBalance: number,
credit: number; credit: number,
debit: number; debit: number,
formattedAmount: string; formattedAmount: string,
formattedCredit: string; formattedCredit: string,
formattedDebit: string; formattedDebit: string,
formattedRunningBalance: string; formattedRunningBalance: string,
currencyCode: string; currencyCode: string,
note?: string; note?: string,
transactionType?: string; transactionType?: string,
transactionNumber: string; transactionNumber: string,
referenceId?: number; referenceId?: number,
referenceType?: string; referenceType?: string,
date: Date | string; date: Date|string,
dateFormatted: string; };
}
export interface IGeneralLedgerSheetAccountBalance { export interface IGeneralLedgerSheetAccountBalance {
date: Date | string; date: Date|string,
amount: number; amount: number,
formattedAmount: string; formattedAmount: string,
currencyCode: string; currencyCode: string,
} }
export interface IGeneralLedgerSheetAccount { export interface IGeneralLedgerSheetAccount {
id: number; id: number,
name: string; name: string,
code: string; code: string,
index: number; index: number,
parentAccountId: number; parentAccountId: number,
transactions: IGeneralLedgerSheetAccountTransaction[]; transactions: IGeneralLedgerSheetAccountTransaction[],
openingBalance: IGeneralLedgerSheetAccountBalance; openingBalance: IGeneralLedgerSheetAccountBalance,
closingBalance: IGeneralLedgerSheetAccountBalance; closingBalance: IGeneralLedgerSheetAccountBalance,
} }
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
export interface IAccountTransaction { export interface IAccountTransaction {
id: number; id: number,
index: number; index: number,
draft: boolean; draft: boolean,
note: string; note: string,
accountId: number; accountId: number,
transactionType: string; transactionType: string,
referenceType: string; referenceType: string,
referenceId: number; referenceId: number,
contactId: number; contactId: number,
contactType: string; contactType: string,
credit: number; credit: number,
debit: number; debit: number,
date: string | Date; date: string|Date,
createdAt: string | Date; createdAt: string|Date,
updatedAt: string | Date; updatedAt: string|Date,
} }
export interface IGeneralLedgerMeta extends IFinancialSheetCommonMeta { export interface IGeneralLedgerMeta {
formattedFromDate: string; isCostComputeRunning: boolean,
formattedToDate: string; organizationName: string,
formattedDateRange: string; baseCurrency: string,
} };
export interface IGeneralLedgerTableData extends IFinancialTable {
meta: IGeneralLedgerMeta;
query: IGeneralLedgerSheetQuery;
}

View File

@@ -1,8 +1,4 @@
import { import { INumberFormatQuery } from './FinancialStatements';
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IInventoryValuationReportQuery { export interface IInventoryValuationReportQuery {
asDate: Date | string; asDate: Date | string;
@@ -16,10 +12,10 @@ export interface IInventoryValuationReportQuery {
branchesIds?: number[]; branchesIds?: number[];
} }
export interface IInventoryValuationSheetMeta export interface IInventoryValuationSheetMeta {
extends IFinancialSheetCommonMeta { organizationName: string;
formattedAsDate: string; baseCurrency: string;
formattedDateRange: string; isCostComputeRunning: boolean;
} }
export interface IInventoryValuationItem { export interface IInventoryValuationItem {
@@ -43,19 +39,9 @@ export interface IInventoryValuationTotal {
quantityFormatted: string; quantityFormatted: string;
} }
export type IInventoryValuationStatement = { export type IInventoryValuationStatement =
items: IInventoryValuationItem[]; | {
total: IInventoryValuationTotal; items: IInventoryValuationItem[];
}; total: IInventoryValuationTotal;
export type IInventoryValuationSheetData = IInventoryValuationStatement; }
| {};
export interface IInventoryValuationSheet {
data: IInventoryValuationStatement;
meta: IInventoryValuationSheetMeta;
query: IInventoryValuationReportQuery;
}
export interface IInventoryValuationTable extends IFinancialTable {
meta: IInventoryValuationSheetMeta;
query: IInventoryValuationReportQuery;
}

View File

@@ -1,7 +1,4 @@
import { import { INumberFormatQuery } from './FinancialStatements';
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
export interface IInventoryDetailsQuery { export interface IInventoryDetailsQuery {
@@ -82,10 +79,10 @@ export type IInventoryDetailsNode =
| IInventoryDetailsItemTransaction; | IInventoryDetailsItemTransaction;
export type IInventoryDetailsData = IInventoryDetailsItem[]; export type IInventoryDetailsData = IInventoryDetailsItem[];
export interface IInventoryItemDetailMeta extends IFinancialSheetCommonMeta { export interface IInventoryItemDetailMeta {
formattedFromDate: string; isCostComputeRunning: boolean;
formattedToDay: string; organizationName: string;
formattedDateRange: string; baseCurrency: string;
} }
export interface IInvetoryItemDetailDOO { export interface IInvetoryItemDetailDOO {

View File

@@ -1,53 +1,36 @@
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IJournalEntry } from './Journal'; import { IJournalEntry } from './Journal';
import { IFinancialTable } from './Table';
export interface IJournalReportQuery { export interface IJournalReportQuery {
fromDate: Date | string; fromDate: Date | string,
toDate: Date | string; toDate: Date | string,
numberFormat: { numberFormat: {
noCents: boolean; noCents: boolean,
divideOn1000: boolean; divideOn1000: boolean,
}; },
transactionType: string; transactionType: string,
transactionId: string; transactionId: string,
accountsIds: number | number[]; accountsIds: number | number[],
fromRange: number; fromRange: number,
toRange: number; toRange: number,
} }
export interface IJournalReportEntriesGroup { export interface IJournalReportEntriesGroup {
id: string; id: string,
date: Date; entries: IJournalEntry[],
dateFormatted: string; currencyCode: string,
entries: IJournalEntry[]; credit: number,
currencyCode: string; debit: number,
credit: number; formattedCredit: string,
debit: number; formattedDebit: string,
formattedCredit: string;
formattedDebit: string;
} }
export interface IJournalReport { export interface IJournalReport {
entries: IJournalReportEntriesGroup[]; entries: IJournalReportEntriesGroup[],
} }
export interface IJournalSheetMeta extends IFinancialSheetCommonMeta { export interface IJournalSheetMeta {
formattedDateRange: string; isCostComputeRunning: boolean,
formattedFromDate: string; organizationName: string,
formattedToDate: string; baseCurrency: string,
} }
export interface IJournalTable extends IFinancialTable {
query: IJournalReportQuery;
meta: IJournalSheetMeta;
}
export type IJournalTableData = IJournalReportEntriesGroup[];
export interface IJournalSheet {
data: IJournalTableData;
query: IJournalReportQuery;
meta: IJournalSheetMeta;
}

View File

@@ -1,17 +1,9 @@
export type IMailAttachment = MailAttachmentPath | MailAttachmentContent;
export interface MailAttachmentPath {
filename: string;
path: string;
cid: string;
}
export interface MailAttachmentContent {
filename: string;
content: Buffer;
}
export interface IMailable { export interface IMailable {
constructor(view: string, data?: { [key: string]: string | number }); constructor(
view: string,
data?: { [key: string]: string | number },
);
send(): Promise<any>; send(): Promise<any>;
build(): void; build(): void;
setData(data: { [key: string]: string | number }): IMailable; setData(data: { [key: string]: string | number }): IMailable;
@@ -21,27 +13,4 @@ export interface IMailable {
setView(view: string): IMailable; setView(view: string): IMailable;
render(data?: { [key: string]: string | number }): string; render(data?: { [key: string]: string | number }): string;
getViewContent(): string; getViewContent(): string;
} }
export interface AddressItem {
label: string;
mail: string;
primary?: boolean;
}
export interface CommonMailOptions {
toAddresses: AddressItem[];
fromAddresses: AddressItem[];
from: string;
to: string | string[];
subject: string;
body: string;
data?: Record<string, any>;
}
export interface CommonMailOptionsDTO {
to?: string | string[];
from?: string;
subject?: string;
body?: string;
}

View File

@@ -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
);

View File

@@ -1,9 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { import { ISystemUser } from '@/interfaces';
CommonMailOptions,
CommonMailOptionsDTO,
ISystemUser,
} from '@/interfaces';
import { ILedgerEntry } from './Ledger'; import { ILedgerEntry } from './Ledger';
import { ISaleInvoice } from './SaleInvoice'; import { ISaleInvoice } from './SaleInvoice';
@@ -23,7 +19,7 @@ export interface IPaymentReceive {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
localAmount?: number; localAmount?: number;
branchId?: number; branchId?: number
} }
export interface IPaymentReceiveCreateDTO { export interface IPaymentReceiveCreateDTO {
customerId: number; customerId: number;
@@ -169,13 +165,3 @@ export type IPaymentReceiveGLCommonEntry = Pick<
| 'createdAt' | 'createdAt'
| 'branchId' | 'branchId'
>; >;
export interface PaymentReceiveMailOpts extends CommonMailOptions {}
export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {}
export interface PaymentReceiveMailPresendEvent {
tenantId: number;
paymentReceiveId: number;
messageOptions: PaymentReceiveMailOptsDTO;
}

View File

@@ -1,56 +0,0 @@
export interface IPlaidItemCreatedEventPayload {
tenantId: number;
plaidAccessToken: string;
plaidItemId: string;
plaidInstitutionId: string;
}
export interface PlaidItemDTO {
publicToken: string;
institutionId: string;
}
export interface PlaidAccount {
account_id: string;
balances: {
available: number;
current: number;
iso_currency_code: string;
limit: null;
unofficial_currency_code: null;
};
mask: string;
name: string;
official_name: string;
persistent_account_id: string;
subtype: string;
type: string;
}
export interface PlaidTransaction {
date: string;
account_id: string;
amount: number;
authorized_date: string;
name: string;
category: string[];
check_number: number | null;
iso_currency_code: string;
transaction_id: string;
transaction_type: string;
payment_meta: { reference_number: string | null; payee: string | null };
}
export interface PlaidFetchedTransactionsUpdates {
added: any[];
modified: any[];
removed: any[];
accessToken: string;
cursor: string;
}
export interface SyncAccountsTransactionsTask {
tenantId: number;
plaidAccountId: number;
plaidTransactions: PlaidTransaction[];
}

View File

@@ -1,6 +1,5 @@
import { import {
IFinancialSheetBranchesQuery, IFinancialSheetBranchesQuery,
IFinancialSheetCommonMeta,
INumberFormatQuery, INumberFormatQuery,
} from './FinancialStatements'; } from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
@@ -135,10 +134,10 @@ export type IProfitLossSheetNode =
| IProfitLossSheetEquationNode | IProfitLossSheetEquationNode
| IProfitLossSheetAccountNode; | IProfitLossSheetAccountNode;
export interface IProfitLossSheetMeta extends IFinancialSheetCommonMeta{ export interface IProfitLossSheetMeta {
formattedDateRange: string; isCostComputeRunning: boolean;
formattedFromDate: string; organizationName: string;
formattedToDate: string; baseCurrency: string;
} }
// ------------------------------------------------ // ------------------------------------------------

View File

@@ -1,58 +0,0 @@
import {
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface IPurchasesByItemsReportQuery {
fromDate: Date | string;
toDate: Date | string;
itemsIds: number[];
numberFormat: INumberFormatQuery;
noneTransactions: boolean;
onlyActive: boolean;
}
export interface IPurchasesByItemsSheetMeta extends IFinancialSheetCommonMeta {
formattedFromDate: string;
formattedToDate: string;
formattedDateRange: string;
}
export interface IPurchasesByItemsItem {
id: number;
name: string;
code: string;
quantitySold: number;
soldCost: number;
averageSellPrice: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
averageSellPriceFormatted: string;
currencyCode: string;
}
export interface IPurchasesByItemsTotal {
quantitySold: number;
soldCost: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
currencyCode: string;
}
export type IPurchasesByItemsSheetData = {
items: IPurchasesByItemsItem[];
total: IPurchasesByItemsTotal;
};
export interface IPurchasesByItemsSheet {
data: IPurchasesByItemsSheetData;
query: IPurchasesByItemsReportQuery;
meta: IPurchasesByItemsSheetMeta;
}
export interface IPurchasesByItemsTable extends IFinancialTable {
query: IPurchasesByItemsReportQuery;
meta: IPurchasesByItemsSheetMeta;
}

View File

@@ -1,7 +1,6 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter'; import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
export interface ISaleEstimate { export interface ISaleEstimate {
id?: number; id?: number;
@@ -125,17 +124,3 @@ export interface ISaleEstimateApprovedEvent {
saleEstimate: ISaleEstimate; saleEstimate: ISaleEstimate;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface SaleEstimateMailOptions extends CommonMailOptions {
attachEstimate?: boolean;
}
export interface SaleEstimateMailOptionsDTO extends CommonMailOptionsDTO {
attachEstimate?: boolean;
}
export interface ISaleEstimateMailPresendEvent {
tenantId: number;
saleEstimateId: number;
messageOptions: SaleEstimateMailOptionsDTO;
}

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces'; import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
import { IDynamicListFilter } from '@/interfaces/DynamicFilter'; import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
@@ -187,29 +186,3 @@ export enum SaleInvoiceAction {
Writeoff = 'Writeoff', Writeoff = 'Writeoff',
NotifyBySms = 'NotifyBySms', NotifyBySms = 'NotifyBySms',
} }
export interface SaleInvoiceMailOptions extends CommonMailOptions {
attachInvoice: boolean;
}
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
attachInvoice?: boolean;
}
export interface ISaleInvoiceNotifyPayload {
tenantId: number;
saleInvoiceId: number;
messageDTO: SendInvoiceMailDTO;
}
export interface ISaleInvoiceMailSend {
tenantId: number;
saleInvoiceId: number;
messageOptions: SendInvoiceMailDTO;
}
export interface ISaleInvoiceMailSent {
tenantId: number;
saleInvoiceId: number;
messageOptions: SendInvoiceMailDTO;
}

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IItemEntry } from './ItemEntry'; import { IItemEntry } from './ItemEntry';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
export interface ISaleReceipt { export interface ISaleReceipt {
id?: number; id?: number;
@@ -135,17 +134,3 @@ export interface ISaleReceiptDeletingPayload {
oldSaleReceipt: ISaleReceipt; oldSaleReceipt: ISaleReceipt;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface SaleReceiptMailOpts extends CommonMailOptions {
attachReceipt: boolean;
}
export interface SaleReceiptMailOptsDTO extends CommonMailOptionsDTO {
attachReceipt?: boolean;
}
export interface ISaleReceiptMailPresend {
tenantId: number;
saleReceiptId: number;
messageOptions: SaleReceiptMailOptsDTO;
}

View File

@@ -1,58 +1,45 @@
import { import {
IFinancialSheetCommonMeta,
INumberFormatQuery, INumberFormatQuery,
} from './FinancialStatements'; } from './FinancialStatements';
import { IFinancialTable } from './Table';
export interface ISalesByItemsReportQuery { export interface ISalesByItemsReportQuery {
fromDate: Date | string; fromDate: Date | string;
toDate: Date | string; toDate: Date | string;
itemsIds: number[]; itemsIds: number[],
numberFormat: INumberFormatQuery; numberFormat: INumberFormatQuery;
noneTransactions: boolean; noneTransactions: boolean;
onlyActive: boolean; onlyActive: boolean;
}
export interface ISalesByItemsSheetMeta extends IFinancialSheetCommonMeta {
formattedFromDate: string;
formattedToDate: string;
formattedDateRange: string;
}
export interface ISalesByItemsItem {
id: number;
name: string;
code: string;
quantitySold: number;
soldCost: number;
averageSellPrice: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
averageSellPriceFormatted: string;
currencyCode: string;
}
export interface ISalesByItemsTotal {
quantitySold: number;
soldCost: number;
quantitySoldFormatted: string;
soldCostFormatted: string;
currencyCode: string;
}
export type ISalesByItemsSheetData = {
items: ISalesByItemsItem[];
total: ISalesByItemsTotal;
}; };
export interface ISalesByItemsSheet { export interface ISalesByItemsSheetMeta {
data: ISalesByItemsSheetData; organizationName: string,
query: ISalesByItemsReportQuery; baseCurrency: string,
meta: ISalesByItemsSheetMeta; };
}
export interface ISalesByItemsItem {
id: number,
name: string,
code: string,
quantitySold: number,
soldCost: number,
averageSellPrice: number,
quantitySoldFormatted: string,
soldCostFormatted: string,
averageSellPriceFormatted: string,
currencyCode: string,
};
export interface ISalesByItemsTotal {
quantitySold: number,
soldCost: number,
quantitySoldFormatted: string,
soldCostFormatted: string,
currencyCode: string,
};
export type ISalesByItemsSheetStatement = {
items: ISalesByItemsItem[],
total: ISalesByItemsTotal
} | {};
export interface ISalesByItemsTable extends IFinancialTable {
query: ISalesByItemsReportQuery;
meta: ISalesByItemsSheetMeta;
}

View File

@@ -1,4 +1,3 @@
import { IFinancialSheetCommonMeta } from "./FinancialStatements";
import { IFinancialTable } from "./Table"; import { IFinancialTable } from "./Table";
export interface SalesTaxLiabilitySummaryQuery { export interface SalesTaxLiabilitySummaryQuery {
@@ -48,10 +47,9 @@ export type SalesTaxLiabilitySummarySalesById = Record<
{ taxRateId: number; credit: number; debit: number } { taxRateId: number; credit: number; debit: number }
>; >;
export interface SalesTaxLiabilitySummaryMeta extends IFinancialSheetCommonMeta { export interface SalesTaxLiabilitySummaryMeta {
formattedFromDate: string; organizationName: string;
formattedToDate: string; baseCurrency: string;
formattedDateRange: string;
} }
export interface ISalesTaxLiabilitySummaryTable extends IFinancialTable { export interface ISalesTaxLiabilitySummaryTable extends IFinancialTable {

View File

@@ -1,4 +1,3 @@
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable, ITableData } from './Table'; import { IFinancialTable, ITableData } from './Table';
import { import {
ITransactionsByContactsAmount, ITransactionsByContactsAmount,
@@ -29,12 +28,10 @@ export type ITransactionsByCustomersData = ITransactionsByCustomersCustomer[];
export interface ITransactionsByCustomersStatement { export interface ITransactionsByCustomersStatement {
data: ITransactionsByCustomersData; data: ITransactionsByCustomersData;
query: ITransactionsByCustomersFilter; query: ITransactionsByCustomersFilter;
meta: ITransactionsByCustomersMeta;
} }
export interface ITransactionsByCustomersTable extends IFinancialTable { export interface ITransactionsByCustomersTable extends IFinancialTable {
query: ITransactionsByCustomersFilter; query: ITransactionsByCustomersFilter;
meta: ITransactionsByCustomersMeta;
} }
export interface ITransactionsByCustomersService { export interface ITransactionsByCustomersService {
@@ -43,9 +40,3 @@ export interface ITransactionsByCustomersService {
filter: ITransactionsByCustomersFilter filter: ITransactionsByCustomersFilter
): Promise<ITransactionsByCustomersStatement>; ): Promise<ITransactionsByCustomersStatement>;
} }
export interface ITransactionsByCustomersMeta
extends IFinancialSheetCommonMeta {
formattedFromDate: string;
formattedToDate: string;
formattedDateRange: string;
}

View File

@@ -1,4 +1,3 @@
import { IFinancialSheetCommonMeta } from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
import { import {
ITransactionsByContactsAmount, ITransactionsByContactsAmount,
@@ -28,8 +27,6 @@ export type ITransactionsByVendorsData = ITransactionsByVendorsVendor[];
export interface ITransactionsByVendorsStatement { export interface ITransactionsByVendorsStatement {
data: ITransactionsByVendorsData; data: ITransactionsByVendorsData;
query: ITransactionsByVendorsFilter;
meta: ITransactionsByVendorMeta;
} }
export interface ITransactionsByVendorsService { export interface ITransactionsByVendorsService {
@@ -41,10 +38,4 @@ export interface ITransactionsByVendorsService {
export interface ITransactionsByVendorTable extends IFinancialTable { export interface ITransactionsByVendorTable extends IFinancialTable {
query: ITransactionsByVendorsFilter; query: ITransactionsByVendorsFilter;
meta: ITransactionsByVendorMeta; }
}
export interface ITransactionsByVendorMeta extends IFinancialSheetCommonMeta {
formattedFromDate: string;
formattedToDate: string;
formattedDateRange: string;
}

View File

@@ -1,7 +1,4 @@
import { import { INumberFormatQuery } from './FinancialStatements';
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
export interface ITrialBalanceSheetQuery { export interface ITrialBalanceSheetQuery {
@@ -27,10 +24,10 @@ export interface ITrialBalanceTotal {
formattedBalance: string; formattedBalance: string;
} }
export interface ITrialBalanceSheetMeta extends IFinancialSheetCommonMeta { export interface ITrialBalanceSheetMeta {
formattedFromDate: string; isCostComputeRunning: boolean;
formattedToDate: string; organizationName: string;
formattedDateRange: string; baseCurrency: string;
} }
export interface ITrialBalanceAccount extends ITrialBalanceTotal { export interface ITrialBalanceAccount extends ITrialBalanceTotal {

View File

@@ -1,7 +1,4 @@
import { import { INumberFormatQuery } from './FinancialStatements';
IFinancialSheetCommonMeta,
INumberFormatQuery,
} from './FinancialStatements';
import { IFinancialTable } from './Table'; import { IFinancialTable } from './Table';
export interface IVendorBalanceSummaryQuery { export interface IVendorBalanceSummaryQuery {
@@ -42,9 +39,8 @@ export interface IVendorBalanceSummaryData {
export interface IVendorBalanceSummaryStatement { export interface IVendorBalanceSummaryStatement {
data: IVendorBalanceSummaryData; data: IVendorBalanceSummaryData;
columns: {};
query: IVendorBalanceSummaryQuery; query: IVendorBalanceSummaryQuery;
meta: IVendorBalanceSummaryMeta;
} }
export interface IVendorBalanceSummaryService { export interface IVendorBalanceSummaryService {
@@ -56,9 +52,4 @@ export interface IVendorBalanceSummaryService {
export interface IVendorBalanceSummaryTable extends IFinancialTable { export interface IVendorBalanceSummaryTable extends IFinancialTable {
query: IVendorBalanceSummaryQuery; query: IVendorBalanceSummaryQuery;
meta: IVendorBalanceSummaryMeta;
}
export interface IVendorBalanceSummaryMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
} }

View File

@@ -74,7 +74,6 @@ export * from './Tasks';
export * from './Times'; export * from './Times';
export * from './ProjectProfitabilitySummary'; export * from './ProjectProfitabilitySummary';
export * from './TaxRate'; export * from './TaxRate';
export * from './Plaid';
export interface I18nService { export interface I18nService {
__: (input: string) => string; __: (input: string) => string;

View File

@@ -1,45 +0,0 @@
import { OpenExchangeRate } from './OpenExchangeRate';
import { ExchangeRateServiceType, IExchangeRateService } from './types';
export class ExchangeRate {
private exchangeRateService: IExchangeRateService;
private exchangeRateServiceType: ExchangeRateServiceType;
/**
* Constructor method.
* @param {ExchangeRateServiceType} service
*/
constructor(service: ExchangeRateServiceType) {
this.exchangeRateServiceType = service;
this.initService();
}
/**
* Initialize the exchange rate service based on the service type.
*/
private initService() {
if (
this.exchangeRateServiceType === ExchangeRateServiceType.OpenExchangeRate
) {
this.setExchangeRateService(new OpenExchangeRate());
}
}
/**
* Sets the exchange rate service.
* @param {IExchangeRateService} service
*/
private setExchangeRateService(service: IExchangeRateService) {
this.exchangeRateService = service;
}
/**
* Gets the latest exchange rate.
* @param {string} baseCurrency
* @param {string} toCurrency
* @returns {number}
*/
public latest(baseCurrency: string, toCurrency: string): Promise<number> {
return this.exchangeRateService.latest(baseCurrency, toCurrency);
}
}

View File

@@ -1,81 +0,0 @@
import Axios, { AxiosError } from 'axios';
import {
EchangeRateErrors,
IExchangeRateService,
OPEN_EXCHANGE_RATE_LATEST_URL,
} from './types';
import config from '@/config';
import { ServiceError } from '@/exceptions';
export class OpenExchangeRate implements IExchangeRateService {
/**
* Gets the latest exchange rate.
* @param {string} baseCurrency
* @param {string} toCurrency
* @returns {Promise<number}
*/
public async latest(
baseCurrency: string,
toCurrency: string
): Promise<number> {
// Vaclidates the Open Exchange Rate api id early.
this.validateApiIdExistance();
try {
const result = await Axios.get(OPEN_EXCHANGE_RATE_LATEST_URL, {
params: {
app_id: config.exchangeRate.openExchangeRate.appId,
base: baseCurrency,
symbols: toCurrency,
},
});
return result.data.rates[toCurrency] || (1 as number);
} catch (error) {
this.handleLatestErrors(error);
}
}
/**
* Validates the Open Exchange Rate api id.
* @throws {ServiceError}
*/
private validateApiIdExistance() {
const apiId = config.exchangeRate.openExchangeRate.appId;
if (!apiId) {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
}
}
/**
* Handles the latest errors.
* @param {any} error
* @throws {ServiceError}
*/
private handleLatestErrors(error: any) {
if (error.response.data?.message === 'missing_app_id') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
} else if (error.response.data?.message === 'invalid_app_id') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
} else if (error.response.data?.message === 'not_allowed') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
'Getting the exchange rate from the given base currency to the given currency is not allowed.'
);
} else if (error.response.data?.message === 'invalid_base') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
'The given base currency is invalid.'
);
}
}
}

View File

@@ -1,17 +0,0 @@
export interface IExchangeRateService {
latest(baseCurrency: string, toCurrency: string): Promise<number>;
}
export enum ExchangeRateServiceType {
OpenExchangeRate = 'OpenExchangeRate',
}
export enum EchangeRateErrors {
EX_RATE_SERVICE_NOT_ALLOWED = 'EX_RATE_SERVICE_NOT_ALLOWED',
EX_RATE_LIMIT_EXCEEDED = 'EX_RATE_LIMIT_EXCEEDED',
EX_RATE_SERVICE_API_KEY_REQUIRED = 'EX_RATE_SERVICE_API_KEY_REQUIRED',
EX_RATE_INVALID_BASE_CURRENCY = 'EX_RATE_INVALID_BASE_CURRENCY',
}
export const OPEN_EXCHANGE_RATE_LATEST_URL =
'https://openexchangerates.org/api/latest.json';

View File

@@ -2,13 +2,18 @@ import fs from 'fs';
import Mustache from 'mustache'; import Mustache from 'mustache';
import { Container } from 'typedi'; import { Container } from 'typedi';
import path from 'path'; import path from 'path';
import { IMailAttachment } from '@/interfaces'; import { IMailable } from '@/interfaces';
interface IMailAttachment {
filename: string;
path: string;
cid: string;
}
export default class Mail { export default class Mail {
view: string; view: string;
subject: string = ''; subject: string;
content: string = ''; to: string;
to: string | string[];
from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`; from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`;
data: { [key: string]: string | number }; data: { [key: string]: string | number };
attachments: IMailAttachment[]; attachments: IMailAttachment[];
@@ -16,24 +21,16 @@ export default class Mail {
/** /**
* Mail options. * Mail options.
*/ */
public get mailOptions() { private get mailOptions() {
return { return {
to: this.to, to: this.to,
from: this.from, from: this.from,
subject: this.subject, subject: this.subject,
html: this.html, html: this.render(this.data),
attachments: this.attachments, attachments: this.attachments,
}; };
} }
/**
* Retrieves the html content of the mail.
* @returns {string}
*/
public get html() {
return this.view ? Mail.render(this.view, this.data) : this.content;
}
/** /**
* Sends the given mail to the target address. * Sends the given mail to the target address.
*/ */
@@ -55,7 +52,7 @@ export default class Mail {
* Set send mail to address. * Set send mail to address.
* @param {string} to - * @param {string} to -
*/ */
setTo(to: string | string[]) { setTo(to: string) {
this.to = to; this.to = to;
return this; return this;
} }
@@ -65,16 +62,11 @@ export default class Mail {
* @param {string} from * @param {string} from
* @return {} * @return {}
*/ */
setFrom(from: string) { private setFrom(from: string) {
this.from = from; this.from = from;
return this; return this;
} }
/**
* Set attachments to the mail.
* @param {IMailAttachment[]} attachments
* @returns {Mail}
*/
setAttachments(attachments: IMailAttachment[]) { setAttachments(attachments: IMailAttachment[]) {
this.attachments = attachments; this.attachments = attachments;
return this; return this;
@@ -103,26 +95,21 @@ export default class Mail {
return this; return this;
} }
setContent(content: string) {
this.content = content;
return this;
}
/** /**
* Renders the view template with the given data. * Renders the view template with the given data.
* @param {object} data * @param {object} data
* @return {string} * @return {string}
*/ */
static render(view: string, data: Record<string, any>): string { render(data): string {
const viewContent = Mail.getViewContent(view); const viewContent = this.getViewContent();
return Mustache.render(viewContent, data); return Mustache.render(viewContent, data);
} }
/** /**
* Retrieve view content from the view directory. * Retrieve view content from the view directory.
*/ */
static getViewContent(view: string): string { private getViewContent(): string {
const filePath = path.join(global.__views_dir, `/${view}`); const filePath = path.join(global.__views_dir, `/${this.view}`);
return fs.readFileSync(filePath, 'utf8'); return fs.readFileSync(filePath, 'utf8');
} }
} }

View File

@@ -1,103 +0,0 @@
import { forEach } from 'lodash';
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';
import { createPlaidApiEvent } from './PlaidApiEventsDBSync';
import config from '@/config';
const OPTIONS = { clientApp: 'Plaid-Pattern' };
// We want to log requests to / responses from the Plaid API (via the Plaid client), as this data
// can be useful for troubleshooting.
/**
* Logging function for Plaid client methods that use an access_token as an argument. Associates
* the Plaid API event log entry with the item and user the request is for.
*
* @param {string} clientMethod the name of the Plaid client method called.
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
* @param {Object} response the response from the Plaid client.
*/
const defaultLogger = async (clientMethod, clientMethodArgs, response) => {
const accessToken = clientMethodArgs[0].access_token;
// const { id: itemId, user_id: userId } = await retrieveItemByPlaidAccessToken(
// accessToken
// );
// await createPlaidApiEvent(1, 1, clientMethod, clientMethodArgs, response);
// console.log(response);
};
/**
* Logging function for Plaid client methods that do not use access_token as an argument. These
* Plaid API event log entries will not be associated with an item or user.
*
* @param {string} clientMethod the name of the Plaid client method called.
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
* @param {Object} response the response from the Plaid client.
*/
const noAccessTokenLogger = async (
clientMethod,
clientMethodArgs,
response
) => {
// console.log(response);
// await createPlaidApiEvent(
// undefined,
// undefined,
// clientMethod,
// clientMethodArgs,
// response
// );
};
// Plaid client methods used in this app, mapped to their appropriate logging functions.
const clientMethodLoggingFns = {
accountsGet: defaultLogger,
institutionsGet: noAccessTokenLogger,
institutionsGetById: noAccessTokenLogger,
itemPublicTokenExchange: noAccessTokenLogger,
itemRemove: defaultLogger,
linkTokenCreate: noAccessTokenLogger,
transactionsSync: defaultLogger,
sandboxItemResetLogin: defaultLogger,
};
// Wrapper for the Plaid client. This allows us to easily log data for all Plaid client requests.
export class PlaidClientWrapper {
constructor() {
// Initialize the Plaid client.
const configuration = new Configuration({
basePath: PlaidEnvironments[config.plaid.env],
baseOptions: {
headers: {
'PLAID-CLIENT-ID': config.plaid.clientId,
'PLAID-SECRET':
config.plaid.env === 'development'
? config.plaid.secretDevelopment
: config.plaid.secretSandbox,
'Plaid-Version': '2020-09-14',
},
},
});
this.client = new PlaidApi(configuration);
// Wrap the Plaid client methods to add a logging function.
forEach(clientMethodLoggingFns, (logFn, method) => {
this[method] = this.createWrappedClientMethod(method, logFn);
});
}
// Allows us to log API request data for troubleshooting purposes.
createWrappedClientMethod(clientMethod, log) {
return async (...args) => {
try {
const res = await this.client[clientMethod](...args);
await log(clientMethod, args, res);
return res;
} catch (err) {
await log(clientMethod, args, err?.response?.data);
throw err;
}
};
}
}

View File

@@ -1,48 +0,0 @@
/**
* Creates a single Plaid api event log entry.
*
* @param {string} itemId the item id in the request.
* @param {string} userId the user id in the request.
* @param {string} plaidMethod the Plaid client method called.
* @param {Array} clientMethodArgs the arguments passed to the Plaid client method.
* @param {Object} response the Plaid api response object.
*/
export const createPlaidApiEvent = async (
itemId,
userId,
plaidMethod,
clientMethodArgs,
response
) => {
const {
error_code: errorCode,
error_type: errorType,
request_id: requestId,
} = response;
const query = {
text: `
INSERT INTO plaid_api_events_table
(
item_id,
user_id,
plaid_method,
arguments,
request_id,
error_type,
error_code
)
VALUES
($1, $2, $3, $4, $5, $6, $7);
`,
values: [
itemId,
userId,
plaidMethod,
JSON.stringify(clientMethodArgs),
requestId,
errorType,
errorCode,
],
};
// await db.query(query);
};

View File

@@ -1 +0,0 @@
export * from './Plaid';

Some files were not shown because too many files have changed in this diff Show More