mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
Compare commits
130 Commits
docker-com
...
all-contri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1a3510f0b | ||
|
|
172eea0ad1 | ||
|
|
01f7effc71 | ||
|
|
26c6ca9e36 | ||
|
|
ffef627dc3 | ||
|
|
f105980f08 | ||
|
|
efad38fcdc | ||
|
|
d84568e43a | ||
|
|
ed6517c0e1 | ||
|
|
0fd256c801 | ||
|
|
f0285560aa | ||
|
|
7a33f79268 | ||
|
|
ef5ef647d4 | ||
|
|
ce62a0524c | ||
|
|
278c8a01c5 | ||
|
|
f22dc9a18b | ||
|
|
b224a2c313 | ||
|
|
d4a933ef18 | ||
|
|
8b0feb9022 | ||
|
|
92f929152f | ||
|
|
da514403a1 | ||
|
|
d57f9e5171 | ||
|
|
0a1299b8a6 | ||
|
|
326a2038e7 | ||
|
|
94bb153120 | ||
|
|
223756b7ae | ||
|
|
a773ee9966 | ||
|
|
47790fba84 | ||
|
|
b122fdc43a | ||
|
|
cb93f313ec | ||
|
|
74c31c5f20 | ||
|
|
d411ae3e68 | ||
|
|
aecef878ba | ||
|
|
ec90056f7b | ||
|
|
30f2f1fb4c | ||
|
|
c72f5374f3 | ||
|
|
66de03b143 | ||
|
|
2b33583a03 | ||
|
|
e5611b4446 | ||
|
|
d12157a8d4 | ||
|
|
b24badfa52 | ||
|
|
c992562760 | ||
|
|
485138344c | ||
|
|
8d990ae85d | ||
|
|
7fbe51ddf2 | ||
|
|
215eb97762 | ||
|
|
3d4fd0b904 | ||
|
|
e552ff6449 | ||
|
|
268942af42 | ||
|
|
02489a907a | ||
|
|
4e0037d1c0 | ||
|
|
39786e5b1f | ||
|
|
6373862044 | ||
|
|
b46154ba59 | ||
|
|
59e3a4016b | ||
|
|
b6a1c9ab4b | ||
|
|
7171fb2a69 | ||
|
|
c64a14aef3 | ||
|
|
ac539aed34 | ||
|
|
c7b4846cb0 | ||
|
|
d54aac9b32 | ||
|
|
fe87713df0 | ||
|
|
4770fdf709 | ||
|
|
1b3c525ba5 | ||
|
|
b35d22d3b3 | ||
|
|
44fce6f33e | ||
|
|
e58a1d6ad1 | ||
|
|
5f191cf335 | ||
|
|
46bf1cc39a | ||
|
|
c98fe00f88 | ||
|
|
4b95c19d3e | ||
|
|
eadaac30d6 | ||
|
|
ca4d543482 | ||
|
|
1f3adf4879 | ||
|
|
532ad61500 | ||
|
|
a44d779670 | ||
|
|
ab5f9f50d0 | ||
|
|
f3f10db6db | ||
|
|
de5694681b | ||
|
|
b1a997c287 | ||
|
|
3e36146bce | ||
|
|
db833888c8 | ||
|
|
c415e3d693 | ||
|
|
e145eabf02 | ||
|
|
caab21647d | ||
|
|
98528e9e5b | ||
|
|
b993fad37f | ||
|
|
94ea44b58e | ||
|
|
877a57043a | ||
|
|
70415d1d63 | ||
|
|
ef2d1977d6 | ||
|
|
46ea26891d | ||
|
|
f750cede89 | ||
|
|
e5d0f16096 | ||
|
|
706694c768 | ||
|
|
d1a09e3b15 | ||
|
|
01c27b56ef | ||
|
|
0d8fb8cf25 | ||
|
|
6562e3ab8c | ||
|
|
c93650ffd3 | ||
|
|
e6a2825065 | ||
|
|
0c2a0b0010 | ||
|
|
a332c51249 | ||
|
|
0b6d0bc016 | ||
|
|
e6336b1451 | ||
|
|
46290c4d37 | ||
|
|
ff2b7563c8 | ||
|
|
b9572420ed | ||
|
|
35ebb9c2aa | ||
|
|
3ebeb29dc0 | ||
|
|
8e98068538 | ||
|
|
6a72594faf | ||
|
|
728729094a | ||
|
|
93d540fbd2 | ||
|
|
eb9b6ce717 | ||
|
|
f716d42d26 | ||
|
|
1c4c364f06 | ||
|
|
162ad91547 | ||
|
|
2950e5ede4 | ||
|
|
73b041d8d2 | ||
|
|
7bf008a9cb | ||
|
|
4d9e3ccfb4 | ||
|
|
1bfe19f26c | ||
|
|
a371cedb67 | ||
|
|
4ed9c36ebd | ||
|
|
e24b23ce7e | ||
|
|
19fe6e2423 | ||
|
|
aec09f178b | ||
|
|
ffe51bae07 | ||
|
|
68231d5edb |
71
.all-contributorsrc
Normal file
71
.all-contributorsrc
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"imageSize": 100,
|
||||||
|
"commit": false,
|
||||||
|
"commitType": "docs",
|
||||||
|
"commitConvention": "angular",
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"login": "abouolia",
|
||||||
|
"name": "Ahmed Bouhuolia",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/2197422?v=4",
|
||||||
|
"profile": "https://github.com/abouolia",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ameir",
|
||||||
|
"name": "Ameir Abdeldayem",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/374330?v=4",
|
||||||
|
"profile": "http://ameir.net",
|
||||||
|
"contributions": [
|
||||||
|
"bug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "elforjani13",
|
||||||
|
"name": "ElforJani13",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/39470382?v=4",
|
||||||
|
"profile": "https://github.com/elforjani13",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "scheibling",
|
||||||
|
"name": "Lars Scheibling",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/24367830?v=4",
|
||||||
|
"profile": "https://scheibling.se",
|
||||||
|
"contributions": [
|
||||||
|
"bug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "suhaibaffan",
|
||||||
|
"name": "Suhaib Affan",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/18115937?v=4",
|
||||||
|
"profile": "https://github.com/suhaibaffan",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "KalliopiPliogka",
|
||||||
|
"name": "Kalliopi Pliogka",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/81677549?v=4",
|
||||||
|
"profile": "https://github.com/KalliopiPliogka",
|
||||||
|
"contributions": [
|
||||||
|
"bug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contributorsPerLine": 7,
|
||||||
|
"skipCi": true,
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
|
"projectName": "bigcapital",
|
||||||
|
"projectOwner": "bigcapitalhq"
|
||||||
|
}
|
||||||
39
.env.example
39
.env.example
@@ -8,26 +8,32 @@ MAIL_FROM_NAME=
|
|||||||
MAIL_FROM_ADDRESS=
|
MAIL_FROM_ADDRESS=
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
DB_USER=
|
DB_HOST=mysql
|
||||||
DB_HOST=
|
DB_USER=bigcapital
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=bigcapital
|
||||||
DB_CHARSET=
|
DB_ROOT_PASSWORD=root
|
||||||
|
DB_CHARSET=utf8
|
||||||
|
|
||||||
# System database
|
# System database
|
||||||
SYSTEM_DB_NAME=bigcapital_system
|
SYSTEM_DB_NAME=bigcapital_system
|
||||||
|
# SYSTEM_DB_USER=
|
||||||
|
# SYSTEM_DB_PASSWORD=
|
||||||
|
# SYSTEM_DB_NAME=
|
||||||
|
# SYSTEM_DB_CHARSET=
|
||||||
|
|
||||||
# Tenants databases
|
# Tenant databases
|
||||||
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
||||||
|
# TENANT_DB_HOST=
|
||||||
# MongoDB
|
# TENANT_DB_USER=
|
||||||
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
# TENANT_DB_PASSWORD=
|
||||||
|
# TENANT_DB_CHARSET=
|
||||||
# Authentication
|
|
||||||
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
BASE_URL=https://bigcapital.ly
|
BASE_URL=http://example.com
|
||||||
CONTACT_US_MAIL=support@bigcapital.ly
|
JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI
|
||||||
|
|
||||||
|
# Jobs MongoDB
|
||||||
|
MONGODB_DATABASE_URL=mongodb://localhost/bigcapital
|
||||||
|
|
||||||
# App proxy
|
# App proxy
|
||||||
PUBLIC_PROXY_PORT=80
|
PUBLIC_PROXY_PORT=80
|
||||||
@@ -38,6 +44,9 @@ AGENDASH_AUTH_USER=agendash
|
|||||||
AGENDASH_AUTH_PASSWORD=123123
|
AGENDASH_AUTH_PASSWORD=123123
|
||||||
|
|
||||||
# Sign-up restrictions
|
# Sign-up restrictions
|
||||||
SIGNUP_DISABLED=true
|
SIGNUP_DISABLED=false
|
||||||
SIGNUP_ALLOWED_DOMAINS=
|
SIGNUP_ALLOWED_DOMAINS=
|
||||||
SIGNUP_ALLOWED_EMAILS=
|
SIGNUP_ALLOWED_EMAILS=
|
||||||
|
|
||||||
|
# API rate limit (points,duration,block duration).
|
||||||
|
API_RATE_LIMIT=120,60,600
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
docker/nginx/scripts/build-nginx.sh text eol=lf
|
||||||
|
docker/mariadb/docker-entrypoint.sh text eol=lf
|
||||||
87
CHANGELOG.md
87
CHANGELOG.md
@@ -2,6 +2,93 @@
|
|||||||
|
|
||||||
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.9.11] - 23-07-2023
|
||||||
|
|
||||||
|
* added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198
|
||||||
|
* fix: Expose and expand the rate limit to the env variables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/195
|
||||||
|
|
||||||
|
# [0.9.10] - 18-07-2023
|
||||||
|
|
||||||
|
* feat(e2e): E2E onboarding process by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/176
|
||||||
|
* fix(webapp): Show loading message of cost computing job on financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/196
|
||||||
|
* fix(webapp): Change the currency code of sales and purchases transactions with foreign contacts.
|
||||||
|
|
||||||
|
# [0.9.9] - 28-06-2023
|
||||||
|
|
||||||
|
* refactor: Customer and vendor select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/171
|
||||||
|
* chore: Move auto-increment components in separate files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/170
|
||||||
|
* fix: Style of quick item drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/173
|
||||||
|
* fix: Should not show the form before loading account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/172
|
||||||
|
* fix: Payment made form does not handle not unique number an e… by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/177
|
||||||
|
* fix: Internal note of invoice/bill payment does not saving by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/181
|
||||||
|
* fix: Storing cash flow transaction description by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/180
|
||||||
|
* fix: No currency in amount field on money in/out dialogs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/179
|
||||||
|
* fix: No default branch for customer/vendor opening balance branch by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/182
|
||||||
|
|
||||||
|
# [0.9.8] - 19-06-2023
|
||||||
|
|
||||||
|
`bigcapitalhq/webapp`
|
||||||
|
|
||||||
|
* add: Inventory Adjustment option to the item drawer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/158
|
||||||
|
* fix: use all drawers names from common enum object by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/157
|
||||||
|
* fix: adjustment type options do not show up by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/159
|
||||||
|
* fix: change the remove line text to be red to intent as a danger action by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/162
|
||||||
|
* fix: rename sidebar localization keys names to be keyword path by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/161
|
||||||
|
* fix: manual journal placeholder text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/160
|
||||||
|
* fix: warehouses select component by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/168
|
||||||
|
|
||||||
|
`bigcapitalhq/server`
|
||||||
|
|
||||||
|
* fix: sending emails on reset password and registration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/167
|
||||||
|
|
||||||
|
## [0.9.7] - 14-06-2023
|
||||||
|
|
||||||
|
`@bigcapital/webapp`
|
||||||
|
* fix: change the footer links of onboarding pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/139
|
||||||
|
|
||||||
|
`@bigcapital/server`
|
||||||
|
* fix: expense transaction journal entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/155
|
||||||
|
|
||||||
|
## [0.9.6] - 12-06-2023
|
||||||
|
|
||||||
|
`@bigcapital/webapp`
|
||||||
|
|
||||||
|
* fix: remove duplicated form submitting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/138
|
||||||
|
* feat: add monorepo version on the application sidebar by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/136
|
||||||
|
|
||||||
|
## [0.9.5] - 11-06-2023
|
||||||
|
|
||||||
|
`@bigcapital/server`
|
||||||
|
|
||||||
|
* fix: filter ledger entries that effect contact balance to AR/AP accounts only by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/132
|
||||||
|
|
||||||
|
`@bigcapital/webapp`
|
||||||
|
|
||||||
|
* fix: catch journal error when create a journal with accounts have different currency by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/135
|
||||||
|
* fix: add duplicate icon to context menu of customers and vendors table by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/133
|
||||||
|
* fix: customer/vendor opening balance with exchange rate by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/134
|
||||||
|
|
||||||
|
## [0.9.4] - 08-06-2023
|
||||||
|
|
||||||
|
`@bigcapital/monorepo`
|
||||||
|
- fixed: docker-compose line-ending issue on Windows by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/130
|
||||||
|
|
||||||
|
`@bigcapital/server`
|
||||||
|
- fixed: Disable Webpack minification for JS class name reading.
|
||||||
|
|
||||||
|
## [0.9.3] -04-06-2023
|
||||||
|
|
||||||
|
`@bigcapital/monorepo`
|
||||||
|
* Added: Add env variable to customize the proxy public ports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/125
|
||||||
|
* Added: Migrate the server database to MariaDB by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/128
|
||||||
|
|
||||||
|
## [0.9.2] - 31-05-2023
|
||||||
|
|
||||||
|
`@bigcapital/webapp`
|
||||||
|
|
||||||
|
- fixed: move `packaeg-lock.json` inside docker container.
|
||||||
|
- fixed: remove Sentry from the web client.
|
||||||
|
|
||||||
## [0.9.1] - 28-05-2023
|
## [0.9.1] - 28-05-2023
|
||||||
|
|
||||||
`@bigcapital/server`
|
`@bigcapital/server`
|
||||||
|
|||||||
57
README.md
57
README.md
@@ -7,6 +7,24 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
Simple, smart online accounting software for small and medium businesses.
|
Simple, smart online accounting software for small and medium businesses.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/bigcapitalhq/bigcapital/commits/develop">
|
||||||
|
<img src="https://img.shields.io/github/commit-activity/m/bigcapitalhq/bigcapital/develop" />
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.com/invite/c8nPBJafeb">
|
||||||
|
<img src="https://img.shields.io/discord/1066514716752625725?label=Discord" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/bigcapitalhq/bigcapital/graphs/contributors">
|
||||||
|
<img src="https://img.shields.io/github/contributors/bigcapitalhq/bigcapital" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/bigcapitalhq/bigcapital/blob/develop/LICENSE">
|
||||||
|
<img src="https://img.shields.io/github/license/bigcapitalhq/bigcapital" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/bigcapitalhq">
|
||||||
|
<img src="https://img.shields.io/twitter/follow/bigcapitalhq?style=social" alt="twitter" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# What's Bigcapital?
|
# What's Bigcapital?
|
||||||
@@ -22,10 +40,47 @@ Bigcapital is a smart and open-source accounting and inventory software, Bigcapi
|
|||||||
# Resources
|
# Resources
|
||||||
|
|
||||||
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
|
- [Documentation](https://docs.bigcapital.ly/) - Learn how to use.
|
||||||
|
- [Contribution](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md) - Welcome to any contributions.
|
||||||
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
|
- [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help.
|
||||||
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
|
- [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs.
|
||||||
- [Source Code](https://github.com/bigcapitalhq/bigcapital) - Github repo.
|
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Please see [Releases](https://github.com/bigcapitalhq/bigcapital/releases) for more information what has changed recently.
|
Please see [Releases](https://github.com/bigcapitalhq/bigcapital/releases) for more information what has changed recently.
|
||||||
|
|
||||||
|
# Recognition
|
||||||
|
|
||||||
|
<a href="https://news.ycombinator.com/item?id=36118990">
|
||||||
|
<img
|
||||||
|
style="width: 250px; height: 54px;" width="250" height="54"
|
||||||
|
alt="Featured on Hacker News"
|
||||||
|
src="https://hackernews-badge.vercel.app/api?id=36118990"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
|
||||||
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/abouolia"><img src="https://avatars.githubusercontent.com/u/2197422?v=4?s=100" width="100px;" alt="Ahmed Bouhuolia"/><br /><sub><b>Ahmed Bouhuolia</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=abouolia" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="http://ameir.net"><img src="https://avatars.githubusercontent.com/u/374330?v=4?s=100" width="100px;" alt="Ameir Abdeldayem"/><br /><sub><b>Ameir Abdeldayem</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Aameir" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/elforjani13"><img src="https://avatars.githubusercontent.com/u/39470382?v=4?s=100" width="100px;" alt="ElforJani13"/><br /><sub><b>ElforJani13</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=elforjani13" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://scheibling.se"><img src="https://avatars.githubusercontent.com/u/24367830?v=4?s=100" width="100px;" alt="Lars Scheibling"/><br /><sub><b>Lars Scheibling</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3Ascheibling" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/suhaibaffan"><img src="https://avatars.githubusercontent.com/u/18115937?v=4?s=100" width="100px;" alt="Suhaib Affan"/><br /><sub><b>Suhaib Affan</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/commits?author=suhaibaffan" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KalliopiPliogka"><img src="https://avatars.githubusercontent.com/u/81677549?v=4?s=100" width="100px;" alt="Kalliopi Pliogka"/><br /><sub><b>Kalliopi Pliogka</b></sub></a><br /><a href="https://github.com/bigcapitalhq/bigcapital/issues?q=author%3AKalliopiPliogka" title="Bug reports">🐛</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- server
|
- server
|
||||||
- webapp
|
- webapp
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
|
|
||||||
webapp:
|
webapp:
|
||||||
container_name: bigcapital-webapp
|
container_name: bigcapital-webapp
|
||||||
image: ghcr.io/bigcapitalhq/webapp:latest
|
image: ghcr.io/bigcapitalhq/webapp:latest
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
|
|
||||||
server:
|
server:
|
||||||
container_name: bigcapital-server
|
container_name: bigcapital-server
|
||||||
@@ -37,6 +43,9 @@ services:
|
|||||||
- mysql
|
- mysql
|
||||||
- mongo
|
- mongo
|
||||||
- redis
|
- redis
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
# Mail
|
# Mail
|
||||||
- MAIL_HOST=${MAIL_HOST}
|
- MAIL_HOST=${MAIL_HOST}
|
||||||
@@ -93,20 +102,26 @@ services:
|
|||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
container_name: bigcapital-mysql
|
container_name: bigcapital-mysql
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
build:
|
build:
|
||||||
context: ./docker/mysql
|
context: ./docker/mariadb
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_DATABASE=${SYSTEM_DB_NAME}
|
- MYSQL_DATABASE=${SYSTEM_DB_NAME}
|
||||||
- MYSQL_USER=${DB_USER}
|
- MYSQL_USER=${DB_USER}
|
||||||
- MYSQL_PASSWORD=${DB_PASSWORD}
|
- MYSQL_PASSWORD=${DB_PASSWORD}
|
||||||
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
|
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- mysql:/var/lib/mysql
|
- mysql:/var/lib/mysql
|
||||||
expose:
|
expose:
|
||||||
- '3306'
|
- '3306'
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
container_name: bigcapital-mongo
|
container_name: bigcapital-mongo
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
build: ./docker/mongo
|
build: ./docker/mongo
|
||||||
expose:
|
expose:
|
||||||
- '27017'
|
- '27017'
|
||||||
@@ -115,6 +130,9 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: bigcapital-redis
|
container_name: bigcapital-redis
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
build:
|
build:
|
||||||
context: ./docker/redis
|
context: ./docker/redis
|
||||||
expose:
|
expose:
|
||||||
|
|||||||
@@ -6,20 +6,23 @@
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mysql:
|
mariadb:
|
||||||
build:
|
build:
|
||||||
context: ./docker/mysql
|
context: ./docker/mariadb
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_DATABASE=${SYSTEM_DB_NAME}
|
- MYSQL_DATABASE=${SYSTEM_DB_NAME}
|
||||||
- MYSQL_USER=${DB_USER}
|
- MYSQL_USER=${DB_USER}
|
||||||
- MYSQL_PASSWORD=${DB_PASSWORD}
|
- MYSQL_PASSWORD=${DB_PASSWORD}
|
||||||
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
|
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- mysql:/var/lib/mysql
|
- mysql:/var/lib/mysql
|
||||||
expose:
|
expose:
|
||||||
- '3306'
|
- '3306'
|
||||||
ports:
|
ports:
|
||||||
- '3306:3306'
|
- '3306:3306'
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
build: ./docker/mongo
|
build: ./docker/mongo
|
||||||
@@ -29,6 +32,9 @@ services:
|
|||||||
- mongo:/var/lib/mongodb
|
- mongo:/var/lib/mongodb
|
||||||
ports:
|
ports:
|
||||||
- '27017:27017'
|
- '27017:27017'
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
build:
|
build:
|
||||||
@@ -37,6 +43,9 @@ services:
|
|||||||
- "6379"
|
- "6379"
|
||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: unless-stopped
|
||||||
|
|
||||||
# Volumes
|
# Volumes
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mysql:5.7
|
FROM mariadb:10.2
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
ADD my.cnf /etc/mysql/conf.d/my.cnf
|
ADD my.cnf /etc/mysql/conf.d/my.cnf
|
||||||
@@ -17,7 +17,7 @@ ENV MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
|
|||||||
COPY ./init.sql /scripts/init.template.sql
|
COPY ./init.sql /scripts/init.template.sql
|
||||||
COPY ./docker-entrypoint.sh /docker-entrypoint-initdb.d/docker-initialize.sh
|
COPY ./docker-entrypoint.sh /docker-entrypoint-initdb.d/docker-initialize.sh
|
||||||
|
|
||||||
# The scripts in the docker-entrypoint-initdb.d/ directory are executed as
|
# The scripts in the `docker-entrypoint-initdb.d/` directory are executed as
|
||||||
# the mysql user inside the MySQL Docker container.
|
# the mysql user inside the MySQL Docker container.
|
||||||
RUN chown -R mysql:root /docker-entrypoint-initdb.d
|
RUN chown -R mysql:root /docker-entrypoint-initdb.d
|
||||||
RUN chown -R mysql:root /scripts
|
RUN chown -R mysql:root /scripts
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
GRANT ALL PRIVILEGES ON *.* TO '{MYSQL_USER}'@'%' IDENTIFIED BY '{MYSQL_PASSWORD}' WITH GRANT OPTION;
|
GRANT ALL PRIVILEGES ON *.* TO '{MYSQL_USER}'@'%' IDENTIFIED BY '{MYSQL_PASSWORD}' WITH GRANT OPTION;
|
||||||
|
|
||||||
FLUSH PRIVILEGES;
|
FLUSH PRIVILEGES;
|
||||||
13
e2e/_utils.ts
Normal file
13
e2e/_utils.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
export const clearLocalStorage = (page: Page) => {
|
||||||
|
return page.evaluate(`window.localStorage.clear()`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultPageConfig = () => {
|
||||||
|
return {
|
||||||
|
extraHTTPHeaders: {
|
||||||
|
'ngrok-skip-browser-warning': 'any-value',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,14 +1,23 @@
|
|||||||
import { test, expect, Page } from '@playwright/test';
|
import { test, expect, Page } from '@playwright/test';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { clearLocalStorage, defaultPageConfig } from './_utils';
|
||||||
|
|
||||||
let authPage: Page;
|
let authPage: Page;
|
||||||
|
|
||||||
test.describe('authentication', () => {
|
test.describe('authentication', () => {
|
||||||
test.beforeAll(async ({ browser }) => {
|
test.beforeAll(async ({ browser }) => {
|
||||||
authPage = await browser.newPage();
|
authPage = await browser.newPage({ ...defaultPageConfig() });
|
||||||
|
});
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await authPage.close();
|
||||||
|
});
|
||||||
|
test.afterEach(async ({ context }) => {
|
||||||
|
context.clearCookies();
|
||||||
|
await clearLocalStorage(authPage);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('login', () => {
|
test.describe('login', () => {
|
||||||
test.beforeAll(async () => {
|
test.beforeEach(async () => {
|
||||||
await authPage.goto('/auth/login');
|
await authPage.goto('/auth/login');
|
||||||
});
|
});
|
||||||
test('should show the login page.', async () => {
|
test('should show the login page.', async () => {
|
||||||
@@ -30,10 +39,23 @@ test.describe('authentication', () => {
|
|||||||
await authPage.getByRole('link', { name: 'Sign up' }).click();
|
await authPage.getByRole('link', { name: 'Sign up' }).click();
|
||||||
await expect(authPage.url()).toContain('/auth/register');
|
await expect(authPage.url()).toContain('/auth/register');
|
||||||
});
|
});
|
||||||
|
test('should the email or password is not correct.', async () => {
|
||||||
|
await authPage.getByLabel('Email Address').click();
|
||||||
|
await authPage.getByLabel('Email Address').fill(faker.internet.email());
|
||||||
|
|
||||||
|
await authPage.getByLabel('Password').click();
|
||||||
|
await authPage.getByLabel('Password').fill(faker.internet.password());
|
||||||
|
|
||||||
|
await authPage.getByRole('button', { name: 'Log in' }).click();
|
||||||
|
|
||||||
|
await expect(authPage.locator('body')).toContainText(
|
||||||
|
'The email and password you entered did not match our records.'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('register', () => {
|
test.describe('register', () => {
|
||||||
test.beforeAll(async () => {
|
test.beforeEach(async () => {
|
||||||
await authPage.goto('/auth/register');
|
await authPage.goto('/auth/register');
|
||||||
});
|
});
|
||||||
test('should first name, last name, email and password be required.', async () => {
|
test('should first name, last name, email and password be required.', async () => {
|
||||||
@@ -52,10 +74,36 @@ test.describe('authentication', () => {
|
|||||||
'Password is a required field'
|
'Password is a required field'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test('should signup successfully.', async () => {
|
||||||
|
const form = authPage.locator('form');
|
||||||
|
await form.getByLabel('First Name').click();
|
||||||
|
await form.getByLabel('First Name').fill(faker.person.firstName());
|
||||||
|
|
||||||
|
await form.getByLabel('Email').click();
|
||||||
|
await form.getByLabel('Email').fill(faker.internet.email());
|
||||||
|
|
||||||
|
await form.getByLabel('Last Name').click();
|
||||||
|
await form.getByLabel('Last Name').fill(faker.person.lastName());
|
||||||
|
|
||||||
|
await form.getByLabel('Password').click();
|
||||||
|
await form.getByLabel('Password').fill(faker.internet.password());
|
||||||
|
|
||||||
|
await authPage.getByRole('button', { name: 'Register' }).click();
|
||||||
|
|
||||||
|
await expect(authPage.locator('h1')).toContainText(
|
||||||
|
'Register a New Organization now!'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('reset password', () => {
|
test.describe('reset password', () => {
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
authPage = await browser.newPage({ ...defaultPageConfig() });
|
||||||
|
});
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await authPage.close();
|
||||||
|
});
|
||||||
|
test.beforeEach(async () => {
|
||||||
await authPage.goto('/auth/send_reset_password');
|
await authPage.goto('/auth/send_reset_password');
|
||||||
});
|
});
|
||||||
test('should email be required.', async () => {
|
test('should email be required.', async () => {
|
||||||
|
|||||||
7
e2e/items.spec.ts
Normal file
7
e2e/items.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { test, expect, Page } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('item', () => {
|
||||||
|
test('should validate all required fields.', () => {});
|
||||||
|
test('should save the item successfully.', () => {});
|
||||||
|
test('should item code be unqiue.', () => {});
|
||||||
|
});
|
||||||
86
e2e/onboarding.spec.ts
Normal file
86
e2e/onboarding.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { test, expect, Page } from '@playwright/test';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { defaultPageConfig } from './_utils';
|
||||||
|
|
||||||
|
let authPage: Page;
|
||||||
|
let businessLegalName: string = faker.company.name();
|
||||||
|
|
||||||
|
test.describe('onboarding', () => {
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
authPage = await browser.newPage({ ...defaultPageConfig() });
|
||||||
|
await authPage.goto('/auth/register');
|
||||||
|
|
||||||
|
const form = authPage.locator('form');
|
||||||
|
|
||||||
|
await form.getByLabel('First Name').fill(faker.person.firstName());
|
||||||
|
await form.getByLabel('Email').fill(faker.internet.email());
|
||||||
|
await form.getByLabel('Last Name').fill(faker.person.lastName());
|
||||||
|
await form.getByLabel('Password').fill(faker.internet.password());
|
||||||
|
|
||||||
|
await authPage.getByRole('button', { name: 'Register' }).click();
|
||||||
|
});
|
||||||
|
test('should validation catch all required fields', async () => {
|
||||||
|
const form = authPage.locator('form');
|
||||||
|
|
||||||
|
await authPage.getByRole('button', { name: 'Save & Continue' }).click();
|
||||||
|
|
||||||
|
await expect(form).toContainText('Organization name is a required field');
|
||||||
|
await expect(form).toContainText('Location is a required field');
|
||||||
|
await expect(form).toContainText('Base currency is a required field');
|
||||||
|
await expect(form).toContainText('Fiscal year is a required field');
|
||||||
|
await expect(form).toContainText('Time zone is a required field');
|
||||||
|
});
|
||||||
|
test.describe('after onboarding', () => {
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
await authPage.getByLabel('Legal Organization Name').click();
|
||||||
|
await authPage
|
||||||
|
.getByLabel('Legal Organization Name')
|
||||||
|
.fill(businessLegalName);
|
||||||
|
|
||||||
|
// Fill Business Location.
|
||||||
|
await authPage
|
||||||
|
.getByRole('button', { name: 'Select Business Location...' })
|
||||||
|
.click();
|
||||||
|
await authPage.locator('a').filter({ hasText: 'Albania' }).click();
|
||||||
|
|
||||||
|
// Fill Base Currency.
|
||||||
|
await authPage
|
||||||
|
.getByRole('button', { name: 'Select Base Currency...' })
|
||||||
|
.click();
|
||||||
|
await authPage
|
||||||
|
.locator('a')
|
||||||
|
.filter({ hasText: 'AED - United Arab Emirates Dirham' })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Fill Fasical Year.
|
||||||
|
await authPage
|
||||||
|
.getByRole('button', { name: 'Select Fiscal Year...' })
|
||||||
|
.click();
|
||||||
|
await authPage.locator('a').filter({ hasText: 'June - May' }).click();
|
||||||
|
|
||||||
|
// Fill Timezone.
|
||||||
|
await authPage
|
||||||
|
.getByRole('button', { name: 'Select Time Zone...' })
|
||||||
|
.click();
|
||||||
|
await authPage.getByText('Pacific/Marquesas-09:30').click();
|
||||||
|
|
||||||
|
// Click on Submit button
|
||||||
|
await authPage.getByRole('button', { name: 'Save & Continue' }).click();
|
||||||
|
});
|
||||||
|
test('should onboarding process success', async () => {
|
||||||
|
await expect(authPage.locator('body')).toContainText(
|
||||||
|
'Congrats! You are ready to go',
|
||||||
|
{
|
||||||
|
timeout: 30000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('should go to the dashboard after clicking on "Go to dashboard" button.', async () => {
|
||||||
|
await authPage.getByRole('button', { name: 'Go to dashboard' }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
authPage.locator('[data-testId="dashboard-topbar"] h1')
|
||||||
|
).toContainText(businessLegalName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"version": "0.0.0",
|
"version": "0.9.6",
|
||||||
"npmClient": "npm"
|
"npmClient": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -333,6 +333,12 @@
|
|||||||
"@jridgewell/trace-mapping": "0.3.9"
|
"@jridgewell/trace-mapping": "0.3.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@faker-js/faker": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.0.2.tgz",
|
||||||
|
"integrity": "sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@gar/promisify": {
|
"@gar/promisify": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||||
@@ -945,6 +951,7 @@
|
|||||||
"version": "1.32.3",
|
"version": "1.32.3",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz",
|
||||||
"integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==",
|
"integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"fsevents": "2.3.2",
|
"fsevents": "2.3.2",
|
||||||
@@ -954,7 +961,8 @@
|
|||||||
"playwright-core": {
|
"playwright-core": {
|
||||||
"version": "1.32.3",
|
"version": "1.32.3",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.3.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.3.tgz",
|
||||||
"integrity": "sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg=="
|
"integrity": "sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1003,7 +1011,8 @@
|
|||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "18.14.6",
|
"version": "18.14.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz",
|
||||||
"integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA=="
|
"integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/normalize-package-data": {
|
"@types/normalize-package-data": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
@@ -2324,6 +2333,7 @@
|
|||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"function-bind": {
|
"function-bind": {
|
||||||
|
|||||||
@@ -18,12 +18,13 @@
|
|||||||
"shared/*"
|
"shared/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.4.2",
|
||||||
"@commitlint/config-conventional": "^17.4.2",
|
"@commitlint/config-conventional": "^17.4.2",
|
||||||
"@commitlint/config-lerna-scopes": "^17.4.2",
|
"@commitlint/config-lerna-scopes": "^17.4.2",
|
||||||
|
"@faker-js/faker": "^8.0.2",
|
||||||
|
"@playwright/test": "^1.32.3",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lerna": "^6.4.1",
|
"lerna": "^6.4.1"
|
||||||
"@commitlint/cli": "^17.4.2",
|
|
||||||
"@playwright/test": "^1.32.3"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "14.x"
|
"node": "14.x"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bigcapital/server",
|
"name": "@bigcapital/server",
|
||||||
"version": "1.7.1",
|
"version": "0.9.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/server.ts",
|
"main": "src/server.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ exports.getCommonWebpackOptions = ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export default class AuthenticationController extends BaseController {
|
|||||||
check('password')
|
check('password')
|
||||||
.exists()
|
.exists()
|
||||||
.isString()
|
.isString()
|
||||||
|
.isLength({ min: 6 })
|
||||||
.trim()
|
.trim()
|
||||||
.escape()
|
.escape()
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
@@ -106,7 +107,7 @@ export default class AuthenticationController extends BaseController {
|
|||||||
return [
|
return [
|
||||||
check('password')
|
check('password')
|
||||||
.exists()
|
.exists()
|
||||||
.isLength({ min: 5 })
|
.isLength({ min: 6 })
|
||||||
.custom((value, { req }) => {
|
.custom((value, { req }) => {
|
||||||
if (value !== req.body.confirm_password) {
|
if (value !== req.body.confirm_password) {
|
||||||
throw new Error("Passwords don't match");
|
throw new Error("Passwords don't match");
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query } from 'express-validator';
|
import { check, param, query } from 'express-validator';
|
||||||
import { Service, Inject } from 'typedi';
|
import {
|
||||||
import { AbilitySubject, BillAction, IBillDTO, IBillEditDTO } from '@/interfaces';
|
AbilitySubject,
|
||||||
|
BillAction,
|
||||||
|
IBillDTO,
|
||||||
|
IBillEditDTO,
|
||||||
|
} from '@/interfaces';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import BillsService from '@/services/Purchases/Bills';
|
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import BillPaymentsService from '@/services/Purchases/BillPaymentsService';
|
import { BillsApplication } from '@/services/Purchases/Bills/BillsApplication';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class BillsController extends BaseController {
|
export default class BillsController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private billsService: BillsService;
|
private billsApplication: BillsApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private dynamicListService: DynamicListingService;
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private billPayments: BillPaymentsService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -97,7 +98,7 @@ export default class BillsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Common validation schema.
|
* Common validation schema.
|
||||||
*/
|
*/
|
||||||
get billValidationSchema() {
|
private get billValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('bill_number').exists().trim().escape(),
|
check('bill_number').exists().trim().escape(),
|
||||||
check('reference_no').optional().trim().escape(),
|
check('reference_no').optional().trim().escape(),
|
||||||
@@ -142,7 +143,7 @@ export default class BillsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Common validation schema.
|
* Common validation schema.
|
||||||
*/
|
*/
|
||||||
get billEditValidationSchema() {
|
private get billEditValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('bill_number').optional().trim().escape(),
|
check('bill_number').optional().trim().escape(),
|
||||||
check('reference_no').optional().trim().escape(),
|
check('reference_no').optional().trim().escape(),
|
||||||
@@ -184,14 +185,14 @@ export default class BillsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Bill validation schema.
|
* Bill validation schema.
|
||||||
*/
|
*/
|
||||||
get specificBillValidationSchema() {
|
private get specificBillValidationSchema() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bills list validation schema.
|
* Bills list validation schema.
|
||||||
*/
|
*/
|
||||||
get billsListingValidationSchema() {
|
private get billsListingValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('view_slug').optional().isString().trim(),
|
query('view_slug').optional().isString().trim(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
@@ -203,7 +204,7 @@ export default class BillsController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
get dueBillsListingValidationSchema() {
|
private get dueBillsListingValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('vendor_id').optional().trim().escape(),
|
query('vendor_id').optional().trim().escape(),
|
||||||
query('payment_made_id').optional().trim().escape(),
|
query('payment_made_id').optional().trim().escape(),
|
||||||
@@ -216,17 +217,16 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
async newBill(req: Request, res: Response, next: NextFunction) {
|
private async newBill(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const billDTO: IBillDTO = this.matchedBodyData(req);
|
const billDTO: IBillDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedBill = await this.billsService.createBill(
|
const storedBill = await this.billsApplication.createBill(
|
||||||
tenantId,
|
tenantId,
|
||||||
billDTO,
|
billDTO,
|
||||||
user
|
user
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedBill.id,
|
id: storedBill.id,
|
||||||
message: 'The bill has been created successfully.',
|
message: 'The bill has been created successfully.',
|
||||||
@@ -241,13 +241,13 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async editBill(req: Request, res: Response, next: NextFunction) {
|
private async editBill(req: Request, res: Response, next: NextFunction) {
|
||||||
const { id: billId } = req.params;
|
const { id: billId } = req.params;
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const billDTO: IBillEditDTO = this.matchedBodyData(req);
|
const billDTO: IBillEditDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.billsService.editBill(tenantId, billId, billDTO, user);
|
await this.billsApplication.editBill(tenantId, billId, billDTO, user);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: billId,
|
id: billId,
|
||||||
@@ -263,12 +263,12 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async openBill(req: Request, res: Response, next: NextFunction) {
|
private async openBill(req: Request, res: Response, next: NextFunction) {
|
||||||
const { id: billId } = req.params;
|
const { id: billId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.billsService.openBill(tenantId, billId);
|
await this.billsApplication.openBill(tenantId, billId);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: billId,
|
id: billId,
|
||||||
@@ -285,12 +285,12 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async getBill(req: Request, res: Response, next: NextFunction) {
|
private async getBill(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: billId } = req.params;
|
const { id: billId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bill = await this.billsService.getBill(tenantId, billId);
|
const bill = await this.billsApplication.getBill(tenantId, billId);
|
||||||
|
|
||||||
return res.status(200).send(this.transfromToResponse({ bill }));
|
return res.status(200).send(this.transfromToResponse({ bill }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -304,12 +304,12 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async deleteBill(req: Request, res: Response, next: NextFunction) {
|
private async deleteBill(req: Request, res: Response, next: NextFunction) {
|
||||||
const billId = req.params.id;
|
const billId = req.params.id;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.billsService.deleteBill(tenantId, billId);
|
await this.billsApplication.deleteBill(tenantId, billId);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: billId,
|
id: billId,
|
||||||
@@ -326,7 +326,7 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
public async billsList(req: Request, res: Response, next: NextFunction) {
|
private async billsList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -338,7 +338,7 @@ export default class BillsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { bills, pagination, filterMeta } =
|
const { bills, pagination, filterMeta } =
|
||||||
await this.billsService.getBills(tenantId, filter);
|
await this.billsApplication.getBills(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
bills: this.transfromToResponse(bills),
|
bills: this.transfromToResponse(bills),
|
||||||
@@ -356,12 +356,13 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
public async getDueBills(req: Request, res: Response, next: NextFunction) {
|
private async getDueBills(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { vendorId } = this.matchedQueryData(req);
|
const { vendorId } = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bills = await this.billsService.getDueBills(tenantId, vendorId);
|
const bills = await this.billsApplication.getDueBills(tenantId, vendorId);
|
||||||
|
|
||||||
return res.status(200).send({ bills });
|
return res.status(200).send({ bills });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -374,7 +375,7 @@ export default class BillsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
public getBillPaymentsTransactions = async (
|
private getBillPaymentsTransactions = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -383,7 +384,7 @@ export default class BillsController extends BaseController {
|
|||||||
const { id: billId } = req.params;
|
const { id: billId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const billPayments = await this.billPayments.getBillPayments(
|
const billPayments = await this.billsApplication.getBillPayments(
|
||||||
tenantId,
|
tenantId,
|
||||||
billId
|
billId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { check, param, query, ValidationChain } from 'express-validator';
|
|||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
|
import { BillPaymentsApplication } from '@/services/Purchases/BillPayments/BillPaymentsApplication';
|
||||||
import BillPaymentsPages from '@/services/Purchases/BillPayments/BillPaymentsPages';
|
import BillPaymentsPages from '@/services/Purchases/BillPayments/BillPaymentsPages';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
@@ -17,18 +17,18 @@ import { AbilitySubject, IPaymentMadeAction } from '@/interfaces';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class BillsPayments extends BaseController {
|
export default class BillsPayments extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
billPaymentService: BillPaymentsService;
|
private billPaymentsApplication: BillPaymentsApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
dynamicListService: DynamicListingService;
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
billPaymentsPages: BillPaymentsPages;
|
private billPaymentsPages: BillPaymentsPages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -106,7 +106,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
* Bill payments schema validation.
|
* Bill payments schema validation.
|
||||||
* @return {ValidationChain[]}
|
* @return {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get billPaymentSchemaValidation(): ValidationChain[] {
|
private get billPaymentSchemaValidation(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('vendor_id').exists().isNumeric().toInt(),
|
check('vendor_id').exists().isNumeric().toInt(),
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
@@ -129,7 +129,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
* Specific bill payment schema validation.
|
* Specific bill payment schema validation.
|
||||||
* @returns {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get specificBillPaymentValidateSchema(): ValidationChain[] {
|
private get specificBillPaymentValidateSchema(): ValidationChain[] {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
* Bills payment list validation schema.
|
* Bills payment list validation schema.
|
||||||
* @returns {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get listingValidationSchema(): ValidationChain[] {
|
private get listingValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
query('custom_view_id').optional().isNumeric().toInt(),
|
query('custom_view_id').optional().isNumeric().toInt(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
@@ -154,7 +154,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async getBillPaymentNewPageEntries(req: Request, res: Response) {
|
private async getBillPaymentNewPageEntries(req: Request, res: Response) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { vendorId } = this.matchedQueryData(req);
|
const { vendorId } = this.matchedQueryData(req);
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getBillPaymentEditPage(
|
private async getBillPaymentEditPage(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -205,16 +205,19 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async createBillPayment(req: Request, res: Response, next: NextFunction) {
|
private async createBillPayment(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const billPaymentDTO = this.matchedBodyData(req);
|
const billPaymentDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const billPayment = await this.billPaymentService.createBillPayment(
|
const billPayment = await this.billPaymentsApplication.createBillPayment(
|
||||||
tenantId,
|
tenantId,
|
||||||
billPaymentDTO
|
billPaymentDTO
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: billPayment.id,
|
id: billPayment.id,
|
||||||
message: 'Payment made has been created successfully.',
|
message: 'Payment made has been created successfully.',
|
||||||
@@ -229,13 +232,17 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async editBillPayment(req: Request, res: Response, next: NextFunction) {
|
private async editBillPayment(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const billPaymentDTO = this.matchedBodyData(req);
|
const billPaymentDTO = this.matchedBodyData(req);
|
||||||
const { id: billPaymentId } = req.params;
|
const { id: billPaymentId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const paymentMade = await this.billPaymentService.editBillPayment(
|
const paymentMade = await this.billPaymentsApplication.editBillPayment(
|
||||||
tenantId,
|
tenantId,
|
||||||
billPaymentId,
|
billPaymentId,
|
||||||
billPaymentDTO
|
billPaymentDTO
|
||||||
@@ -256,12 +263,19 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @return {Response} res -
|
* @return {Response} res -
|
||||||
*/
|
*/
|
||||||
async deleteBillPayment(req: Request, res: Response, next: NextFunction) {
|
private async deleteBillPayment(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: billPaymentId } = req.params;
|
const { id: billPaymentId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.billPaymentService.deleteBillPayment(tenantId, billPaymentId);
|
await this.billPaymentsApplication.deleteBillPayment(
|
||||||
|
tenantId,
|
||||||
|
billPaymentId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: billPaymentId,
|
id: billPaymentId,
|
||||||
@@ -277,16 +291,19 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getBillPayment(req: Request, res: Response, next: NextFunction) {
|
private async getBillPayment(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: billPaymentId } = req.params;
|
const { id: billPaymentId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const billPayment = await this.billPaymentService.getBillPayment(
|
const billPayment = await this.billPaymentsApplication.getBillPayment(
|
||||||
tenantId,
|
tenantId,
|
||||||
billPaymentId
|
billPaymentId
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
bill_payment: this.transfromToResponse(billPayment),
|
bill_payment: this.transfromToResponse(billPayment),
|
||||||
});
|
});
|
||||||
@@ -301,12 +318,16 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async getPaymentBills(req: Request, res: Response, next: NextFunction) {
|
private async getPaymentBills(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: billPaymentId } = req.params;
|
const { id: billPaymentId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bills = await this.billPaymentService.getPaymentBills(
|
const bills = await this.billPaymentsApplication.getPaymentBills(
|
||||||
tenantId,
|
tenantId,
|
||||||
billPaymentId
|
billPaymentId
|
||||||
);
|
);
|
||||||
@@ -322,7 +343,11 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async getBillsPayments(req: Request, res: Response, next: NextFunction) {
|
private async getBillsPayments(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const billPaymentsFilter = {
|
const billPaymentsFilter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -335,7 +360,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { billPayments, pagination, filterMeta } =
|
const { billPayments, pagination, filterMeta } =
|
||||||
await this.billPaymentService.listBillPayments(
|
await this.billPaymentsApplication.getBillPayments(
|
||||||
tenantId,
|
tenantId,
|
||||||
billPaymentsFilter
|
billPaymentsFilter
|
||||||
);
|
);
|
||||||
@@ -357,7 +382,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
handleServiceError(
|
private handleServiceError(
|
||||||
error: Error,
|
error: Error,
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
|
|||||||
@@ -1,42 +1,29 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query, ValidationChain } from 'express-validator';
|
import { check, param, query, ValidationChain } from 'express-validator';
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import {
|
import {
|
||||||
AbilitySubject,
|
AbilitySubject,
|
||||||
IPaymentReceiveDTO,
|
IPaymentReceiveDTO,
|
||||||
PaymentReceiveAction,
|
PaymentReceiveAction,
|
||||||
SaleInvoiceAction,
|
|
||||||
} 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';
|
||||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
|
||||||
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
|
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
|
||||||
import PaymentReceiveNotifyBySms from '@/services/Sales/PaymentReceives/PaymentReceiveSmsNotify';
|
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import GetPaymentReceivePdf from '@/services/Sales/PaymentReceives/GetPaymentReeceivePdf';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
/**
|
|
||||||
* Payments receives controller.
|
|
||||||
* @service
|
|
||||||
*/
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class PaymentReceivesController extends BaseController {
|
export default class PaymentReceivesController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
paymentReceiveService: PaymentReceiveService;
|
private paymentReceiveApplication: PaymentReceivesApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
PaymentReceivesPages: PaymentReceivesPages;
|
private PaymentReceivesPages: PaymentReceivesPages;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
dynamicListService: DynamicListingService;
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
paymentReceiveSmsNotify: PaymentReceiveNotifyBySms;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
paymentReceivePdf: GetPaymentReceivePdf;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -137,7 +124,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* Payment receive schema.
|
* Payment receive schema.
|
||||||
* @return {Array}
|
* @return {Array}
|
||||||
*/
|
*/
|
||||||
get paymentReceiveSchema(): ValidationChain[] {
|
private get paymentReceiveSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('customer_id').exists().isNumeric().toInt(),
|
check('customer_id').exists().isNumeric().toInt(),
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
@@ -162,7 +149,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Payment receive list validation schema.
|
* Payment receive list validation schema.
|
||||||
*/
|
*/
|
||||||
get validatePaymentReceiveList(): ValidationChain[] {
|
private get validatePaymentReceiveList(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
|
|
||||||
@@ -181,7 +168,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Validate payment receive parameters.
|
* Validate payment receive parameters.
|
||||||
*/
|
*/
|
||||||
get paymentReceiveValidation() {
|
private get paymentReceiveValidation() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,14 +176,14 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* New payment receive validation schema.
|
* New payment receive validation schema.
|
||||||
* @return {Array}
|
* @return {Array}
|
||||||
*/
|
*/
|
||||||
get newPaymentReceiveValidation() {
|
private get newPaymentReceiveValidation() {
|
||||||
return [...this.paymentReceiveSchema];
|
return [...this.paymentReceiveSchema];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit payment receive validation.
|
* Edit payment receive validation.
|
||||||
*/
|
*/
|
||||||
get editPaymentReceiveValidation() {
|
private get editPaymentReceiveValidation() {
|
||||||
return [
|
return [
|
||||||
param('id').exists().isNumeric().toInt(),
|
param('id').exists().isNumeric().toInt(),
|
||||||
...this.paymentReceiveSchema,
|
...this.paymentReceiveSchema,
|
||||||
@@ -209,13 +196,17 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async newPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
private async newPaymentReceive(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedPaymentReceive =
|
const storedPaymentReceive =
|
||||||
await this.paymentReceiveService.createPaymentReceive(
|
await this.paymentReceiveApplication.createPaymentReceive(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
user
|
user
|
||||||
@@ -235,14 +226,18 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async editPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
private async editPaymentReceive(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.paymentReceiveService.editPaymentReceive(
|
await this.paymentReceiveApplication.editPaymentReceive(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
@@ -262,17 +257,20 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async deletePaymentReceive(req: Request, res: Response, next: NextFunction) {
|
private async deletePaymentReceive(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.paymentReceiveService.deletePaymentReceive(
|
await this.paymentReceiveApplication.deletePaymentReceive(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
user
|
user
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: paymentReceiveId,
|
id: paymentReceiveId,
|
||||||
message: 'The payment receive has been deleted successfully',
|
message: 'The payment receive has been deleted successfully',
|
||||||
@@ -288,7 +286,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveInvoices(
|
private async getPaymentReceiveInvoices(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -298,7 +296,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const saleInvoices =
|
const saleInvoices =
|
||||||
await this.paymentReceiveService.getPaymentReceiveInvoices(
|
await this.paymentReceiveApplication.getPaymentReceiveInvoices(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
@@ -315,7 +313,11 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
private async getPaymentReceiveList(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
@@ -327,7 +329,10 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { paymentReceives, pagination, filterMeta } =
|
const { paymentReceives, pagination, filterMeta } =
|
||||||
await this.paymentReceiveService.listPaymentReceives(tenantId, filter);
|
await this.paymentReceiveApplication.getPaymentReceives(
|
||||||
|
tenantId,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
payment_receives: this.transfromToResponse(paymentReceives),
|
payment_receives: this.transfromToResponse(paymentReceives),
|
||||||
@@ -344,7 +349,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Request} req - Request.
|
* @param {Request} req - Request.
|
||||||
* @param {Response} res - Response.
|
* @param {Response} res - Response.
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveNewPageEntries(
|
private async getPaymentReceiveNewPageEntries(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -371,7 +376,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveEditPage(
|
private async getPaymentReceiveEditPage(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -402,15 +407,20 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async getPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
private async getPaymentReceive(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const paymentReceive = await this.paymentReceiveService.getPaymentReceive(
|
const paymentReceive =
|
||||||
tenantId,
|
await this.paymentReceiveApplication.getPaymentReceive(
|
||||||
paymentReceiveId
|
tenantId,
|
||||||
);
|
paymentReceiveId
|
||||||
|
);
|
||||||
|
|
||||||
const ACCEPT_TYPE = {
|
const ACCEPT_TYPE = {
|
||||||
APPLICATION_PDF: 'application/pdf',
|
APPLICATION_PDF: 'application/pdf',
|
||||||
@@ -423,10 +433,11 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||||
const pdfContent = await this.paymentReceivePdf.getPaymentReceivePdf(
|
const pdfContent =
|
||||||
tenantId,
|
await this.paymentReceiveApplication.getPaymentReceivePdf(
|
||||||
paymentReceive
|
tenantId,
|
||||||
);
|
paymentReceive
|
||||||
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
@@ -454,10 +465,11 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const paymentReceive = await this.paymentReceiveSmsNotify.notifyBySms(
|
const paymentReceive =
|
||||||
tenantId,
|
await this.paymentReceiveApplication.notifyPaymentBySms(
|
||||||
paymentReceiveId
|
tenantId,
|
||||||
);
|
paymentReceiveId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: paymentReceive.id,
|
id: paymentReceive.id,
|
||||||
message: 'The payment notification has been sent successfully.',
|
message: 'The payment notification has been sent successfully.',
|
||||||
@@ -482,10 +494,11 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const smsDetails = await this.paymentReceiveSmsNotify.smsDetails(
|
const smsDetails =
|
||||||
tenantId,
|
await this.paymentReceiveApplication.getPaymentSmsDetails(
|
||||||
paymentReceiveId
|
tenantId,
|
||||||
);
|
paymentReceiveId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
data: smsDetails,
|
data: smsDetails,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query, matchedData } 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,
|
||||||
SaleInvoiceAction,
|
|
||||||
} 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';
|
||||||
import SaleEstimateService from '@/services/Sales/SalesEstimate';
|
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import SaleEstimatesPdfService from '@/services/Sales/Estimates/SaleEstimatesPdf';
|
|
||||||
import SaleEstimateNotifyBySms from '@/services/Sales/Estimates/SaleEstimateSmsNotify';
|
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
|
import { SaleEstimatesApplication } from '@/services/Sales/Estimates/SaleEstimatesApplication';
|
||||||
|
|
||||||
const ACCEPT_TYPE = {
|
const ACCEPT_TYPE = {
|
||||||
APPLICATION_PDF: 'application/pdf',
|
APPLICATION_PDF: 'application/pdf',
|
||||||
@@ -23,21 +20,15 @@ const ACCEPT_TYPE = {
|
|||||||
@Service()
|
@Service()
|
||||||
export default class SalesEstimatesController extends BaseController {
|
export default class SalesEstimatesController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
saleEstimateService: SaleEstimateService;
|
private saleEstimatesApplication: SaleEstimatesApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
dynamicListService: DynamicListingService;
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleEstimatesPdf: SaleEstimatesPdfService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleEstimateNotifySms: SaleEstimateNotifyBySms;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -136,7 +127,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Estimate validation schema.
|
* Estimate validation schema.
|
||||||
*/
|
*/
|
||||||
get estimateValidationSchema() {
|
private get estimateValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('customer_id').exists().isNumeric().toInt(),
|
check('customer_id').exists().isNumeric().toInt(),
|
||||||
check('estimate_date').exists().isISO8601().toDate(),
|
check('estimate_date').exists().isISO8601().toDate(),
|
||||||
@@ -177,14 +168,14 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Specific sale estimate validation schema.
|
* Specific sale estimate validation schema.
|
||||||
*/
|
*/
|
||||||
get validateSpecificEstimateSchema() {
|
private get validateSpecificEstimateSchema() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sales estimates list validation schema.
|
* Sales estimates list validation schema.
|
||||||
*/
|
*/
|
||||||
get validateEstimateListSchema() {
|
private get validateEstimateListSchema() {
|
||||||
return [
|
return [
|
||||||
query('view_slug').optional().isString().trim(),
|
query('view_slug').optional().isString().trim(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
@@ -202,15 +193,16 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @return {Response} res -
|
* @return {Response} res -
|
||||||
*/
|
*/
|
||||||
async newEstimate(req: Request, res: Response, next: NextFunction) {
|
private async newEstimate(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedEstimate = await this.saleEstimateService.createEstimate(
|
const storedEstimate =
|
||||||
tenantId,
|
await this.saleEstimatesApplication.createSaleEstimate(
|
||||||
estimateDTO
|
tenantId,
|
||||||
);
|
estimateDTO
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedEstimate.id,
|
id: storedEstimate.id,
|
||||||
@@ -226,19 +218,18 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async editEstimate(req: Request, res: Response, next: NextFunction) {
|
private async editEstimate(req: Request, res: Response, next: NextFunction) {
|
||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update estimate with associated estimate entries.
|
// Update estimate with associated estimate entries.
|
||||||
await this.saleEstimateService.editEstimate(
|
await this.saleEstimatesApplication.editSaleEstimate(
|
||||||
tenantId,
|
tenantId,
|
||||||
estimateId,
|
estimateId,
|
||||||
estimateDTO
|
estimateDTO
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
message: 'The sale estimate has been created successfully.',
|
message: 'The sale estimate has been created successfully.',
|
||||||
@@ -253,13 +244,19 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async deleteEstimate(req: Request, res: Response, next: NextFunction) {
|
private async deleteEstimate(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleEstimateService.deleteEstimate(tenantId, estimateId);
|
await this.saleEstimatesApplication.deleteSaleEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
message: 'The sale estimate has been deleted successfully.',
|
message: 'The sale estimate has been deleted successfully.',
|
||||||
@@ -274,13 +271,19 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async deliverSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
private async deliverSaleEstimate(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleEstimateService.deliverSaleEstimate(tenantId, estimateId);
|
await this.saleEstimatesApplication.deliverSaleEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
message: 'The sale estimate has been delivered successfully.',
|
message: 'The sale estimate has been delivered successfully.',
|
||||||
@@ -296,12 +299,19 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async approveSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
private async approveSaleEstimate(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleEstimateService.approveSaleEstimate(tenantId, estimateId);
|
await this.saleEstimatesApplication.approveSaleEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
@@ -318,12 +328,19 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async rejectSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
private async rejectSaleEstimate(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleEstimateService.rejectSaleEstimate(tenantId, estimateId);
|
await this.saleEstimatesApplication.rejectSaleEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
@@ -340,12 +357,12 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async getEstimate(req: Request, res: Response, next: NextFunction) {
|
private async getEstimate(req: Request, res: Response, next: NextFunction) {
|
||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const estimate = await this.saleEstimateService.getEstimate(
|
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
|
||||||
tenantId,
|
tenantId,
|
||||||
estimateId
|
estimateId
|
||||||
);
|
);
|
||||||
@@ -357,10 +374,11 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
},
|
},
|
||||||
// PDF content type.
|
// PDF content type.
|
||||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||||
const pdfContent = await this.saleEstimatesPdf.saleEstimatePdf(
|
const pdfContent =
|
||||||
tenantId,
|
await this.saleEstimatesApplication.getSaleEstimatePdf(
|
||||||
estimate
|
tenantId,
|
||||||
);
|
estimate
|
||||||
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
@@ -378,7 +396,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
private async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
@@ -390,7 +408,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { salesEstimates, pagination, filterMeta } =
|
const { salesEstimates, pagination, filterMeta } =
|
||||||
await this.saleEstimateService.estimatesList(tenantId, filter);
|
await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter);
|
||||||
|
|
||||||
res.format({
|
res.format({
|
||||||
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
|
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
|
||||||
@@ -408,7 +426,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saleEstimateNotifyBySms = async (
|
private saleEstimateNotifyBySms = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -417,10 +435,11 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleEstimate = await this.saleEstimateNotifySms.notifyBySms(
|
const saleEstimate =
|
||||||
tenantId,
|
await this.saleEstimatesApplication.notifySaleEstimateBySms(
|
||||||
estimateId
|
tenantId,
|
||||||
);
|
estimateId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleEstimate.id,
|
id: saleEstimate.id,
|
||||||
message:
|
message:
|
||||||
@@ -437,7 +456,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
public saleEstimateSmsDetails = async (
|
private saleEstimateSmsDetails = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -446,10 +465,11 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const { id: estimateId } = req.params;
|
const { id: estimateId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const estimateSmsDetails = await this.saleEstimateNotifySms.smsDetails(
|
const estimateSmsDetails =
|
||||||
tenantId,
|
await this.saleEstimatesApplication.getSaleEstimateSmsDetails(
|
||||||
estimateId
|
tenantId,
|
||||||
);
|
estimateId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
data: estimateSmsDetails,
|
data: estimateSmsDetails,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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';
|
||||||
import SaleInvoiceService from '@/services/Sales/SalesInvoices';
|
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import {
|
import {
|
||||||
@@ -12,11 +11,8 @@ import {
|
|||||||
SaleInvoiceAction,
|
SaleInvoiceAction,
|
||||||
AbilitySubject,
|
AbilitySubject,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import SaleInvoicePdf from '@/services/Sales/SaleInvoicePdf';
|
|
||||||
import SaleInvoiceWriteoff from '@/services/Sales/SaleInvoiceWriteoff';
|
|
||||||
import SaleInvoiceNotifyBySms from '@/services/Sales/SaleInvoiceNotifyBySms';
|
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
import InvoicePaymentsService from '@/services/Sales/Invoices/InvoicePaymentsService';
|
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
|
||||||
|
|
||||||
const ACCEPT_TYPE = {
|
const ACCEPT_TYPE = {
|
||||||
APPLICATION_PDF: 'application/pdf',
|
APPLICATION_PDF: 'application/pdf',
|
||||||
@@ -25,27 +21,15 @@ const ACCEPT_TYPE = {
|
|||||||
@Service()
|
@Service()
|
||||||
export default class SaleInvoicesController extends BaseController {
|
export default class SaleInvoicesController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
saleInvoiceService: SaleInvoiceService;
|
private saleInvoiceApplication: SaleInvoiceApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
dynamicListService: DynamicListingService;
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleInvoicePdf: SaleInvoicePdf;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleInvoiceWriteoff: SaleInvoiceWriteoff;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleInvoiceSmsNotify: SaleInvoiceNotifyBySms;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
invoicePaymentsSerivce: InvoicePaymentsService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -167,7 +151,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Sale invoice validation schema.
|
* Sale invoice validation schema.
|
||||||
*/
|
*/
|
||||||
get saleInvoiceValidationSchema() {
|
private get saleInvoiceValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('customer_id').exists().isNumeric().toInt(),
|
check('customer_id').exists().isNumeric().toInt(),
|
||||||
check('invoice_date').exists().isISO8601().toDate(),
|
check('invoice_date').exists().isISO8601().toDate(),
|
||||||
@@ -227,14 +211,14 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Specific sale invoice validation schema.
|
* Specific sale invoice validation schema.
|
||||||
*/
|
*/
|
||||||
get specificSaleInvoiceValidation() {
|
private get specificSaleInvoiceValidation() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sales invoices list validation schema.
|
* Sales invoices list validation schema.
|
||||||
*/
|
*/
|
||||||
get saleInvoiceListValidationSchema() {
|
private get saleInvoiceListValidationSchema() {
|
||||||
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(),
|
||||||
@@ -249,7 +233,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Due sale invoice list validation schema.
|
* Due sale invoice list validation schema.
|
||||||
*/
|
*/
|
||||||
get dueSalesInvoicesListValidationSchema() {
|
private get dueSalesInvoicesListValidationSchema() {
|
||||||
return [query('customer_id').optional().isNumeric().toInt()];
|
return [query('customer_id').optional().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,17 +243,22 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
async newSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
private async newSaleInvoice(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const saleInvoiceDTO: ISaleInvoiceCreateDTO = this.matchedBodyData(req);
|
const saleInvoiceDTO: ISaleInvoiceCreateDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Creates a new sale invoice with associated entries.
|
// Creates a new sale invoice with associated entries.
|
||||||
const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice(
|
const storedSaleInvoice =
|
||||||
tenantId,
|
await this.saleInvoiceApplication.createSaleInvoice(
|
||||||
saleInvoiceDTO,
|
tenantId,
|
||||||
user
|
saleInvoiceDTO,
|
||||||
);
|
user
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedSaleInvoice.id,
|
id: storedSaleInvoice.id,
|
||||||
message: 'The sale invoice has been created successfully.',
|
message: 'The sale invoice has been created successfully.',
|
||||||
@@ -285,14 +274,18 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
async editSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
private async editSaleInvoice(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const { id: saleInvoiceId } = req.params;
|
const { id: saleInvoiceId } = req.params;
|
||||||
const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req);
|
const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the given sale invoice details.
|
// Update the given sale invoice details.
|
||||||
await this.saleInvoiceService.editSaleInvoice(
|
await this.saleInvoiceApplication.editSaleInvoice(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
saleInvoiceOTD,
|
saleInvoiceOTD,
|
||||||
@@ -313,12 +306,16 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @param {NextFunction} next -
|
* @param {NextFunction} next -
|
||||||
*/
|
*/
|
||||||
async deliverSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
private async deliverSaleInvoice(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const { id: saleInvoiceId } = req.params;
|
const { id: saleInvoiceId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleInvoiceService.deliverSaleInvoice(
|
await this.saleInvoiceApplication.deliverSaleInvoice(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
user
|
user
|
||||||
@@ -338,13 +335,17 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
async deleteSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
private async deleteSaleInvoice(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { id: saleInvoiceId } = req.params;
|
const { id: saleInvoiceId } = req.params;
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Deletes the sale invoice with associated entries and journal transaction.
|
// Deletes the sale invoice with associated entries and journal transaction.
|
||||||
await this.saleInvoiceService.deleteSaleInvoice(
|
await this.saleInvoiceApplication.deleteSaleInvoice(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
user
|
user
|
||||||
@@ -364,12 +365,16 @@ 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.
|
||||||
*/
|
*/
|
||||||
async getSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
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;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleInvoice = await this.saleInvoiceService.getSaleInvoice(
|
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
user
|
user
|
||||||
@@ -384,7 +389,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
},
|
},
|
||||||
// PDF content type.
|
// PDF content type.
|
||||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||||
const pdfContent = await this.saleInvoicePdf.saleInvoicePdf(
|
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoice
|
saleInvoice
|
||||||
);
|
);
|
||||||
@@ -420,7 +425,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const { salesInvoices, filterMeta, pagination } =
|
const { salesInvoices, filterMeta, pagination } =
|
||||||
await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_invoices: this.transfromToResponse(salesInvoices),
|
sales_invoices: this.transfromToResponse(salesInvoices),
|
||||||
@@ -448,10 +453,11 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const { customerId } = this.matchedQueryData(req);
|
const { customerId } = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const salesInvoices = await this.saleInvoiceService.getPayableInvoices(
|
const salesInvoices =
|
||||||
tenantId,
|
await this.saleInvoiceApplication.getReceivableSaleInvoices(
|
||||||
customerId
|
tenantId,
|
||||||
);
|
customerId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_invoices: this.transfromToResponse(salesInvoices),
|
sales_invoices: this.transfromToResponse(salesInvoices),
|
||||||
});
|
});
|
||||||
@@ -477,7 +483,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const writeoffDTO = this.matchedBodyData(req);
|
const writeoffDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleInvoice = await this.saleInvoiceWriteoff.writeOff(
|
const saleInvoice = await this.saleInvoiceApplication.writeOff(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId,
|
invoiceId,
|
||||||
writeoffDTO
|
writeoffDTO
|
||||||
@@ -485,7 +491,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleInvoice.id,
|
id: saleInvoice.id,
|
||||||
message: 'The given sale invoice has been writte-off successfully.',
|
message: 'The given sale invoice has been written-off successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -507,7 +513,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const { id: invoiceId } = req.params;
|
const { id: invoiceId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleInvoice = await this.saleInvoiceWriteoff.cancelWrittenoff(
|
const saleInvoice = await this.saleInvoiceApplication.cancelWrittenoff(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
@@ -538,11 +544,12 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const invoiceNotifySmsDTO = this.matchedBodyData(req);
|
const invoiceNotifySmsDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleInvoice = await this.saleInvoiceSmsNotify.notifyBySms(
|
const saleInvoice =
|
||||||
tenantId,
|
await this.saleInvoiceApplication.notifySaleInvoiceBySms(
|
||||||
invoiceId,
|
tenantId,
|
||||||
invoiceNotifySmsDTO.notificationKey
|
invoiceId,
|
||||||
);
|
invoiceNotifySmsDTO.notificationKey
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleInvoice.id,
|
id: saleInvoice.id,
|
||||||
message:
|
message:
|
||||||
@@ -569,11 +576,12 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const smsDetailsDTO = this.matchedQueryData(req);
|
const smsDetailsDTO = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const invoiceSmsDetails = await this.saleInvoiceSmsNotify.smsDetails(
|
const invoiceSmsDetails =
|
||||||
tenantId,
|
await this.saleInvoiceApplication.getSaleInvoiceSmsDetails(
|
||||||
invoiceId,
|
tenantId,
|
||||||
smsDetailsDTO
|
invoiceId,
|
||||||
);
|
smsDetailsDTO
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
data: invoiceSmsDetails,
|
data: invoiceSmsDetails,
|
||||||
});
|
});
|
||||||
@@ -599,7 +607,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const invoicePayments =
|
const invoicePayments =
|
||||||
await this.invoicePaymentsSerivce.getInvoicePayments(
|
await this.saleInvoiceApplication.getInvoicePayments(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,34 +2,26 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { 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 SaleReceiptService from '@/services/Sales/SalesReceipts';
|
|
||||||
import SaleReceiptsPdfService from '@/services/Sales/Receipts/SaleReceiptsPdfService';
|
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
|
import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import SaleReceiptNotifyBySms from '@/services/Sales/SaleReceiptNotifyBySms';
|
|
||||||
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';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SalesReceiptsController extends BaseController {
|
export default class SalesReceiptsController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
saleReceiptService: SaleReceiptService;
|
private saleReceiptsApplication: SaleReceiptApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
saleReceiptsPdf: SaleReceiptsPdfService;
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
dynamicListService: DynamicListingService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleReceiptSmsNotify: SaleReceiptNotifyBySms;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -105,7 +97,7 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
* Sales receipt validation schema.
|
* Sales receipt validation schema.
|
||||||
* @return {Array}
|
* @return {Array}
|
||||||
*/
|
*/
|
||||||
get salesReceiptsValidationSchema() {
|
private get salesReceiptsValidationSchema() {
|
||||||
return [
|
return [
|
||||||
check('customer_id').exists().isNumeric().toInt(),
|
check('customer_id').exists().isNumeric().toInt(),
|
||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
@@ -146,14 +138,14 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Specific sale receipt validation schema.
|
* Specific sale receipt validation schema.
|
||||||
*/
|
*/
|
||||||
get specificReceiptValidationSchema() {
|
private get specificReceiptValidationSchema() {
|
||||||
return [param('id').exists().isNumeric().toInt()];
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List sales receipts validation schema.
|
* List sales receipts validation schema.
|
||||||
*/
|
*/
|
||||||
get listSalesReceiptsValidationSchema() {
|
private get listSalesReceiptsValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('view_slug').optional().isString().trim(),
|
query('view_slug').optional().isString().trim(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
@@ -170,16 +162,21 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async newSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
private async newSaleReceipt(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const saleReceiptDTO: ISaleReceiptDTO = this.matchedBodyData(req);
|
const saleReceiptDTO: ISaleReceiptDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Store the given sale receipt details with associated entries.
|
// Store the given sale receipt details with associated entries.
|
||||||
const storedSaleReceipt = await this.saleReceiptService.createSaleReceipt(
|
const storedSaleReceipt =
|
||||||
tenantId,
|
await this.saleReceiptsApplication.createSaleReceipt(
|
||||||
saleReceiptDTO
|
tenantId,
|
||||||
);
|
saleReceiptDTO
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedSaleReceipt.id,
|
id: storedSaleReceipt.id,
|
||||||
message: 'Sale receipt has been created successfully.',
|
message: 'Sale receipt has been created successfully.',
|
||||||
@@ -194,13 +191,20 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async deleteSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
private async deleteSaleReceipt(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: saleReceiptId } = req.params;
|
const { id: saleReceiptId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Deletes the sale receipt.
|
// Deletes the sale receipt.
|
||||||
await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
|
await this.saleReceiptsApplication.deleteSaleReceipt(
|
||||||
|
tenantId,
|
||||||
|
saleReceiptId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleReceiptId,
|
id: saleReceiptId,
|
||||||
@@ -217,14 +221,18 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async editSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
private async editSaleReceipt(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: saleReceiptId } = req.params;
|
const { id: saleReceiptId } = req.params;
|
||||||
const saleReceipt = this.matchedBodyData(req);
|
const saleReceipt = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the given sale receipt details.
|
// Update the given sale receipt details.
|
||||||
await this.saleReceiptService.editSaleReceipt(
|
await this.saleReceiptsApplication.editSaleReceipt(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId,
|
saleReceiptId,
|
||||||
saleReceipt
|
saleReceipt
|
||||||
@@ -244,13 +252,20 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async closeSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
private async closeSaleReceipt(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: saleReceiptId } = req.params;
|
const { id: saleReceiptId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the given sale receipt details.
|
// Update the given sale receipt details.
|
||||||
await this.saleReceiptService.closeSaleReceipt(tenantId, saleReceiptId);
|
await this.saleReceiptsApplication.closeSaleReceipt(
|
||||||
|
tenantId,
|
||||||
|
saleReceiptId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleReceiptId,
|
id: saleReceiptId,
|
||||||
message: 'Sale receipt has been closed successfully.',
|
message: 'Sale receipt has been closed successfully.',
|
||||||
@@ -265,7 +280,11 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
private async getSalesReceipts(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
@@ -274,10 +293,9 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, pagination, filterMeta } =
|
const { data, pagination, filterMeta } =
|
||||||
await this.saleReceiptService.salesReceiptsList(tenantId, filter);
|
await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter);
|
||||||
|
|
||||||
const response = this.transfromToResponse({
|
const response = this.transfromToResponse({
|
||||||
data,
|
data,
|
||||||
@@ -301,11 +319,10 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleReceipt = await this.saleReceiptService.getSaleReceipt(
|
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
|
|
||||||
res.format({
|
res.format({
|
||||||
'application/json': () => {
|
'application/json': () => {
|
||||||
return res
|
return res
|
||||||
@@ -313,10 +330,11 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
.send(this.transfromToResponse({ saleReceipt }));
|
.send(this.transfromToResponse({ saleReceipt }));
|
||||||
},
|
},
|
||||||
'application/pdf': async () => {
|
'application/pdf': async () => {
|
||||||
const pdfContent = await this.saleReceiptsPdf.saleReceiptPdf(
|
const pdfContent =
|
||||||
tenantId,
|
await this.saleReceiptsApplication.getSaleReceiptPdf(
|
||||||
saleReceipt
|
tenantId,
|
||||||
);
|
saleReceipt
|
||||||
|
);
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': 'application/pdf',
|
'Content-Type': 'application/pdf',
|
||||||
'Content-Length': pdfContent.length,
|
'Content-Length': pdfContent.length,
|
||||||
@@ -344,10 +362,11 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
const { id: receiptId } = req.params;
|
const { id: receiptId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saleReceipt = await this.saleReceiptSmsNotify.notifyBySms(
|
const saleReceipt =
|
||||||
tenantId,
|
await this.saleReceiptsApplication.saleReceiptNotifyBySms(
|
||||||
receiptId
|
tenantId,
|
||||||
);
|
receiptId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleReceipt.id,
|
id: saleReceipt.id,
|
||||||
message:
|
message:
|
||||||
@@ -373,10 +392,11 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
const { id: receiptId } = req.params;
|
const { id: receiptId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const smsDetails = await this.saleReceiptSmsNotify.smsDetails(
|
const smsDetails =
|
||||||
tenantId,
|
await this.saleReceiptsApplication.getSaleReceiptSmsDetails(
|
||||||
receiptId
|
tenantId,
|
||||||
);
|
receiptId
|
||||||
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
data: smsDetails,
|
data: smsDetails,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { Container, Service } from 'typedi';
|
import { Container, Service } from 'typedi';
|
||||||
|
import SalesInvoices from './SalesInvoices'
|
||||||
import SalesEstimates from './SalesEstimates';
|
import SalesEstimates from './SalesEstimates';
|
||||||
import SalesReceipts from './SalesReceipts';
|
import SalesReceipts from './SalesReceipts';
|
||||||
import SalesInvoices from './SalesInvoices'
|
|
||||||
import PaymentReceives from './PaymentReceives';
|
|
||||||
import CreditNotes from './CreditNotes';
|
import CreditNotes from './CreditNotes';
|
||||||
|
import PaymentReceives from './PaymentReceives';
|
||||||
@Service()
|
@Service()
|
||||||
export default class SalesController {
|
export default class SalesController {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { toInteger } from 'lodash';
|
||||||
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
|
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
const API_RATE_LIMIT = process.env.API_RATE_LIMIT?.split(',') || [];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
* Your favorite port
|
* Your favorite port
|
||||||
@@ -95,16 +98,15 @@ module.exports = {
|
|||||||
* JWT secret.
|
* JWT secret.
|
||||||
*/
|
*/
|
||||||
jwtSecret: process.env.JWT_SECRET,
|
jwtSecret: process.env.JWT_SECRET,
|
||||||
resetPasswordSeconds: 600,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
customerSuccess: {
|
resetPasswordSeconds: 600,
|
||||||
email: 'success@bigcapital.ly',
|
|
||||||
phoneNumber: '(218) 92 791 8381',
|
|
||||||
},
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application base URL.
|
||||||
|
*/
|
||||||
baseURL: process.env.BASE_URL,
|
baseURL: process.env.BASE_URL,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,19 +133,9 @@ module.exports = {
|
|||||||
blockDuration: 60 * 15,
|
blockDuration: 60 * 15,
|
||||||
},
|
},
|
||||||
requests: {
|
requests: {
|
||||||
points: 60,
|
points: API_RATE_LIMIT[0] ? toInteger(API_RATE_LIMIT[0]) : 120,
|
||||||
duration: 60,
|
duration: API_RATE_LIMIT[1] ? toInteger(API_RATE_LIMIT[1]) : 60,
|
||||||
blockDuration: 60 * 10,
|
blockDuration: API_RATE_LIMIT[2] ? toInteger(API_RATE_LIMIT[2]) : 60 * 10,
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users registeration configuration.
|
|
||||||
*/
|
|
||||||
registration: {
|
|
||||||
countries: {
|
|
||||||
whitelist: ['LY'],
|
|
||||||
blacklist: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -167,8 +159,6 @@ module.exports = {
|
|||||||
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
|
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
|
||||||
},
|
},
|
||||||
|
|
||||||
protocol: '',
|
|
||||||
hostname: '',
|
|
||||||
scheduleComputeItemCost: 'in 5 seconds',
|
scheduleComputeItemCost: 'in 5 seconds',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export interface ILedger {
|
|||||||
|
|
||||||
getEntries(): ILedgerEntry[];
|
getEntries(): ILedgerEntry[];
|
||||||
|
|
||||||
|
filter(cb: (entry: ILedgerEntry) => boolean): ILedger;
|
||||||
|
|
||||||
whereAccountId(accountId: number): ILedger;
|
whereAccountId(accountId: number): ILedger;
|
||||||
whereContactId(contactId: number): ILedger;
|
whereContactId(contactId: number): ILedger;
|
||||||
whereFromDate(fromDate: Date | string): ILedger;
|
whereFromDate(fromDate: Date | string): ILedger;
|
||||||
@@ -39,6 +41,8 @@ export interface ILedgerEntry {
|
|||||||
index: number;
|
index: number;
|
||||||
indexGroup?: number;
|
indexGroup?: number;
|
||||||
|
|
||||||
|
note?: string;
|
||||||
|
|
||||||
userId?: number;
|
userId?: number;
|
||||||
itemId?: number;
|
itemId?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
|||||||
@@ -1,38 +1,34 @@
|
|||||||
import { Container, Inject } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import AuthenticationService from '@/services/Authentication/AuthApplication';
|
import AuthenticationMailMesssages from '@/services/Authentication/AuthenticationMailMessages';
|
||||||
|
|
||||||
export default class WelcomeEmailJob {
|
export default class ResetPasswordEmailJob {
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
* @param {Agenda} agenda
|
* @param {Agenda} agenda
|
||||||
*/
|
*/
|
||||||
constructor(agenda) {
|
constructor(agenda) {
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'reset-password-mail',
|
'reset-password-mail',
|
||||||
{ priority: 'high' },
|
{ priority: 'high' },
|
||||||
this.handler.bind(this),
|
this.handler.bind(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle send welcome mail job.
|
* Handle send welcome mail job.
|
||||||
* @param {Job} job
|
* @param {Job} job
|
||||||
* @param {Function} done
|
* @param {Function} done
|
||||||
*/
|
*/
|
||||||
public async handler(job, done: Function): Promise<void> {
|
public async handler(job, done: Function): Promise<void> {
|
||||||
const { data } = job.attrs;
|
const { data } = job.attrs;
|
||||||
const { user, token } = data;
|
const { user, token } = data;
|
||||||
const Logger = Container.get('logger');
|
const authService = Container.get(AuthenticationMailMesssages);
|
||||||
const authService = Container.get(AuthenticationService);
|
|
||||||
|
|
||||||
Logger.info(`[send_reset_password] started.`, { data });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authService.mailMessages.sendResetPasswordMessage(user, token);
|
await authService.sendResetPasswordMessage(user, token);
|
||||||
Logger.info(`[send_reset_password] finished.`, { data });
|
done();
|
||||||
done()
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(`[send_reset_password] error.`, { data, error });
|
console.log(error);
|
||||||
done(error);
|
done(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Container } from 'typedi';
|
|
||||||
|
|
||||||
export default class SmsNotification {
|
|
||||||
constructor(agenda) {
|
|
||||||
agenda.define('sms-notification', { priority: 'high' }, this.handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Job}job
|
|
||||||
*/
|
|
||||||
async handler(job) {
|
|
||||||
const { message, to } = job.attrs.data;
|
|
||||||
const smsClient = Container.get('SMSClient');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await smsClient.sendMessage(to, message);
|
|
||||||
} catch (error) {
|
|
||||||
done(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Container, Inject } from 'typedi';
|
|
||||||
import AuthenticationService from '@/services/Authentication/AuthApplication';
|
|
||||||
|
|
||||||
export default class WelcomeSMSJob {
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
* @param {Agenda} agenda
|
|
||||||
*/
|
|
||||||
constructor(agenda) {
|
|
||||||
agenda.define('welcome-sms', { priority: 'high' }, this.handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle send welcome mail job.
|
|
||||||
* @param {Job} job
|
|
||||||
* @param {Function} done
|
|
||||||
*/
|
|
||||||
public async handler(job, done: Function): Promise<void> {
|
|
||||||
const { tenant, user } = job.attrs.data;
|
|
||||||
|
|
||||||
const Logger = Container.get('logger');
|
|
||||||
const authService = Container.get(AuthenticationService);
|
|
||||||
|
|
||||||
Logger.info(`[welcome_sms] started: ${job.attrs.data}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await authService.smsMessages.sendWelcomeMessage(tenant, user);
|
|
||||||
Logger.info(`[welcome_sms] finished`, { tenant, user });
|
|
||||||
done();
|
|
||||||
} catch (error) {
|
|
||||||
Logger.info(`[welcome_sms] error`, { error, tenant, user });
|
|
||||||
done(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { SaleInvoicesCost } from '@/services/Sales/Invoices/SalesInvoicesCost';
|
||||||
|
|
||||||
export default class WriteInvoicesJournalEntries {
|
export default class WriteInvoicesJournalEntries {
|
||||||
eventPublisher: EventPublisher;
|
eventPublisher: EventPublisher;
|
||||||
@@ -26,7 +26,7 @@ export default class WriteInvoicesJournalEntries {
|
|||||||
*/
|
*/
|
||||||
public async handler(job, done: Function): Promise<void> {
|
public async handler(job, done: Function): Promise<void> {
|
||||||
const { startingDate, tenantId } = job.attrs.data;
|
const { startingDate, tenantId } = job.attrs.data;
|
||||||
const salesInvoicesCost = Container.get(SalesInvoicesCost);
|
const salesInvoicesCost = Container.get(SaleInvoicesCost);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await salesInvoicesCost.writeCostLotsGLEntries(tenantId, startingDate);
|
await salesInvoicesCost.writeCostLotsGLEntries(tenantId, startingDate);
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { Container } from 'typedi';
|
|
||||||
import AuthenticationService from '@/services/Authentication/AuthApplication';
|
|
||||||
|
|
||||||
export default class WelcomeEmailJob {
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
* @param {Agenda} agenda -
|
|
||||||
*/
|
|
||||||
constructor(agenda) {
|
|
||||||
// Welcome mail and SMS message.
|
|
||||||
agenda.define(
|
|
||||||
'welcome-email',
|
|
||||||
{ priority: 'high' },
|
|
||||||
this.handler.bind(this),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle send welcome mail job.
|
|
||||||
* @param {Job} job
|
|
||||||
* @param {Function} done
|
|
||||||
*/
|
|
||||||
public async handler(job, done: Function): Promise<void> {
|
|
||||||
const { organizationId, user } = job.attrs.data;
|
|
||||||
const Logger: any = Container.get('logger');
|
|
||||||
const authService = Container.get(AuthenticationService);
|
|
||||||
|
|
||||||
Logger.info(`[welcome_mail] started: ${job.attrs.data}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await authService.mailMessages.sendWelcomeMessage(user, organizationId);
|
|
||||||
Logger.info(`[welcome_mail] finished: ${job.attrs.data}`);
|
|
||||||
done();
|
|
||||||
} catch (error) {
|
|
||||||
Logger.error(`[welcome_mail] error: ${job.attrs.data}, error: ${error}`);
|
|
||||||
done(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,11 +7,11 @@ import PaymentSyncBillBalance from '@/subscribers/PaymentMades/PaymentSyncBillBa
|
|||||||
import SaleReceiptInventoryTransactionsSubscriber from '@/subscribers/SaleReceipt/WriteInventoryTransactions';
|
import SaleReceiptInventoryTransactionsSubscriber from '@/subscribers/SaleReceipt/WriteInventoryTransactions';
|
||||||
import SaleInvoiceWriteInventoryTransactions from '@/subscribers/SaleInvoices/WriteInventoryTransactions';
|
import SaleInvoiceWriteInventoryTransactions from '@/subscribers/SaleInvoices/WriteInventoryTransactions';
|
||||||
import SaleInvoiceWriteGLEntriesSubscriber from '@/subscribers/SaleInvoices/WriteJournalEntries';
|
import SaleInvoiceWriteGLEntriesSubscriber from '@/subscribers/SaleInvoices/WriteJournalEntries';
|
||||||
|
import SaleReceiptWriteGLEntriesSubscriber from '@/subscribers/SaleReceipt/WriteJournalEntries';
|
||||||
import PaymentReceiveSyncInvoices from '@/subscribers/PaymentReceive/PaymentReceiveSyncInvoices';
|
import PaymentReceiveSyncInvoices from '@/subscribers/PaymentReceive/PaymentReceiveSyncInvoices';
|
||||||
import CashflowTransactionSubscriber from '@/services/Cashflow/CashflowTransactionSubscriber';
|
import CashflowTransactionSubscriber from '@/services/Cashflow/CashflowTransactionSubscriber';
|
||||||
import PaymentReceivesWriteGLEntriesSubscriber from '@/subscribers/PaymentReceive/WriteGLEntries';
|
import PaymentReceivesWriteGLEntriesSubscriber from '@/subscribers/PaymentReceive/WriteGLEntries';
|
||||||
import InventorySubscriber from '@/subscribers/Inventory/Inventory';
|
import InventorySubscriber from '@/subscribers/Inventory/Inventory';
|
||||||
import SaleReceiptWriteGLEntriesSubscriber from '@/subscribers/SaleReceipt/WriteJournalEntries';
|
|
||||||
import { CustomerWriteGLOpeningBalanceSubscriber } from '@/services/Contacts/Customers/Subscribers/CustomerGLEntriesSubscriber';
|
import { CustomerWriteGLOpeningBalanceSubscriber } from '@/services/Contacts/Customers/Subscribers/CustomerGLEntriesSubscriber';
|
||||||
import { VendorsWriteGLOpeningSubscriber } from '@/services/Contacts/Vendors/Subscribers/VendorGLEntriesSubscriber';
|
import { VendorsWriteGLOpeningSubscriber } from '@/services/Contacts/Vendors/Subscribers/VendorGLEntriesSubscriber';
|
||||||
import SaleEstimateAutoSerialSubscriber from '@/subscribers/SaleEstimate/AutoIncrementSerial';
|
import SaleEstimateAutoSerialSubscriber from '@/subscribers/SaleEstimate/AutoIncrementSerial';
|
||||||
@@ -31,12 +31,11 @@ import OrgBuildSmsNotificationSubscriber from '@/subscribers/Organization/BuildS
|
|||||||
import PurgeUserAbilityCache from '@/services/Users/PurgeUserAbilityCache';
|
import PurgeUserAbilityCache from '@/services/Users/PurgeUserAbilityCache';
|
||||||
import ResetLoginThrottleSubscriber from '@/subscribers/Authentication/ResetLoginThrottle';
|
import ResetLoginThrottleSubscriber from '@/subscribers/Authentication/ResetLoginThrottle';
|
||||||
import AuthenticationSubscriber from '@/subscribers/Authentication/SendResetPasswordMail';
|
import AuthenticationSubscriber from '@/subscribers/Authentication/SendResetPasswordMail';
|
||||||
import AuthSendWelcomeMailSubscriber from '@/subscribers/Authentication/SendWelcomeMail';
|
|
||||||
import PurgeAuthorizedUserOnceRoleMutate from '@/services/Roles/PurgeAuthorizedUser';
|
import PurgeAuthorizedUserOnceRoleMutate from '@/services/Roles/PurgeAuthorizedUser';
|
||||||
import SendSmsNotificationToCustomer from '@/subscribers/SaleInvoices/SendSmsNotificationToCustomer';
|
import SendSmsNotificationToCustomer from '@/subscribers/SaleInvoices/SendSmsNotificationToCustomer';
|
||||||
import SendSmsNotificationSaleReceipt from '@/subscribers/SaleReceipt/SendSmsNotificationToCustomer';
|
import SendSmsNotificationSaleReceipt from '@/subscribers/SaleReceipt/SendSmsNotificationToCustomer';
|
||||||
import SendSmsNotificationPaymentReceive from '@/subscribers/PaymentReceive/SendSmsNotificationToCustomer';
|
import SendSmsNotificationPaymentReceive from '@/subscribers/PaymentReceive/SendSmsNotificationToCustomer';
|
||||||
import SaleInvoiceWriteoffSubscriber from '@/services/Sales/SaleInvoiceWriteoffSubscriber';
|
import SaleInvoiceWriteoffSubscriber from '@/services/Sales/Invoices/SaleInvoiceWriteoffSubscriber';
|
||||||
import LandedCostSyncCostTransactionsSubscriber from '@/services/Purchases/LandedCost/LandedCostSyncCostTransactionsSubscriber';
|
import LandedCostSyncCostTransactionsSubscriber from '@/services/Purchases/LandedCost/LandedCostSyncCostTransactionsSubscriber';
|
||||||
import LandedCostInventoryTransactionsSubscriber from '@/services/Purchases/LandedCost/LandedCostInventoryTransactionsSubscriber';
|
import LandedCostInventoryTransactionsSubscriber from '@/services/Purchases/LandedCost/LandedCostInventoryTransactionsSubscriber';
|
||||||
import CreditNoteGLEntriesSubscriber from '@/services/CreditNotes/CreditNoteGLEntriesSubscriber';
|
import CreditNoteGLEntriesSubscriber from '@/services/CreditNotes/CreditNoteGLEntriesSubscriber';
|
||||||
@@ -67,7 +66,6 @@ import { ActivateWarehousesSubscriber } from '@/services/Warehouses/ActivateWare
|
|||||||
import { ManualJournalWriteGLSubscriber } from '@/services/ManualJournals/ManualJournalGLEntriesSubscriber';
|
import { ManualJournalWriteGLSubscriber } from '@/services/ManualJournals/ManualJournalGLEntriesSubscriber';
|
||||||
import { BillGLEntriesSubscriber } from '@/services/Purchases/Bills/BillGLEntriesSubscriber';
|
import { BillGLEntriesSubscriber } from '@/services/Purchases/Bills/BillGLEntriesSubscriber';
|
||||||
import { PaymentWriteGLEntriesSubscriber } from '@/services/Purchases/BillPayments/BillPaymentGLEntriesSubscriber';
|
import { PaymentWriteGLEntriesSubscriber } from '@/services/Purchases/BillPayments/BillPaymentGLEntriesSubscriber';
|
||||||
|
|
||||||
import BranchesIntegrationsSubscribers from '@/services/Branches/EventsProvider';
|
import BranchesIntegrationsSubscribers from '@/services/Branches/EventsProvider';
|
||||||
import WarehousesIntegrationsSubscribers from '@/services/Warehouses/EventsProvider';
|
import WarehousesIntegrationsSubscribers from '@/services/Warehouses/EventsProvider';
|
||||||
import { WarehouseTransferAutoIncrementSubscriber } from '@/services/Warehouses/WarehousesTransfers/WarehouseTransferAutoIncrementSubscriber';
|
import { WarehouseTransferAutoIncrementSubscriber } from '@/services/Warehouses/WarehousesTransfers/WarehouseTransferAutoIncrementSubscriber';
|
||||||
@@ -120,7 +118,6 @@ export const susbcribers = () => {
|
|||||||
PurgeUserAbilityCache,
|
PurgeUserAbilityCache,
|
||||||
ResetLoginThrottleSubscriber,
|
ResetLoginThrottleSubscriber,
|
||||||
AuthenticationSubscriber,
|
AuthenticationSubscriber,
|
||||||
AuthSendWelcomeMailSubscriber,
|
|
||||||
PurgeAuthorizedUserOnceRoleMutate,
|
PurgeAuthorizedUserOnceRoleMutate,
|
||||||
SendSmsNotificationToCustomer,
|
SendSmsNotificationToCustomer,
|
||||||
SendSmsNotificationSaleReceipt,
|
SendSmsNotificationSaleReceipt,
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import Agenda from 'agenda';
|
import Agenda from 'agenda';
|
||||||
import WelcomeEmailJob from 'jobs/welcomeEmail';
|
|
||||||
import WelcomeSMSJob from 'jobs/WelcomeSMS';
|
|
||||||
import ResetPasswordMailJob from 'jobs/ResetPasswordMail';
|
import ResetPasswordMailJob from 'jobs/ResetPasswordMail';
|
||||||
import ComputeItemCost from 'jobs/ComputeItemCost';
|
import ComputeItemCost from 'jobs/ComputeItemCost';
|
||||||
import RewriteInvoicesJournalEntries from 'jobs/writeInvoicesJEntries';
|
import RewriteInvoicesJournalEntries from 'jobs/WriteInvoicesJEntries';
|
||||||
import UserInviteMailJob from 'jobs/UserInviteMail';
|
import UserInviteMailJob from 'jobs/UserInviteMail';
|
||||||
import OrganizationSetupJob from 'jobs/OrganizationSetup';
|
import OrganizationSetupJob from 'jobs/OrganizationSetup';
|
||||||
import OrganizationUpgrade from 'jobs/OrganizationUpgrade';
|
import OrganizationUpgrade from 'jobs/OrganizationUpgrade';
|
||||||
import SmsNotification from 'jobs/SmsNotification';
|
|
||||||
|
|
||||||
export default ({ agenda }: { agenda: Agenda }) => {
|
export default ({ agenda }: { agenda: Agenda }) => {
|
||||||
new WelcomeEmailJob(agenda);
|
|
||||||
new ResetPasswordMailJob(agenda);
|
new ResetPasswordMailJob(agenda);
|
||||||
new WelcomeSMSJob(agenda);
|
|
||||||
new UserInviteMailJob(agenda);
|
new UserInviteMailJob(agenda);
|
||||||
new ComputeItemCost(agenda);
|
new ComputeItemCost(agenda);
|
||||||
new RewriteInvoicesJournalEntries(agenda);
|
new RewriteInvoicesJournalEntries(agenda);
|
||||||
new OrganizationSetupJob(agenda);
|
new OrganizationSetupJob(agenda);
|
||||||
new OrganizationUpgrade(agenda);
|
new OrganizationUpgrade(agenda);
|
||||||
new SmsNotification(agenda);
|
|
||||||
|
|
||||||
agenda.start();
|
agenda.start();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import TenantModel from 'models/TenantModel';
|
|||||||
import BillSettings from './Bill.Settings';
|
import BillSettings from './Bill.Settings';
|
||||||
import ModelSetting from './ModelSetting';
|
import ModelSetting from './ModelSetting';
|
||||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||||
import { DEFAULT_VIEWS } from '@/services/Purchases/constants';
|
import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants';
|
||||||
import ModelSearchable from './ModelSearchable';
|
import ModelSearchable from './ModelSearchable';
|
||||||
|
|
||||||
export default class Bill extends mixin(TenantModel, [
|
export default class Bill extends mixin(TenantModel, [
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import TenantModel from 'models/TenantModel';
|
|||||||
import ModelSetting from './ModelSetting';
|
import ModelSetting from './ModelSetting';
|
||||||
import SaleInvoiceMeta from './SaleInvoice.Settings';
|
import SaleInvoiceMeta from './SaleInvoice.Settings';
|
||||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||||
import { DEFAULT_VIEWS } from '@/services/Sales/constants';
|
import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants';
|
||||||
import ModelSearchable from './ModelSearchable';
|
import ModelSearchable from './ModelSearchable';
|
||||||
|
|
||||||
export default class SaleInvoice extends mixin(TenantModel, [
|
export default class SaleInvoice extends mixin(TenantModel, [
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import async from 'async';
|
import async from 'async';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ILedger, ISaleContactsBalanceQueuePayload } from '@/interfaces';
|
import {
|
||||||
|
ILedger,
|
||||||
|
ILedgerEntry,
|
||||||
|
ISaleContactsBalanceQueuePayload,
|
||||||
|
} from '@/interfaces';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { TenantMetadata } from '@/system/models';
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class LedgerContactsBalanceStorage {
|
export class LedgerContactsBalanceStorage {
|
||||||
@@ -49,6 +54,29 @@ export class LedgerContactsBalanceStorage {
|
|||||||
await this.saveContactBalance(tenantId, ledger, contactId, trx);
|
await this.saveContactBalance(tenantId, ledger, contactId, trx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters AP/AR ledger entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
* @returns {Promise<(entry: ILedgerEntry) => boolean>}
|
||||||
|
*/
|
||||||
|
private filterARAPLedgerEntris = async (
|
||||||
|
tenantId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<(entry: ILedgerEntry) => boolean> => {
|
||||||
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const ARAPAcounts = await Account.query(trx).whereIn('accountType', [
|
||||||
|
ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||||
|
ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||||
|
]);
|
||||||
|
const ARAPAcountsIds = ARAPAcounts.map((a) => a.id);
|
||||||
|
|
||||||
|
return (entry: ILedgerEntry) => {
|
||||||
|
return ARAPAcountsIds.indexOf(entry.accountId) !== -1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -63,16 +91,24 @@ export class LedgerContactsBalanceStorage {
|
|||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { Contact } = this.tenancy.models(tenantId);
|
const { Contact } = this.tenancy.models(tenantId);
|
||||||
const contact = await Contact.query().findById(contactId);
|
const contact = await Contact.query(trx).findById(contactId);
|
||||||
|
|
||||||
// Retrieves the given tenant metadata.
|
// Retrieves the given tenant metadata.
|
||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
// Detarmines whether the contact has foreign currency.
|
// Detarmines whether the contact has foreign currency.
|
||||||
const isForeignContact = contact.currencyCode !== tenantMeta.baseCurrency;
|
const isForeignContact = contact.currencyCode !== tenantMeta.baseCurrency;
|
||||||
|
|
||||||
// Filters the ledger base on the given contact id.
|
// Filters the ledger base on the given contact id.
|
||||||
const contactLedger = ledger.whereContactId(contactId);
|
const filterARAPLedgerEntris = await this.filterARAPLedgerEntris(
|
||||||
|
tenantId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
const contactLedger = ledger
|
||||||
|
// Filter entries only that have contact id.
|
||||||
|
.whereContactId(contactId)
|
||||||
|
// Filter entries on AR/AP accounts.
|
||||||
|
.filter(filterARAPLedgerEntris);
|
||||||
|
|
||||||
const closingBalance = isForeignContact
|
const closingBalance = isForeignContact
|
||||||
? contactLedger
|
? contactLedger
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export const transformLedgerEntryToTransaction = (
|
|||||||
transactionNumber: entry.transactionNumber,
|
transactionNumber: entry.transactionNumber,
|
||||||
referenceNumber: entry.referenceNumber,
|
referenceNumber: entry.referenceNumber,
|
||||||
|
|
||||||
|
note: entry.note,
|
||||||
|
|
||||||
index: entry.index,
|
index: entry.index,
|
||||||
indexGroup: entry.indexGroup,
|
indexGroup: entry.indexGroup,
|
||||||
|
|
||||||
|
|||||||
@@ -5,59 +5,24 @@ import Mail from '@/lib/Mail';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class AuthenticationMailMesssages {
|
export default class AuthenticationMailMesssages {
|
||||||
/**
|
|
||||||
* Sends welcome message.
|
|
||||||
* @param {ISystemUser} user - The system user.
|
|
||||||
* @param {string} organizationName -
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
async sendWelcomeMessage(
|
|
||||||
user: ISystemUser,
|
|
||||||
organizationId: string
|
|
||||||
): Promise<void> {
|
|
||||||
const root = __dirname + '/../../../views/images/bigcapital.png';
|
|
||||||
|
|
||||||
const mail = new Mail()
|
|
||||||
.setView('mail/Welcome.html')
|
|
||||||
.setSubject('Welcome to Bigcapital')
|
|
||||||
.setTo(user.email)
|
|
||||||
.setAttachments([
|
|
||||||
{
|
|
||||||
filename: 'bigcapital.png',
|
|
||||||
path: root,
|
|
||||||
cid: 'bigcapital_logo',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.setData({
|
|
||||||
firstName: user.firstName,
|
|
||||||
organizationId,
|
|
||||||
successPhoneNumber: config.customerSuccess.phoneNumber,
|
|
||||||
successEmail: config.customerSuccess.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
await mail.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends reset password message.
|
* Sends reset password message.
|
||||||
* @param {ISystemUser} user - The system user.
|
* @param {ISystemUser} user - The system user.
|
||||||
* @param {string} token - Reset password token.
|
* @param {string} token - Reset password token.
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async sendResetPasswordMessage(
|
public async sendResetPasswordMessage(
|
||||||
user: ISystemUser,
|
user: ISystemUser,
|
||||||
token: string
|
token: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const root = __dirname + '/../../../views/images/bigcapital.png';
|
await new Mail()
|
||||||
|
|
||||||
const mail = new Mail()
|
|
||||||
.setSubject('Bigcapital - Password Reset')
|
.setSubject('Bigcapital - Password Reset')
|
||||||
.setView('mail/ResetPassword.html')
|
.setView('mail/ResetPassword.html')
|
||||||
.setTo(user.email)
|
.setTo(user.email)
|
||||||
.setAttachments([
|
.setAttachments([
|
||||||
{
|
{
|
||||||
filename: 'bigcapital.png',
|
filename: 'bigcapital.png',
|
||||||
path: root,
|
path: `${global.__views_dir}/images/bigcapital.png`,
|
||||||
cid: 'bigcapital_logo',
|
cid: 'bigcapital_logo',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -65,9 +30,7 @@ export default class AuthenticationMailMesssages {
|
|||||||
resetPasswordUrl: `${config.baseURL}/auth/reset_password/${token}`,
|
resetPasswordUrl: `${config.baseURL}/auth/reset_password/${token}`,
|
||||||
first_name: user.firstName,
|
first_name: user.firstName,
|
||||||
last_name: user.lastName,
|
last_name: user.lastName,
|
||||||
contact_us_email: config.contactUsMail,
|
})
|
||||||
});
|
.send();
|
||||||
|
|
||||||
await mail.send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
|
||||||
import { ISystemUser, ITenant } from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export default class AuthenticationSMSMessages {
|
|
||||||
@Inject('SMSClient')
|
|
||||||
smsClient: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends welcome sms message.
|
|
||||||
* @param {ITenant} tenant
|
|
||||||
* @param {ISystemUser} user
|
|
||||||
*/
|
|
||||||
sendWelcomeMessage(tenant: ITenant, user: ISystemUser) {
|
|
||||||
const message: string = `Hi ${user.firstName}, Welcome to Bigcapital, You've joined the new workspace, if you need any help please don't hesitate to contact us.`;
|
|
||||||
|
|
||||||
return this.smsClient.sendMessage(user.phoneNumber, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import { IBranchDeletedPayload, IBranchDeletePayload } from '@/interfaces';
|
|||||||
import { CURDBranch } from './CRUDBranch';
|
import { CURDBranch } from './CRUDBranch';
|
||||||
import { BranchValidator } from './BranchValidate';
|
import { BranchValidator } from './BranchValidate';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class DeleteBranch extends CURDBranch {
|
export class DeleteBranch extends CURDBranch {
|
||||||
@Inject()
|
@Inject()
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export class ValidateBranchExistance {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate transaction branch id when the feature is active.
|
* Validate transaction branch id when the feature is active.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} branchId
|
* @param {number} branchId
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public validateTransactionBranchWhenActive = async (
|
public validateTransactionBranchWhenActive = async (
|
||||||
@@ -32,18 +32,16 @@ export class ValidateBranchExistance {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate transaction branch id existance.
|
* Validate transaction branch id existance.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} branchId
|
* @param {number} branchId
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public validateTransactionBranch = async (
|
public validateTransactionBranch = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
branchId: number | null
|
branchId: number | null
|
||||||
) => {
|
) => {
|
||||||
//
|
|
||||||
this.validateBranchIdExistance(branchId);
|
this.validateBranchIdExistance(branchId);
|
||||||
|
|
||||||
//
|
|
||||||
await this.validateBranchExistance(tenantId, branchId);
|
await this.validateBranchExistance(tenantId, branchId);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +60,10 @@ export class ValidateBranchExistance {
|
|||||||
* @param tenantId
|
* @param tenantId
|
||||||
* @param branchId
|
* @param branchId
|
||||||
*/
|
*/
|
||||||
public validateBranchExistance = async (tenantId: number, branchId: number) => {
|
public validateBranchExistance = async (
|
||||||
|
tenantId: number,
|
||||||
|
branchId: number
|
||||||
|
) => {
|
||||||
const { Branch } = this.tenancy.models(tenantId);
|
const { Branch } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const branch = await Branch.query().findById(branchId);
|
const branch = await Branch.query().findById(branchId);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the common entry of cashflow transaction.
|
* Retrieves the common entry of cashflow transaction.
|
||||||
* @param {ICashflowTransaction} cashflowTransaction
|
* @param {ICashflowTransaction} cashflowTransaction
|
||||||
* @returns {}
|
* @returns {Partial<ILedgerEntry>}
|
||||||
*/
|
*/
|
||||||
private getCommonEntry = (cashflowTransaction: ICashflowTransaction) => {
|
private getCommonEntry = (cashflowTransaction: ICashflowTransaction) => {
|
||||||
const { entries, ...transaction } = cashflowTransaction;
|
const { entries, ...transaction } = cashflowTransaction;
|
||||||
@@ -41,7 +41,9 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
),
|
),
|
||||||
transactionId: transaction.id,
|
transactionId: transaction.id,
|
||||||
transactionNumber: transaction.transactionNumber,
|
transactionNumber: transaction.transactionNumber,
|
||||||
referenceNo: transaction.referenceNo,
|
referenceNumber: transaction.referenceNo,
|
||||||
|
|
||||||
|
note: transaction.description,
|
||||||
|
|
||||||
branchId: cashflowTransaction.branchId,
|
branchId: cashflowTransaction.branchId,
|
||||||
userId: cashflowTransaction.userId,
|
userId: cashflowTransaction.userId,
|
||||||
@@ -76,9 +78,9 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the cashflow credit GL entry.
|
* Retrieves the cashflow credit GL entry.
|
||||||
* @param {ICashflowTransaction} cashflowTransaction
|
* @param {ICashflowTransaction} cashflowTransaction
|
||||||
* @param {ICashflowTransactionLine} entry
|
* @param {ICashflowTransactionLine} entry
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @returns {ILedgerEntry}
|
* @returns {ILedgerEntry}
|
||||||
*/
|
*/
|
||||||
private getCashflowCreditGLEntry = (
|
private getCashflowCreditGLEntry = (
|
||||||
@@ -102,10 +104,10 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the cashflow transaction GL entry.
|
* Retrieves the cashflow transaction GL entry.
|
||||||
* @param {ICashflowTransaction} cashflowTransaction
|
* @param {ICashflowTransaction} cashflowTransaction
|
||||||
* @param {ICashflowTransactionLine} entry
|
* @param {ICashflowTransactionLine} entry
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @returns
|
* @returns {ILedgerEntry[]}
|
||||||
*/
|
*/
|
||||||
private getJournalEntries = (
|
private getJournalEntries = (
|
||||||
cashflowTransaction: ICashflowTransaction
|
cashflowTransaction: ICashflowTransaction
|
||||||
@@ -118,7 +120,7 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the cashflow GL ledger.
|
* Retrieves the cashflow GL ledger.
|
||||||
* @param {ICashflowTransaction} cashflowTransaction
|
* @param {ICashflowTransaction} cashflowTransaction
|
||||||
* @returns {Ledger}
|
* @returns {Ledger}
|
||||||
*/
|
*/
|
||||||
private getCashflowLedger = (cashflowTransaction: ICashflowTransaction) => {
|
private getCashflowLedger = (cashflowTransaction: ICashflowTransaction) => {
|
||||||
@@ -130,6 +132,7 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
* Write the journal entries of the given cashflow transaction.
|
* Write the journal entries of the given cashflow transaction.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {ICashflowTransaction} cashflowTransaction
|
* @param {ICashflowTransaction} cashflowTransaction
|
||||||
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public writeJournalEntries = async (
|
public writeJournalEntries = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -153,6 +156,7 @@ export default class CashflowTransactionJournalEntries {
|
|||||||
* Delete the journal entries.
|
* Delete the journal entries.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} cashflowTransactionId - Cashflow transaction id.
|
* @param {number} cashflowTransactionId - Cashflow transaction id.
|
||||||
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public revertJournalEntries = async (
|
public revertJournalEntries = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class CreateCustomer {
|
|||||||
} as ICustomerEventCreatingPayload);
|
} as ICustomerEventCreatingPayload);
|
||||||
|
|
||||||
// Creates a new contact as customer.
|
// Creates a new contact as customer.
|
||||||
const customer = await Contact.query().insertAndFetch({
|
const customer = await Contact.query(trx).insertAndFetch({
|
||||||
...customerObj,
|
...customerObj,
|
||||||
});
|
});
|
||||||
// Triggers `onCustomerCreated` event.
|
// Triggers `onCustomerCreated` event.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import Knex from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { sumBy } from 'lodash';
|
import { sumBy } from 'lodash';
|
||||||
import {
|
import {
|
||||||
ICreditNote,
|
ICreditNote,
|
||||||
@@ -8,27 +8,31 @@ import {
|
|||||||
ISaleInvoice,
|
ISaleInvoice,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import events from '@/subscribers/events';
|
import { PaymentReceiveValidators } from '../Sales/PaymentReceives/PaymentReceiveValidators';
|
||||||
import BaseCreditNotes from './CreditNotes';
|
import BaseCreditNotes from './CreditNotes';
|
||||||
import {
|
import {
|
||||||
IApplyCreditToInvoicesDTO,
|
IApplyCreditToInvoicesDTO,
|
||||||
IApplyCreditToInvoicesCreatedPayload,
|
IApplyCreditToInvoicesCreatedPayload,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||||
@Inject('PaymentReceives')
|
@Inject()
|
||||||
paymentReceive: PaymentReceiveService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
uow: UnitOfWork;
|
private paymentReceiveValidators: PaymentReceiveValidators;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
eventPublisher: EventPublisher;
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply credit note to the given invoices.
|
* Apply credit note to the given invoices.
|
||||||
@@ -50,7 +54,7 @@ export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
|||||||
);
|
);
|
||||||
// Retrieve the applied invoices that associated to the credit note customer.
|
// Retrieve the applied invoices that associated to the credit note customer.
|
||||||
const appliedInvoicesEntries =
|
const appliedInvoicesEntries =
|
||||||
await this.paymentReceive.validateInvoicesIDsExistance(
|
await this.paymentReceiveValidators.validateInvoicesIDsExistance(
|
||||||
tenantId,
|
tenantId,
|
||||||
creditNote.customerId,
|
creditNote.customerId,
|
||||||
applyCreditToInvoicesDTO.entries
|
applyCreditToInvoicesDTO.entries
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import Knex from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { IApplyCreditToInvoicesDeletedPayload } from '@/interfaces';
|
import { IApplyCreditToInvoicesDeletedPayload } from '@/interfaces';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import BaseCreditNotes from './CreditNotes';
|
import BaseCreditNotes from './CreditNotes';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class DeletreCreditNoteApplyToInvoices extends BaseCreditNotes {
|
export default class DeletreCreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||||
@Inject('PaymentReceives')
|
@Inject()
|
||||||
paymentReceive: PaymentReceiveService;
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
uow: UnitOfWork;
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
eventPublisher: EventPublisher;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply credit note to the given invoices.
|
* Apply credit note to the given invoices.
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import events from '@/subscribers/events';
|
|||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import { CommandExpenseValidator } from './CommandExpenseValidator';
|
import { CommandExpenseValidator } from './CommandExpenseValidator';
|
||||||
import { ExpenseCategory } from 'models';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -37,7 +36,7 @@ export class DeleteExpense {
|
|||||||
expenseId: number,
|
expenseId: number,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { Expense } = this.tenancy.models(tenantId);
|
const { Expense, ExpenseCategory } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Retrieves the expense transaction with associated entries or
|
// Retrieves the expense transaction with associated entries or
|
||||||
// throw not found error.
|
// throw not found error.
|
||||||
@@ -60,7 +59,7 @@ export class DeleteExpense {
|
|||||||
} as IExpenseDeletingPayload);
|
} as IExpenseDeletingPayload);
|
||||||
|
|
||||||
// Deletes expense associated entries.
|
// Deletes expense associated entries.
|
||||||
await ExpenseCategory.query(trx).findById(expenseId).delete();
|
await ExpenseCategory.query(trx).where('expenseId', expenseId).delete();
|
||||||
|
|
||||||
// Deletes expense transactions.
|
// Deletes expense transactions.
|
||||||
await Expense.query(trx).findById(expenseId).delete();
|
await Expense.query(trx).findById(expenseId).delete();
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class ExpenseGLEntries {
|
|||||||
...commonEntry,
|
...commonEntry,
|
||||||
credit: expense.localAmount,
|
credit: expense.localAmount,
|
||||||
accountId: expense.paymentAccountId,
|
accountId: expense.paymentAccountId,
|
||||||
accountNormal: AccountNormal.CREDIT,
|
accountNormal: AccountNormal.DEBIT,
|
||||||
index: 1,
|
index: 1,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class SyncSystemSendInvite {
|
export default class SyncSystemSendInvite {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
eventPublisher: EventPublisher;
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches events with handlers.
|
* Attaches events with handlers.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class ItemInvoicesTransactionsTransformer extends Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Formatted invoice date.
|
||||||
* @param item
|
* @param item
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
@@ -36,16 +36,17 @@ export class ItemInvoicesTransactionsTransformer extends Transformer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Formatted item quantity.
|
||||||
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public formattedQuantity = (entry): string => {
|
public formattedQuantity = (entry): string => {
|
||||||
return entry.quantity;
|
return entry.quantity;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Formatted date.
|
||||||
* @param entry
|
* @param entry
|
||||||
* @returns
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public formattedRate = (entry): string => {
|
public formattedRate = (entry): string => {
|
||||||
return formatNumber(entry.rate, {
|
return formatNumber(entry.rate, {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IItemEntry, IItemEntryDTO, IItem } from '@/interfaces';
|
import { IItemEntry, IItemEntryDTO, IItem } from '@/interfaces';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { ItemEntry } from '@/models';
|
||||||
import { entriesAmountDiff } from 'utils';
|
import { entriesAmountDiff } from 'utils';
|
||||||
import { ItemEntry } from 'models';
|
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||||
@@ -16,7 +16,7 @@ const ERRORS = {
|
|||||||
@Service()
|
@Service()
|
||||||
export default class ItemsEntriesService {
|
export default class ItemsEntriesService {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the inventory items entries of the reference id and type.
|
* Retrieve the inventory items entries of the reference id and type.
|
||||||
@@ -235,7 +235,7 @@ export default class ItemsEntriesService {
|
|||||||
/**
|
/**
|
||||||
* Sets the cost/sell accounts to the invoice entries.
|
* Sets the cost/sell accounts to the invoice entries.
|
||||||
*/
|
*/
|
||||||
setItemsEntriesDefaultAccounts(tenantId: number) {
|
public setItemsEntriesDefaultAccounts(tenantId: number) {
|
||||||
return async (entries: IItemEntry[]) => {
|
return async (entries: IItemEntry[]) => {
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
const { Item } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -258,10 +258,10 @@ export default class ItemsEntriesService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the total items entries.
|
* Retrieve the total items entries.
|
||||||
* @param entries
|
* @param entries
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getTotalItemsEntries(entries: ItemEntry[]): number {
|
public getTotalItemsEntries(entries: ItemEntry[]): number {
|
||||||
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IBillPaymentEntryDTO } from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { entriesAmountDiff } from '@/utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillPaymentBillSync {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves bills payment amount changes different.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IBillPaymentEntryDTO[]} paymentMadeEntries -
|
||||||
|
* @param {IBillPaymentEntryDTO[]} oldPaymentMadeEntries -
|
||||||
|
*/
|
||||||
|
public async saveChangeBillsPaymentAmount(
|
||||||
|
tenantId: number,
|
||||||
|
paymentMadeEntries: IBillPaymentEntryDTO[],
|
||||||
|
oldPaymentMadeEntries?: IBillPaymentEntryDTO[],
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
const opers: Promise<void>[] = [];
|
||||||
|
|
||||||
|
const diffEntries = entriesAmountDiff(
|
||||||
|
paymentMadeEntries,
|
||||||
|
oldPaymentMadeEntries,
|
||||||
|
'paymentAmount',
|
||||||
|
'billId'
|
||||||
|
);
|
||||||
|
diffEntries.forEach(
|
||||||
|
(diffEntry: { paymentAmount: number; billId: number }) => {
|
||||||
|
if (diffEntry.paymentAmount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oper = Bill.changePaymentAmount(
|
||||||
|
diffEntry.billId,
|
||||||
|
diffEntry.paymentAmount,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
opers.push(oper);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await Promise.all(opers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ export class BillPaymentGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment common entry.
|
* Retrieves the payment common entry.
|
||||||
* @param {IBillPayment} billPayment
|
* @param {IBillPayment} billPayment
|
||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
private getPaymentCommonEntry = (billPayment: IBillPayment) => {
|
private getPaymentCommonEntry = (billPayment: IBillPayment) => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class BillPaymentTransactionTransformer extends Transformer {
|
|||||||
/**
|
/**
|
||||||
* Retrieve formatted bill payment date.
|
* Retrieve formatted bill payment date.
|
||||||
* @param entry
|
* @param entry
|
||||||
* @returns
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedPaymentDate = (entry): string => {
|
protected formattedPaymentDate = (entry): string => {
|
||||||
return this.formatDate(entry.payment.paymentDate);
|
return this.formatDate(entry.payment.paymentDate);
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { sumBy, difference } from 'lodash';
|
||||||
|
import {
|
||||||
|
IBill,
|
||||||
|
IBillPaymentDTO,
|
||||||
|
IBillPaymentEntryDTO,
|
||||||
|
IBillPayment,
|
||||||
|
IBillPaymentEntry,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||||
|
import { BillPayment } from '@/models';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillPaymentValidators {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the payment existance.
|
||||||
|
* @param {BillPayment | undefined | null} payment
|
||||||
|
* @throws {ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND)}
|
||||||
|
*/
|
||||||
|
public async validateBillPaymentExistance(
|
||||||
|
payment: BillPayment | undefined | null
|
||||||
|
) {
|
||||||
|
if (!payment) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the bill payment existance.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
public async getPaymentMadeOrThrowError(
|
||||||
|
tenantid: number,
|
||||||
|
paymentMadeId: number
|
||||||
|
) {
|
||||||
|
const { BillPayment } = this.tenancy.models(tenantid);
|
||||||
|
const billPayment = await BillPayment.query()
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.findById(paymentMadeId);
|
||||||
|
|
||||||
|
if (!billPayment) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return billPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the payment account.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} paymentAccountId
|
||||||
|
* @return {Promise<IAccountType>}
|
||||||
|
*/
|
||||||
|
public async getPaymentAccountOrThrowError(
|
||||||
|
tenantId: number,
|
||||||
|
paymentAccountId: number
|
||||||
|
) {
|
||||||
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
const paymentAccount = await accountRepository.findOneById(
|
||||||
|
paymentAccountId
|
||||||
|
);
|
||||||
|
if (!paymentAccount) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
// Validate the payment account type.
|
||||||
|
if (
|
||||||
|
!paymentAccount.isAccountType([
|
||||||
|
ACCOUNT_TYPE.BANK,
|
||||||
|
ACCOUNT_TYPE.CASH,
|
||||||
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_CURRENT_ASSET_TYPE);
|
||||||
|
}
|
||||||
|
return paymentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the payment number uniqness.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {string} paymentMadeNumber -
|
||||||
|
* @return {Promise<IBillPayment>}
|
||||||
|
*/
|
||||||
|
public async validatePaymentNumber(
|
||||||
|
tenantId: number,
|
||||||
|
paymentMadeNumber: string,
|
||||||
|
notPaymentMadeId?: number
|
||||||
|
) {
|
||||||
|
const { BillPayment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const foundBillPayment = await BillPayment.query().onBuild(
|
||||||
|
(builder: any) => {
|
||||||
|
builder.findOne('payment_number', paymentMadeNumber);
|
||||||
|
|
||||||
|
if (notPaymentMadeId) {
|
||||||
|
builder.whereNot('id', notPaymentMadeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (foundBillPayment) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_PAYMENT_NUMBER_NOT_UNQIUE);
|
||||||
|
}
|
||||||
|
return foundBillPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate whether the entries bills ids exist on the storage.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async validateBillsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentEntries: { billId: number }[],
|
||||||
|
vendorId: number
|
||||||
|
) {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
const entriesBillsIds = billPaymentEntries.map((e: any) => e.billId);
|
||||||
|
|
||||||
|
const storedBills = await Bill.query()
|
||||||
|
.whereIn('id', entriesBillsIds)
|
||||||
|
.where('vendor_id', vendorId);
|
||||||
|
|
||||||
|
const storedBillsIds = storedBills.map((t: IBill) => t.id);
|
||||||
|
const notFoundBillsIds = difference(entriesBillsIds, storedBillsIds);
|
||||||
|
|
||||||
|
if (notFoundBillsIds.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_ENTRIES_IDS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
// Validate the not opened bills.
|
||||||
|
const notOpenedBills = storedBills.filter((bill) => !bill.openedAt);
|
||||||
|
|
||||||
|
if (notOpenedBills.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILLS_NOT_OPENED_YET, null, {
|
||||||
|
notOpenedBills,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return storedBills;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate wether the payment amount bigger than the payable amount.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
public async validateBillsDueAmount(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentEntries: IBillPaymentEntryDTO[],
|
||||||
|
oldPaymentEntries: IBillPaymentEntry[] = []
|
||||||
|
) {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
const billsIds = billPaymentEntries.map(
|
||||||
|
(entry: IBillPaymentEntryDTO) => entry.billId
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedBills = await Bill.query().whereIn('id', billsIds);
|
||||||
|
const storedBillsMap = new Map(
|
||||||
|
storedBills.map((bill) => {
|
||||||
|
const oldEntries = oldPaymentEntries.filter(
|
||||||
|
(entry) => entry.billId === bill.id
|
||||||
|
);
|
||||||
|
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
bill.id,
|
||||||
|
{ ...bill, dueAmount: bill.dueAmount + oldPaymentAmount },
|
||||||
|
];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
interface invalidPaymentAmountError {
|
||||||
|
index: number;
|
||||||
|
due_amount: number;
|
||||||
|
}
|
||||||
|
const hasWrongPaymentAmount: invalidPaymentAmountError[] = [];
|
||||||
|
|
||||||
|
billPaymentEntries.forEach((entry: IBillPaymentEntryDTO, index: number) => {
|
||||||
|
const entryBill = storedBillsMap.get(entry.billId);
|
||||||
|
const { dueAmount } = entryBill;
|
||||||
|
|
||||||
|
if (dueAmount < entry.paymentAmount) {
|
||||||
|
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (hasWrongPaymentAmount.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.INVALID_BILL_PAYMENT_AMOUNT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the payment receive entries IDs existance.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @return {Response}
|
||||||
|
*/
|
||||||
|
public async validateEntriesIdsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentId: number,
|
||||||
|
billPaymentEntries: IBillPaymentEntry[]
|
||||||
|
) {
|
||||||
|
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const entriesIds = billPaymentEntries
|
||||||
|
.filter((entry: any) => entry.id)
|
||||||
|
.map((entry: any) => entry.id);
|
||||||
|
|
||||||
|
const storedEntries = await BillPaymentEntry.query().where(
|
||||||
|
'bill_payment_id',
|
||||||
|
billPaymentId
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
|
||||||
|
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
||||||
|
|
||||||
|
if (notFoundEntriesIds.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_PAYMENT_ENTRIES_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * Validate the payment vendor whether modified.
|
||||||
|
* @param {string} billPaymentNo
|
||||||
|
*/
|
||||||
|
public validateVendorNotModified(
|
||||||
|
billPaymentDTO: IBillPaymentDTO,
|
||||||
|
oldBillPayment: IBillPayment
|
||||||
|
) {
|
||||||
|
if (billPaymentDTO.vendorId !== oldBillPayment.vendorId) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_NUMBER_SHOULD_NOT_MODIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the payment account currency code. The deposit account curreny
|
||||||
|
* should be equals the customer currency code or the base currency.
|
||||||
|
* @param {string} paymentAccountCurrency
|
||||||
|
* @param {string} customerCurrency
|
||||||
|
* @param {string} baseCurrency
|
||||||
|
* @throws {ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID)}
|
||||||
|
*/
|
||||||
|
public validateWithdrawalAccountCurrency = (
|
||||||
|
paymentAccountCurrency: string,
|
||||||
|
customerCurrency: string,
|
||||||
|
baseCurrency: string
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
paymentAccountCurrency !== customerCurrency &&
|
||||||
|
paymentAccountCurrency !== baseCurrency
|
||||||
|
) {
|
||||||
|
throw new ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given vendor has no associated payments.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} vendorId
|
||||||
|
*/
|
||||||
|
public async validateVendorHasNoPayments(tenantId: number, vendorId: number) {
|
||||||
|
const { BillPayment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const payments = await BillPayment.query().where('vendor_id', vendorId);
|
||||||
|
|
||||||
|
if (payments.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.VENDOR_HAS_PAYMENTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,713 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { sumBy, difference } from 'lodash';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import {
|
|
||||||
IBill,
|
|
||||||
IBillPaymentDTO,
|
|
||||||
IBillPaymentEntryDTO,
|
|
||||||
IBillPayment,
|
|
||||||
IBillPaymentsFilter,
|
|
||||||
IPaginationMeta,
|
|
||||||
IFilterMeta,
|
|
||||||
IBillPaymentEntry,
|
|
||||||
IBillPaymentEventCreatedPayload,
|
|
||||||
IBillPaymentEventEditedPayload,
|
|
||||||
IBillPaymentEventDeletedPayload,
|
|
||||||
IBillPaymentCreatingPayload,
|
|
||||||
IBillPaymentEditingPayload,
|
|
||||||
IBillPaymentDeletingPayload,
|
|
||||||
IVendor,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
|
||||||
import { entriesAmountDiff, formatDateFields } from 'utils';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
|
||||||
import { BillPaymentTransformer } from './BillPaymentTransformer';
|
|
||||||
import { ERRORS } from './constants';
|
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
|
||||||
import { TenantMetadata } from '@/system/models';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bill payments service.
|
|
||||||
* @service
|
|
||||||
*/
|
|
||||||
@Service('BillPayments')
|
|
||||||
export default class BillPaymentsService implements IBillPaymentsService {
|
|
||||||
@Inject()
|
|
||||||
tenancy: TenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
journalService: JournalPosterService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
dynamicListService: DynamicListingService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private transformer: TransformerInjectable;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
uow: UnitOfWork;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the bill payment existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
private async getPaymentMadeOrThrowError(
|
|
||||||
tenantid: number,
|
|
||||||
paymentMadeId: number
|
|
||||||
) {
|
|
||||||
const { BillPayment } = this.tenancy.models(tenantid);
|
|
||||||
const billPayment = await BillPayment.query()
|
|
||||||
.withGraphFetched('entries')
|
|
||||||
.findById(paymentMadeId);
|
|
||||||
|
|
||||||
if (!billPayment) {
|
|
||||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return billPayment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the payment account.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {number} paymentAccountId
|
|
||||||
* @return {Promise<IAccountType>}
|
|
||||||
*/
|
|
||||||
private async getPaymentAccountOrThrowError(
|
|
||||||
tenantId: number,
|
|
||||||
paymentAccountId: number
|
|
||||||
) {
|
|
||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
const paymentAccount = await accountRepository.findOneById(
|
|
||||||
paymentAccountId
|
|
||||||
);
|
|
||||||
if (!paymentAccount) {
|
|
||||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
// Validate the payment account type.
|
|
||||||
if (
|
|
||||||
!paymentAccount.isAccountType([
|
|
||||||
ACCOUNT_TYPE.BANK,
|
|
||||||
ACCOUNT_TYPE.CASH,
|
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
|
||||||
])
|
|
||||||
) {
|
|
||||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_CURRENT_ASSET_TYPE);
|
|
||||||
}
|
|
||||||
return paymentAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the payment number uniqness.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {string} paymentMadeNumber -
|
|
||||||
* @return {Promise<IBillPayment>}
|
|
||||||
*/
|
|
||||||
private async validatePaymentNumber(
|
|
||||||
tenantId: number,
|
|
||||||
paymentMadeNumber: string,
|
|
||||||
notPaymentMadeId?: number
|
|
||||||
) {
|
|
||||||
const { BillPayment } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const foundBillPayment = await BillPayment.query().onBuild(
|
|
||||||
(builder: any) => {
|
|
||||||
builder.findOne('payment_number', paymentMadeNumber);
|
|
||||||
|
|
||||||
if (notPaymentMadeId) {
|
|
||||||
builder.whereNot('id', notPaymentMadeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (foundBillPayment) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_PAYMENT_NUMBER_NOT_UNQIUE);
|
|
||||||
}
|
|
||||||
return foundBillPayment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether the entries bills ids exist on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
public async validateBillsExistance(
|
|
||||||
tenantId: number,
|
|
||||||
billPaymentEntries: { billId: number }[],
|
|
||||||
vendorId: number
|
|
||||||
) {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
const entriesBillsIds = billPaymentEntries.map((e: any) => e.billId);
|
|
||||||
|
|
||||||
const storedBills = await Bill.query()
|
|
||||||
.whereIn('id', entriesBillsIds)
|
|
||||||
.where('vendor_id', vendorId);
|
|
||||||
|
|
||||||
const storedBillsIds = storedBills.map((t: IBill) => t.id);
|
|
||||||
const notFoundBillsIds = difference(entriesBillsIds, storedBillsIds);
|
|
||||||
|
|
||||||
if (notFoundBillsIds.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_ENTRIES_IDS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
// Validate the not opened bills.
|
|
||||||
const notOpenedBills = storedBills.filter((bill) => !bill.openedAt);
|
|
||||||
|
|
||||||
if (notOpenedBills.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILLS_NOT_OPENED_YET, null, {
|
|
||||||
notOpenedBills,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return storedBills;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate wether the payment amount bigger than the payable amount.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
private async validateBillsDueAmount(
|
|
||||||
tenantId: number,
|
|
||||||
billPaymentEntries: IBillPaymentEntryDTO[],
|
|
||||||
oldPaymentEntries: IBillPaymentEntry[] = []
|
|
||||||
) {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
const billsIds = billPaymentEntries.map(
|
|
||||||
(entry: IBillPaymentEntryDTO) => entry.billId
|
|
||||||
);
|
|
||||||
|
|
||||||
const storedBills = await Bill.query().whereIn('id', billsIds);
|
|
||||||
const storedBillsMap = new Map(
|
|
||||||
storedBills.map((bill) => {
|
|
||||||
const oldEntries = oldPaymentEntries.filter(
|
|
||||||
(entry) => entry.billId === bill.id
|
|
||||||
);
|
|
||||||
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0;
|
|
||||||
|
|
||||||
return [
|
|
||||||
bill.id,
|
|
||||||
{ ...bill, dueAmount: bill.dueAmount + oldPaymentAmount },
|
|
||||||
];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
interface invalidPaymentAmountError {
|
|
||||||
index: number;
|
|
||||||
due_amount: number;
|
|
||||||
}
|
|
||||||
const hasWrongPaymentAmount: invalidPaymentAmountError[] = [];
|
|
||||||
|
|
||||||
billPaymentEntries.forEach((entry: IBillPaymentEntryDTO, index: number) => {
|
|
||||||
const entryBill = storedBillsMap.get(entry.billId);
|
|
||||||
const { dueAmount } = entryBill;
|
|
||||||
|
|
||||||
if (dueAmount < entry.paymentAmount) {
|
|
||||||
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (hasWrongPaymentAmount.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.INVALID_BILL_PAYMENT_AMOUNT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the payment receive entries IDs existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @return {Response}
|
|
||||||
*/
|
|
||||||
private async validateEntriesIdsExistance(
|
|
||||||
tenantId: number,
|
|
||||||
billPaymentId: number,
|
|
||||||
billPaymentEntries: IBillPaymentEntry[]
|
|
||||||
) {
|
|
||||||
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const entriesIds = billPaymentEntries
|
|
||||||
.filter((entry: any) => entry.id)
|
|
||||||
.map((entry: any) => entry.id);
|
|
||||||
|
|
||||||
const storedEntries = await BillPaymentEntry.query().where(
|
|
||||||
'bill_payment_id',
|
|
||||||
billPaymentId
|
|
||||||
);
|
|
||||||
|
|
||||||
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
|
|
||||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
|
||||||
|
|
||||||
if (notFoundEntriesIds.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_PAYMENT_ENTRIES_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* * Validate the payment vendor whether modified.
|
|
||||||
* @param {string} billPaymentNo
|
|
||||||
*/
|
|
||||||
private validateVendorNotModified(
|
|
||||||
billPaymentDTO: IBillPaymentDTO,
|
|
||||||
oldBillPayment: IBillPayment
|
|
||||||
) {
|
|
||||||
if (billPaymentDTO.vendorId !== oldBillPayment.vendorId) {
|
|
||||||
throw new ServiceError(ERRORS.PAYMENT_NUMBER_SHOULD_NOT_MODIFY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the payment account currency code. The deposit account curreny
|
|
||||||
* should be equals the customer currency code or the base currency.
|
|
||||||
* @param {string} paymentAccountCurrency
|
|
||||||
* @param {string} customerCurrency
|
|
||||||
* @param {string} baseCurrency
|
|
||||||
* @throws {ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID)}
|
|
||||||
*/
|
|
||||||
public validateWithdrawalAccountCurrency = (
|
|
||||||
paymentAccountCurrency: string,
|
|
||||||
customerCurrency: string,
|
|
||||||
baseCurrency: string
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
paymentAccountCurrency !== customerCurrency &&
|
|
||||||
paymentAccountCurrency !== baseCurrency
|
|
||||||
) {
|
|
||||||
throw new ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms create/edit DTO to model.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IBillPaymentDTO} billPaymentDTO - Bill payment.
|
|
||||||
* @param {IBillPayment} oldBillPayment - Old bill payment.
|
|
||||||
* @return {Promise<IBillPayment>}
|
|
||||||
*/
|
|
||||||
async transformDTOToModel(
|
|
||||||
tenantId: number,
|
|
||||||
billPaymentDTO: IBillPaymentDTO,
|
|
||||||
vendor: IVendor,
|
|
||||||
oldBillPayment?: IBillPayment
|
|
||||||
): Promise<IBillPayment> {
|
|
||||||
const initialDTO = {
|
|
||||||
...formatDateFields(billPaymentDTO, ['paymentDate']),
|
|
||||||
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
|
|
||||||
currencyCode: vendor.currencyCode,
|
|
||||||
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
|
||||||
entries: billPaymentDTO.entries,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
|
||||||
)(initialDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bill payment transcations and store it to the storage
|
|
||||||
* with associated bills entries and journal transactions.
|
|
||||||
*
|
|
||||||
* Precedures:-
|
|
||||||
* ------
|
|
||||||
* - Records the bill payment transaction.
|
|
||||||
* - Records the bill payment associated entries.
|
|
||||||
* - Increment the payment amount of the given vendor bills.
|
|
||||||
* - Decrement the vendor balance.
|
|
||||||
* - Records payment journal entries.
|
|
||||||
* ------
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {BillPaymentDTO} billPayment - Bill payment object.
|
|
||||||
*/
|
|
||||||
public async createBillPayment(
|
|
||||||
tenantId: number,
|
|
||||||
billPaymentDTO: IBillPaymentDTO
|
|
||||||
): Promise<IBillPayment> {
|
|
||||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
|
||||||
|
|
||||||
// Retrieves the payment vendor or throw not found error.
|
|
||||||
const vendor = await Contact.query()
|
|
||||||
.findById(billPaymentDTO.vendorId)
|
|
||||||
.modify('vendor')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Transform create DTO to model object.
|
|
||||||
const billPaymentObj = await this.transformDTOToModel(
|
|
||||||
tenantId,
|
|
||||||
billPaymentDTO,
|
|
||||||
vendor
|
|
||||||
);
|
|
||||||
// Validate the payment account existance and type.
|
|
||||||
const paymentAccount = await this.getPaymentAccountOrThrowError(
|
|
||||||
tenantId,
|
|
||||||
billPaymentObj.paymentAccountId
|
|
||||||
);
|
|
||||||
// Validate the payment number uniquiness.
|
|
||||||
if (billPaymentObj.paymentNumber) {
|
|
||||||
await this.validatePaymentNumber(tenantId, billPaymentObj.paymentNumber);
|
|
||||||
}
|
|
||||||
// Validates the bills existance and associated to the given vendor.
|
|
||||||
await this.validateBillsExistance(
|
|
||||||
tenantId,
|
|
||||||
billPaymentObj.entries,
|
|
||||||
billPaymentDTO.vendorId
|
|
||||||
);
|
|
||||||
// Validates the bills due payment amount.
|
|
||||||
await this.validateBillsDueAmount(tenantId, billPaymentObj.entries);
|
|
||||||
|
|
||||||
// Validates the withdrawal account currency code.
|
|
||||||
this.validateWithdrawalAccountCurrency(
|
|
||||||
paymentAccount.currencyCode,
|
|
||||||
vendor.currencyCode,
|
|
||||||
tenantMeta.baseCurrency
|
|
||||||
);
|
|
||||||
// Writes bill payment transacation with associated transactions
|
|
||||||
// under unit-of-work envirement.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBillPaymentCreating` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
|
|
||||||
tenantId,
|
|
||||||
billPaymentDTO,
|
|
||||||
trx,
|
|
||||||
} as IBillPaymentCreatingPayload);
|
|
||||||
|
|
||||||
// Writes the bill payment graph to the storage.
|
|
||||||
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
|
|
||||||
...billPaymentObj,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Triggers `onBillPaymentCreated` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
|
||||||
tenantId,
|
|
||||||
billPayment,
|
|
||||||
billPaymentId: billPayment.id,
|
|
||||||
trx,
|
|
||||||
} as IBillPaymentEventCreatedPayload);
|
|
||||||
|
|
||||||
return billPayment;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edits the details of the given bill payment.
|
|
||||||
*
|
|
||||||
* Preceducres:
|
|
||||||
* ------
|
|
||||||
* - Update the bill payment transaction.
|
|
||||||
* - Insert the new bill payment entries that have no ids.
|
|
||||||
* - Update the bill paymeny entries that have ids.
|
|
||||||
* - Delete the bill payment entries that not presented.
|
|
||||||
* - Re-insert the journal transactions and update the diff accounts balance.
|
|
||||||
* - Update the diff vendor balance.
|
|
||||||
* - Update the diff bill payment amount.
|
|
||||||
* ------
|
|
||||||
* @param {number} tenantId - Tenant id
|
|
||||||
* @param {Integer} billPaymentId
|
|
||||||
* @param {BillPaymentDTO} billPayment
|
|
||||||
* @param {IBillPayment} oldBillPayment
|
|
||||||
*/
|
|
||||||
public async editBillPayment(
|
|
||||||
tenantId: number,
|
|
||||||
billPaymentId: number,
|
|
||||||
billPaymentDTO
|
|
||||||
): Promise<IBillPayment> {
|
|
||||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
|
||||||
|
|
||||||
//
|
|
||||||
const oldBillPayment = await this.getPaymentMadeOrThrowError(
|
|
||||||
tenantId,
|
|
||||||
billPaymentId
|
|
||||||
);
|
|
||||||
|
|
||||||
//
|
|
||||||
const vendor = await Contact.query()
|
|
||||||
.modify('vendor')
|
|
||||||
.findById(billPaymentDTO.vendorId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Transform bill payment DTO to model object.
|
|
||||||
const billPaymentObj = await this.transformDTOToModel(
|
|
||||||
tenantId,
|
|
||||||
billPaymentDTO,
|
|
||||||
vendor,
|
|
||||||
oldBillPayment
|
|
||||||
);
|
|
||||||
// Validate vendor not modified.
|
|
||||||
this.validateVendorNotModified(billPaymentDTO, oldBillPayment);
|
|
||||||
|
|
||||||
// Validate the payment account existance and type.
|
|
||||||
const paymentAccount = await this.getPaymentAccountOrThrowError(
|
|
||||||
tenantId,
|
|
||||||
billPaymentObj.paymentAccountId
|
|
||||||
);
|
|
||||||
// Validate the items entries IDs existance on the storage.
|
|
||||||
await this.validateEntriesIdsExistance(
|
|
||||||
tenantId,
|
|
||||||
billPaymentId,
|
|
||||||
billPaymentObj.entries
|
|
||||||
);
|
|
||||||
// Validate the bills existance and associated to the given vendor.
|
|
||||||
await this.validateBillsExistance(
|
|
||||||
tenantId,
|
|
||||||
billPaymentObj.entries,
|
|
||||||
billPaymentDTO.vendorId
|
|
||||||
);
|
|
||||||
// Validates the bills due payment amount.
|
|
||||||
await this.validateBillsDueAmount(
|
|
||||||
tenantId,
|
|
||||||
billPaymentObj.entries,
|
|
||||||
oldBillPayment.entries
|
|
||||||
);
|
|
||||||
// Validate the payment number uniquiness.
|
|
||||||
if (billPaymentObj.paymentNumber) {
|
|
||||||
await this.validatePaymentNumber(
|
|
||||||
tenantId,
|
|
||||||
billPaymentObj.paymentNumber,
|
|
||||||
billPaymentId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Validates the withdrawal account currency code.
|
|
||||||
this.validateWithdrawalAccountCurrency(
|
|
||||||
paymentAccount.currencyCode,
|
|
||||||
vendor.currencyCode,
|
|
||||||
tenantMeta.baseCurrency
|
|
||||||
);
|
|
||||||
// Edits the bill transactions with associated transactions
|
|
||||||
// under unit-of-work envirement.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBillPaymentEditing` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onEditing, {
|
|
||||||
tenantId,
|
|
||||||
oldBillPayment,
|
|
||||||
billPaymentDTO,
|
|
||||||
trx,
|
|
||||||
} as IBillPaymentEditingPayload);
|
|
||||||
|
|
||||||
// Deletes the bill payment transaction graph from the storage.
|
|
||||||
const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({
|
|
||||||
id: billPaymentId,
|
|
||||||
...billPaymentObj,
|
|
||||||
});
|
|
||||||
// Triggers `onBillPaymentEdited` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onEdited, {
|
|
||||||
tenantId,
|
|
||||||
billPaymentId,
|
|
||||||
billPayment,
|
|
||||||
oldBillPayment,
|
|
||||||
trx,
|
|
||||||
} as IBillPaymentEventEditedPayload);
|
|
||||||
|
|
||||||
return billPayment;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the bill payment and associated transactions.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {Integer} billPaymentId - The given bill payment id.
|
|
||||||
* @return {Promise}
|
|
||||||
*/
|
|
||||||
public async deleteBillPayment(tenantId: number, billPaymentId: number) {
|
|
||||||
const { BillPayment, BillPaymentEntry } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retrieve the bill payment or throw not found service error.
|
|
||||||
const oldBillPayment = await this.getPaymentMadeOrThrowError(
|
|
||||||
tenantId,
|
|
||||||
billPaymentId
|
|
||||||
);
|
|
||||||
// Deletes the bill transactions with associated transactions under
|
|
||||||
// unit-of-work envirement.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBillPaymentDeleting` payload.
|
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onDeleting, {
|
|
||||||
tenantId,
|
|
||||||
trx,
|
|
||||||
oldBillPayment,
|
|
||||||
} as IBillPaymentDeletingPayload);
|
|
||||||
|
|
||||||
// Deletes the bill payment assocaited entries.
|
|
||||||
await BillPaymentEntry.query(trx)
|
|
||||||
.where('bill_payment_id', billPaymentId)
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
// Deletes the bill payment transaction.
|
|
||||||
await BillPayment.query(trx).where('id', billPaymentId).delete();
|
|
||||||
|
|
||||||
// Triggers `onBillPaymentDeleted` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.billPayment.onDeleted, {
|
|
||||||
tenantId,
|
|
||||||
billPaymentId,
|
|
||||||
oldBillPayment,
|
|
||||||
trx,
|
|
||||||
} as IBillPaymentEventDeletedPayload);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve payment made associated bills.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {number} billPaymentId -
|
|
||||||
*/
|
|
||||||
public async getPaymentBills(tenantId: number, billPaymentId: number) {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const billPayment = await this.getPaymentMadeOrThrowError(
|
|
||||||
tenantId,
|
|
||||||
billPaymentId
|
|
||||||
);
|
|
||||||
const paymentBillsIds = billPayment.entries.map((entry) => entry.id);
|
|
||||||
|
|
||||||
const bills = await Bill.query().whereIn('id', paymentBillsIds);
|
|
||||||
|
|
||||||
return bills;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve bill payment.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} billPyamentId
|
|
||||||
* @return {Promise<IBillPayment>}
|
|
||||||
*/
|
|
||||||
public async getBillPayment(
|
|
||||||
tenantId: number,
|
|
||||||
billPyamentId: number
|
|
||||||
): Promise<IBillPayment> {
|
|
||||||
const { BillPayment } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const billPayment = await BillPayment.query()
|
|
||||||
.withGraphFetched('entries.bill')
|
|
||||||
.withGraphFetched('vendor')
|
|
||||||
.withGraphFetched('paymentAccount')
|
|
||||||
.withGraphFetched('transactions')
|
|
||||||
.withGraphFetched('branch')
|
|
||||||
.findById(billPyamentId);
|
|
||||||
|
|
||||||
if (!billPayment) {
|
|
||||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
billPayment,
|
|
||||||
new BillPaymentTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseListFilterDTO(filterDTO) {
|
|
||||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve bill payment paginted and filterable list.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IBillPaymentsFilter} billPaymentsFilter
|
|
||||||
*/
|
|
||||||
public async listBillPayments(
|
|
||||||
tenantId: number,
|
|
||||||
filterDTO: IBillPaymentsFilter
|
|
||||||
): Promise<{
|
|
||||||
billPayments: IBillPayment;
|
|
||||||
pagination: IPaginationMeta;
|
|
||||||
filterMeta: IFilterMeta;
|
|
||||||
}> {
|
|
||||||
const { BillPayment } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Parses filter DTO.
|
|
||||||
const filter = this.parseListFilterDTO(filterDTO);
|
|
||||||
|
|
||||||
// Dynamic list service.
|
|
||||||
const dynamicList = await this.dynamicListService.dynamicList(
|
|
||||||
tenantId,
|
|
||||||
BillPayment,
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
|
|
||||||
const { results, pagination } = await BillPayment.query()
|
|
||||||
.onBuild((builder) => {
|
|
||||||
builder.withGraphFetched('vendor');
|
|
||||||
builder.withGraphFetched('paymentAccount');
|
|
||||||
|
|
||||||
dynamicList.buildQuery()(builder);
|
|
||||||
})
|
|
||||||
.pagination(filter.page - 1, filter.pageSize);
|
|
||||||
|
|
||||||
// Transformes the bill payments models to POJO.
|
|
||||||
const billPayments = await this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
results,
|
|
||||||
new BillPaymentTransformer()
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
billPayments,
|
|
||||||
pagination,
|
|
||||||
filterMeta: dynamicList.getResponseMeta(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves bills payment amount changes different.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {IBillPaymentEntryDTO[]} paymentMadeEntries -
|
|
||||||
* @param {IBillPaymentEntryDTO[]} oldPaymentMadeEntries -
|
|
||||||
*/
|
|
||||||
public async saveChangeBillsPaymentAmount(
|
|
||||||
tenantId: number,
|
|
||||||
paymentMadeEntries: IBillPaymentEntryDTO[],
|
|
||||||
oldPaymentMadeEntries?: IBillPaymentEntryDTO[],
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
const opers: Promise<void>[] = [];
|
|
||||||
|
|
||||||
const diffEntries = entriesAmountDiff(
|
|
||||||
paymentMadeEntries,
|
|
||||||
oldPaymentMadeEntries,
|
|
||||||
'paymentAmount',
|
|
||||||
'billId'
|
|
||||||
);
|
|
||||||
diffEntries.forEach(
|
|
||||||
(diffEntry: { paymentAmount: number; billId: number }) => {
|
|
||||||
if (diffEntry.paymentAmount === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const oper = Bill.changePaymentAmount(
|
|
||||||
diffEntry.billId,
|
|
||||||
diffEntry.paymentAmount,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
opers.push(oper);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await Promise.all(opers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the given vendor has no associated payments.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} vendorId
|
|
||||||
*/
|
|
||||||
public async validateVendorHasNoPayments(tenantId: number, vendorId: number) {
|
|
||||||
const { BillPayment } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const payments = await BillPayment.query().where('vendor_id', vendorId);
|
|
||||||
|
|
||||||
if (payments.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.VENDOR_HAS_PAYMENTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { IBillPaymentDTO, IBillPayment } from '@/interfaces';
|
||||||
|
import { CreateBillPayment } from './CreateBillPayment';
|
||||||
|
import { DeleteBillPayment } from './DeleteBillPayment';
|
||||||
|
import { EditBillPayment } from './EditBillPayment';
|
||||||
|
import { GetBillPayments } from './GetBillPayments';
|
||||||
|
import { GetBillPayment } from './GetBillPayment';
|
||||||
|
import { GetPaymentBills } from './GetPaymentBills';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill payments application.
|
||||||
|
* @service
|
||||||
|
*/
|
||||||
|
@Service()
|
||||||
|
export class BillPaymentsApplication {
|
||||||
|
@Inject()
|
||||||
|
private createBillPaymentService: CreateBillPayment;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private deleteBillPaymentService: DeleteBillPayment;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private editBillPaymentService: EditBillPayment;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getBillPaymentsService: GetBillPayments;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getBillPaymentService: GetBillPayment;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentBillsService: GetPaymentBills;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a bill payment with associated GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillPaymentDTO} billPaymentDTO
|
||||||
|
* @returns {Promise<IBillPayment>}
|
||||||
|
*/
|
||||||
|
public createBillPayment(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentDTO: IBillPaymentDTO
|
||||||
|
): Promise<IBillPayment> {
|
||||||
|
return this.createBillPaymentService.createBillPayment(
|
||||||
|
tenantId,
|
||||||
|
billPaymentDTO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delets the given bill payment with associated GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billPaymentId
|
||||||
|
*/
|
||||||
|
public deleteBillPayment(tenantId: number, billPaymentId: number) {
|
||||||
|
return this.deleteBillPaymentService.deleteBillPayment(
|
||||||
|
tenantId,
|
||||||
|
billPaymentId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given bill payment with associated GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billPaymentId
|
||||||
|
* @param billPaymentDTO
|
||||||
|
* @returns {Promise<IBillPayment>}
|
||||||
|
*/
|
||||||
|
public editBillPayment(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentId: number,
|
||||||
|
billPaymentDTO
|
||||||
|
): Promise<IBillPayment> {
|
||||||
|
return this.editBillPaymentService.editBillPayment(
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
billPaymentDTO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves bill payments list.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param filterDTO
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getBillPayments(tenantId: number, filterDTO: IBillPaymentsFilter) {
|
||||||
|
return this.getBillPaymentsService.getBillPayments(tenantId, filterDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve specific bill payment.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billPyamentId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getBillPayment(tenantId: number, billPyamentId: number) {
|
||||||
|
return this.getBillPaymentService.getBillPayment(tenantId, billPyamentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve payment made associated bills.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} billPaymentId -
|
||||||
|
*/
|
||||||
|
public getPaymentBills(tenantId: number, billPaymentId: number) {
|
||||||
|
return this.getPaymentBillsService.getPaymentBills(tenantId, billPaymentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,10 @@ import { IBill, IBillPayment, IBillReceivePageEntry } from '@/interfaces';
|
|||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
/**
|
|
||||||
* Bill payments edit and create pages services.
|
|
||||||
*/
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class BillPaymentsPages {
|
export default class BillPaymentsPages {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve bill payment with associated metadata.
|
* Retrieve bill payment with associated metadata.
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { sumBy } from 'lodash';
|
||||||
|
import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces';
|
||||||
|
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||||
|
import { formatDateFields } from '@/utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CommandBillPaymentDTOTransformer {
|
||||||
|
@Inject()
|
||||||
|
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms create/edit DTO to model.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillPaymentDTO} billPaymentDTO - Bill payment.
|
||||||
|
* @param {IBillPayment} oldBillPayment - Old bill payment.
|
||||||
|
* @return {Promise<IBillPayment>}
|
||||||
|
*/
|
||||||
|
public async transformDTOToModel(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentDTO: IBillPaymentDTO,
|
||||||
|
vendor: IVendor,
|
||||||
|
oldBillPayment?: IBillPayment
|
||||||
|
): Promise<IBillPayment> {
|
||||||
|
const initialDTO = {
|
||||||
|
...formatDateFields(billPaymentDTO, ['paymentDate']),
|
||||||
|
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
|
||||||
|
currencyCode: vendor.currencyCode,
|
||||||
|
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
||||||
|
entries: billPaymentDTO.entries,
|
||||||
|
};
|
||||||
|
return R.compose(
|
||||||
|
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
||||||
|
)(initialDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
IBillPaymentDTO,
|
||||||
|
IBillPayment,
|
||||||
|
IBillPaymentEventCreatedPayload,
|
||||||
|
IBillPaymentCreatingPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||||
|
import { CommandBillPaymentDTOTransformer } from './CommandBillPaymentDTOTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreateBillPayment {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillPaymentValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private commandTransformerDTO: CommandBillPaymentDTOTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new bill payment transcations and store it to the storage
|
||||||
|
* with associated bills entries and journal transactions.
|
||||||
|
* ------
|
||||||
|
* Precedures:-
|
||||||
|
* ------
|
||||||
|
* - Records the bill payment transaction.
|
||||||
|
* - Records the bill payment associated entries.
|
||||||
|
* - Increment the payment amount of the given vendor bills.
|
||||||
|
* - Decrement the vendor balance.
|
||||||
|
* - Records payment journal entries.
|
||||||
|
* ------
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {BillPaymentDTO} billPayment - Bill payment object.
|
||||||
|
*/
|
||||||
|
public async createBillPayment(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentDTO: IBillPaymentDTO
|
||||||
|
): Promise<IBillPayment> {
|
||||||
|
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
|
// Retrieves the payment vendor or throw not found error.
|
||||||
|
const vendor = await Contact.query()
|
||||||
|
.findById(billPaymentDTO.vendorId)
|
||||||
|
.modify('vendor')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Transform create DTO to model object.
|
||||||
|
const billPaymentObj = await this.commandTransformerDTO.transformDTOToModel(
|
||||||
|
tenantId,
|
||||||
|
billPaymentDTO,
|
||||||
|
vendor
|
||||||
|
);
|
||||||
|
// Validate the payment account existance and type.
|
||||||
|
const paymentAccount = await this.validators.getPaymentAccountOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.paymentAccountId
|
||||||
|
);
|
||||||
|
// Validate the payment number uniquiness.
|
||||||
|
if (billPaymentObj.paymentNumber) {
|
||||||
|
await this.validators.validatePaymentNumber(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.paymentNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Validates the bills existance and associated to the given vendor.
|
||||||
|
await this.validators.validateBillsExistance(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.entries,
|
||||||
|
billPaymentDTO.vendorId
|
||||||
|
);
|
||||||
|
// Validates the bills due payment amount.
|
||||||
|
await this.validators.validateBillsDueAmount(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.entries
|
||||||
|
);
|
||||||
|
// Validates the withdrawal account currency code.
|
||||||
|
this.validators.validateWithdrawalAccountCurrency(
|
||||||
|
paymentAccount.currencyCode,
|
||||||
|
vendor.currencyCode,
|
||||||
|
tenantMeta.baseCurrency
|
||||||
|
);
|
||||||
|
// Writes bill payment transacation with associated transactions
|
||||||
|
// under unit-of-work envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBillPaymentCreating` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
|
||||||
|
tenantId,
|
||||||
|
billPaymentDTO,
|
||||||
|
trx,
|
||||||
|
} as IBillPaymentCreatingPayload);
|
||||||
|
|
||||||
|
// Writes the bill payment graph to the storage.
|
||||||
|
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
|
||||||
|
...billPaymentObj,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Triggers `onBillPaymentCreated` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
||||||
|
tenantId,
|
||||||
|
billPayment,
|
||||||
|
billPaymentId: billPayment.id,
|
||||||
|
trx,
|
||||||
|
} as IBillPaymentEventCreatedPayload);
|
||||||
|
|
||||||
|
return billPayment;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import {
|
||||||
|
IBillPaymentDeletingPayload,
|
||||||
|
IBillPaymentEventDeletedPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeleteBillPayment {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillPaymentValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the bill payment and associated transactions.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {Integer} billPaymentId - The given bill payment id.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
public async deleteBillPayment(tenantId: number, billPaymentId: number) {
|
||||||
|
const { BillPayment, BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the bill payment or throw not found service error.
|
||||||
|
const oldBillPayment = await BillPayment.query()
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.findById(billPaymentId);
|
||||||
|
|
||||||
|
// Validates the bill payment existance.
|
||||||
|
this.validators.validateBillPaymentExistance(oldBillPayment);
|
||||||
|
|
||||||
|
// Deletes the bill transactions with associated transactions under
|
||||||
|
// unit-of-work envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBillPaymentDeleting` payload.
|
||||||
|
await this.eventPublisher.emitAsync(events.billPayment.onDeleting, {
|
||||||
|
tenantId,
|
||||||
|
trx,
|
||||||
|
oldBillPayment,
|
||||||
|
} as IBillPaymentDeletingPayload);
|
||||||
|
|
||||||
|
// Deletes the bill payment assocaited entries.
|
||||||
|
await BillPaymentEntry.query(trx)
|
||||||
|
.where('bill_payment_id', billPaymentId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Deletes the bill payment transaction.
|
||||||
|
await BillPayment.query(trx).where('id', billPaymentId).delete();
|
||||||
|
|
||||||
|
// Triggers `onBillPaymentDeleted` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billPayment.onDeleted, {
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
oldBillPayment,
|
||||||
|
trx,
|
||||||
|
} as IBillPaymentEventDeletedPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import {
|
||||||
|
IBillPayment,
|
||||||
|
IBillPaymentEditingPayload,
|
||||||
|
IBillPaymentEventEditedPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { CommandBillPaymentDTOTransformer } from './CommandBillPaymentDTOTransformer';
|
||||||
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class EditBillPayment {
|
||||||
|
@Inject()
|
||||||
|
private validators: BillPaymentValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: CommandBillPaymentDTOTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the details of the given bill payment.
|
||||||
|
*
|
||||||
|
* Preceducres:
|
||||||
|
* ------
|
||||||
|
* - Update the bill payment transaction.
|
||||||
|
* - Insert the new bill payment entries that have no ids.
|
||||||
|
* - Update the bill paymeny entries that have ids.
|
||||||
|
* - Delete the bill payment entries that not presented.
|
||||||
|
* - Re-insert the journal transactions and update the diff accounts balance.
|
||||||
|
* - Update the diff vendor balance.
|
||||||
|
* - Update the diff bill payment amount.
|
||||||
|
* ------
|
||||||
|
* @param {number} tenantId - Tenant id
|
||||||
|
* @param {Integer} billPaymentId
|
||||||
|
* @param {BillPaymentDTO} billPayment
|
||||||
|
* @param {IBillPayment} oldBillPayment
|
||||||
|
*/
|
||||||
|
public async editBillPayment(
|
||||||
|
tenantId: number,
|
||||||
|
billPaymentId: number,
|
||||||
|
billPaymentDTO
|
||||||
|
): Promise<IBillPayment> {
|
||||||
|
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||||
|
|
||||||
|
const oldBillPayment = await BillPayment.query().findById(billPaymentId);
|
||||||
|
|
||||||
|
// Validates the bill payment existance.
|
||||||
|
this.validators.validateBillPaymentExistance(oldBillPayment);
|
||||||
|
|
||||||
|
//
|
||||||
|
const vendor = await Contact.query()
|
||||||
|
.modify('vendor')
|
||||||
|
.findById(billPaymentDTO.vendorId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Transform bill payment DTO to model object.
|
||||||
|
const billPaymentObj = await this.transformer.transformDTOToModel(
|
||||||
|
tenantId,
|
||||||
|
billPaymentDTO,
|
||||||
|
vendor,
|
||||||
|
oldBillPayment
|
||||||
|
);
|
||||||
|
// Validate vendor not modified.
|
||||||
|
this.validators.validateVendorNotModified(billPaymentDTO, oldBillPayment);
|
||||||
|
|
||||||
|
// Validate the payment account existance and type.
|
||||||
|
const paymentAccount = await this.validators.getPaymentAccountOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.paymentAccountId
|
||||||
|
);
|
||||||
|
// Validate the items entries IDs existance on the storage.
|
||||||
|
await this.validators.validateEntriesIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
billPaymentObj.entries
|
||||||
|
);
|
||||||
|
// Validate the bills existance and associated to the given vendor.
|
||||||
|
await this.validators.validateBillsExistance(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.entries,
|
||||||
|
billPaymentDTO.vendorId
|
||||||
|
);
|
||||||
|
// Validates the bills due payment amount.
|
||||||
|
await this.validators.validateBillsDueAmount(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.entries,
|
||||||
|
oldBillPayment.entries
|
||||||
|
);
|
||||||
|
// Validate the payment number uniquiness.
|
||||||
|
if (billPaymentObj.paymentNumber) {
|
||||||
|
await this.validators.validatePaymentNumber(
|
||||||
|
tenantId,
|
||||||
|
billPaymentObj.paymentNumber,
|
||||||
|
billPaymentId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Validates the withdrawal account currency code.
|
||||||
|
this.validators.validateWithdrawalAccountCurrency(
|
||||||
|
paymentAccount.currencyCode,
|
||||||
|
vendor.currencyCode,
|
||||||
|
tenantMeta.baseCurrency
|
||||||
|
);
|
||||||
|
// Edits the bill transactions with associated transactions
|
||||||
|
// under unit-of-work envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBillPaymentEditing` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billPayment.onEditing, {
|
||||||
|
tenantId,
|
||||||
|
oldBillPayment,
|
||||||
|
billPaymentDTO,
|
||||||
|
trx,
|
||||||
|
} as IBillPaymentEditingPayload);
|
||||||
|
|
||||||
|
// Deletes the bill payment transaction graph from the storage.
|
||||||
|
const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({
|
||||||
|
id: billPaymentId,
|
||||||
|
...billPaymentObj,
|
||||||
|
});
|
||||||
|
// Triggers `onBillPaymentEdited` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billPayment.onEdited, {
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
billPayment,
|
||||||
|
oldBillPayment,
|
||||||
|
trx,
|
||||||
|
} as IBillPaymentEventEditedPayload);
|
||||||
|
|
||||||
|
return billPayment;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { IBillPayment } from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { BillPaymentTransformer } from './BillPaymentTransformer';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { BillsValidators } from '../Bills/BillsValidators';
|
||||||
|
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetBillPayment {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillPaymentValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve bill payment.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billPyamentId
|
||||||
|
* @return {Promise<IBillPayment>}
|
||||||
|
*/
|
||||||
|
public async getBillPayment(
|
||||||
|
tenantId: number,
|
||||||
|
billPyamentId: number
|
||||||
|
): Promise<IBillPayment> {
|
||||||
|
const { BillPayment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const billPayment = await BillPayment.query()
|
||||||
|
.withGraphFetched('entries.bill')
|
||||||
|
.withGraphFetched('vendor')
|
||||||
|
.withGraphFetched('paymentAccount')
|
||||||
|
.withGraphFetched('transactions')
|
||||||
|
.withGraphFetched('branch')
|
||||||
|
.findById(billPyamentId);
|
||||||
|
|
||||||
|
// Validates the bill payment existance.
|
||||||
|
this.validators.validateBillPaymentExistance(billPayment);
|
||||||
|
|
||||||
|
return this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
billPayment,
|
||||||
|
new BillPaymentTransformer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
IBillPayment,
|
||||||
|
IBillPaymentsFilter,
|
||||||
|
IPaginationMeta,
|
||||||
|
IFilterMeta,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { BillPaymentTransformer } from './BillPaymentTransformer';
|
||||||
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetBillPayments {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve bill payment paginted and filterable list.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillPaymentsFilter} billPaymentsFilter
|
||||||
|
*/
|
||||||
|
public async getBillPayments(
|
||||||
|
tenantId: number,
|
||||||
|
filterDTO: IBillPaymentsFilter
|
||||||
|
): Promise<{
|
||||||
|
billPayments: IBillPayment;
|
||||||
|
pagination: IPaginationMeta;
|
||||||
|
filterMeta: IFilterMeta;
|
||||||
|
}> {
|
||||||
|
const { BillPayment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses filter DTO.
|
||||||
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
BillPayment,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
const { results, pagination } = await BillPayment.query()
|
||||||
|
.onBuild((builder) => {
|
||||||
|
builder.withGraphFetched('vendor');
|
||||||
|
builder.withGraphFetched('paymentAccount');
|
||||||
|
|
||||||
|
dynamicList.buildQuery()(builder);
|
||||||
|
})
|
||||||
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
|
// Transformes the bill payments models to POJO.
|
||||||
|
const billPayments = await this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
results,
|
||||||
|
new BillPaymentTransformer()
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
billPayments,
|
||||||
|
pagination,
|
||||||
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseListFilterDTO(filterDTO) {
|
||||||
|
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentBills {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillPaymentValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve payment made associated bills.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} billPaymentId -
|
||||||
|
*/
|
||||||
|
public async getPaymentBills(tenantId: number, billPaymentId: number) {
|
||||||
|
const { Bill, BillPayment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const billPayment = await BillPayment.query().findById(billPaymentId);
|
||||||
|
|
||||||
|
// Validates the bill payment existance.
|
||||||
|
this.validators.validateBillPaymentExistance(billPayment);
|
||||||
|
|
||||||
|
const paymentBillsIds = billPayment.entries.map((entry) => entry.id);
|
||||||
|
|
||||||
|
const bills = await Bill.query().whereIn('id', paymentBillsIds);
|
||||||
|
|
||||||
|
return bills;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,751 +0,0 @@
|
|||||||
import { omit, sumBy } from 'lodash';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import composeAsync from 'async/compose';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import InventoryService from '@/services/Inventory/Inventory';
|
|
||||||
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
|
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
|
||||||
import { formatDateFields, transformToMap } from 'utils';
|
|
||||||
import {
|
|
||||||
IBillDTO,
|
|
||||||
IBill,
|
|
||||||
ISystemUser,
|
|
||||||
IBillEditDTO,
|
|
||||||
IPaginationMeta,
|
|
||||||
IFilterMeta,
|
|
||||||
IBillsFilter,
|
|
||||||
IBillsService,
|
|
||||||
IItemEntry,
|
|
||||||
IItemEntryDTO,
|
|
||||||
IBillCreatedPayload,
|
|
||||||
IBillEditedPayload,
|
|
||||||
IBIllEventDeletedPayload,
|
|
||||||
IBillEventDeletingPayload,
|
|
||||||
IBillEditingPayload,
|
|
||||||
IBillCreatingPayload,
|
|
||||||
IVendor,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
|
||||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
|
||||||
import { ERRORS } from './constants';
|
|
||||||
import EntriesService from '@/services/Entries';
|
|
||||||
import { PurchaseInvoiceTransformer } from './PurchaseInvoices/PurchaseInvoiceTransformer';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
|
||||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
|
||||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vendor bills services.
|
|
||||||
* @service
|
|
||||||
*/
|
|
||||||
@Service('Bills')
|
|
||||||
export default class BillsService
|
|
||||||
extends SalesInvoicesCost
|
|
||||||
implements IBillsService
|
|
||||||
{
|
|
||||||
@Inject()
|
|
||||||
inventoryService: InventoryService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
tenancy: TenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject('logger')
|
|
||||||
logger: any;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
dynamicListService: DynamicListingService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
itemsEntriesService: ItemsEntriesService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
journalPosterService: JournalPosterService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
entriesService: EntriesService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
transformer: TransformerInjectable;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
uow: UnitOfWork;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the given bill existance.
|
|
||||||
* @async
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {number} billId -
|
|
||||||
*/
|
|
||||||
public async getBillOrThrowError(tenantId: number, billId: number) {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const foundBill = await Bill.query()
|
|
||||||
.findById(billId)
|
|
||||||
.withGraphFetched('entries');
|
|
||||||
|
|
||||||
if (!foundBill) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return foundBill;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the bill number existance.
|
|
||||||
* @async
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
private async validateBillNumberExists(
|
|
||||||
tenantId: number,
|
|
||||||
billNumber: string,
|
|
||||||
notBillId?: number
|
|
||||||
) {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
const foundBills = await Bill.query()
|
|
||||||
.where('bill_number', billNumber)
|
|
||||||
.onBuild((builder) => {
|
|
||||||
if (notBillId) {
|
|
||||||
builder.whereNot('id', notBillId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (foundBills.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the bill has no payment entries.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} billId - Bill id.
|
|
||||||
*/
|
|
||||||
private async validateBillHasNoEntries(tenantId, billId: number) {
|
|
||||||
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retireve the bill associate payment made entries.
|
|
||||||
const entries = await BillPaymentEntry.query().where('bill_id', billId);
|
|
||||||
|
|
||||||
if (entries.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES);
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the bill number require.
|
|
||||||
* @param {string} billNo -
|
|
||||||
*/
|
|
||||||
private validateBillNoRequire(billNo: string) {
|
|
||||||
if (!billNo) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_NO_IS_REQUIRED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate bill transaction has no associated allocated landed cost transactions.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} billId
|
|
||||||
*/
|
|
||||||
private async validateBillHasNoLandedCost(tenantId: number, billId: number) {
|
|
||||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const billLandedCosts = await BillLandedCost.query().where(
|
|
||||||
'billId',
|
|
||||||
billId
|
|
||||||
);
|
|
||||||
if (billLandedCosts.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_LANDED_COSTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate transaction entries that have landed cost type should not be
|
|
||||||
* inventory items.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {IItemEntryDTO[]} newEntriesDTO -
|
|
||||||
*/
|
|
||||||
public async validateCostEntriesShouldBeInventoryItems(
|
|
||||||
tenantId: number,
|
|
||||||
newEntriesDTO: IItemEntryDTO[]
|
|
||||||
) {
|
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const entriesItemsIds = newEntriesDTO.map((e) => e.itemId);
|
|
||||||
const entriesItems = await Item.query().whereIn('id', entriesItemsIds);
|
|
||||||
|
|
||||||
const entriesItemsById = transformToMap(entriesItems, 'id');
|
|
||||||
|
|
||||||
// Filter the landed cost entries that not associated with inventory item.
|
|
||||||
const nonInventoryHasCost = newEntriesDTO.filter((entry) => {
|
|
||||||
const item = entriesItemsById.get(entry.itemId);
|
|
||||||
|
|
||||||
return entry.landedCost && item.type !== 'inventory';
|
|
||||||
});
|
|
||||||
if (nonInventoryHasCost.length > 0) {
|
|
||||||
throw new ServiceError(
|
|
||||||
ERRORS.LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default cost account to the bill entries.
|
|
||||||
*/
|
|
||||||
private setBillEntriesDefaultAccounts(tenantId: number) {
|
|
||||||
return async (entries: IItemEntry[]) => {
|
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
|
||||||
const items = await Item.query().whereIn('id', entriesItemsIds);
|
|
||||||
|
|
||||||
return entries.map((entry) => {
|
|
||||||
const item = items.find((i) => i.id === entry.itemId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...entry,
|
|
||||||
...(item.type !== 'inventory' && {
|
|
||||||
costAccountId: entry.costAccountId || item.costAccountId,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the bill entries total.
|
|
||||||
* @param {IItemEntry[]} entries
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
private getBillEntriesTotal(tenantId: number, entries: IItemEntry[]): number {
|
|
||||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the bill landed cost amount.
|
|
||||||
* @param {IBillDTO} billDTO
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
private getBillLandedCostAmount(tenantId: number, billDTO: IBillDTO): number {
|
|
||||||
const costEntries = billDTO.entries.filter((entry) => entry.landedCost);
|
|
||||||
|
|
||||||
return this.getBillEntriesTotal(tenantId, costEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts create bill DTO to model.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IBillDTO} billDTO
|
|
||||||
* @param {IBill} oldBill
|
|
||||||
* @returns {IBill}
|
|
||||||
*/
|
|
||||||
private async billDTOToModel(
|
|
||||||
tenantId: number,
|
|
||||||
billDTO: IBillDTO,
|
|
||||||
vendor: IVendor,
|
|
||||||
authorizedUser: ISystemUser,
|
|
||||||
oldBill?: IBill
|
|
||||||
) {
|
|
||||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const amount = sumBy(billDTO.entries, (e) => ItemEntry.calcAmount(e));
|
|
||||||
|
|
||||||
// Retrieve the landed cost amount from landed cost entries.
|
|
||||||
const landedCostAmount = this.getBillLandedCostAmount(tenantId, billDTO);
|
|
||||||
|
|
||||||
// Bill number from DTO or from auto-increment.
|
|
||||||
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
|
||||||
|
|
||||||
const initialEntries = billDTO.entries.map((entry) => ({
|
|
||||||
reference_type: 'Bill',
|
|
||||||
...omit(entry, ['amount']),
|
|
||||||
}));
|
|
||||||
const entries = await composeAsync(
|
|
||||||
// Sets the default cost account to the bill entries.
|
|
||||||
this.setBillEntriesDefaultAccounts(tenantId)
|
|
||||||
)(initialEntries);
|
|
||||||
|
|
||||||
const initialDTO = {
|
|
||||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
|
||||||
'billDate',
|
|
||||||
'dueDate',
|
|
||||||
]),
|
|
||||||
amount,
|
|
||||||
landedCostAmount,
|
|
||||||
currencyCode: vendor.currencyCode,
|
|
||||||
exchangeRate: billDTO.exchangeRate || 1,
|
|
||||||
billNumber,
|
|
||||||
entries,
|
|
||||||
// Avoid rewrite the open date in edit mode when already opened.
|
|
||||||
...(billDTO.open &&
|
|
||||||
!oldBill?.openedAt && {
|
|
||||||
openedAt: moment().toMySqlDateTime(),
|
|
||||||
}),
|
|
||||||
userId: authorizedUser.id,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
this.branchDTOTransform.transformDTO(tenantId),
|
|
||||||
this.warehouseDTOTransform.transformDTO(tenantId)
|
|
||||||
)(initialDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bill and stored it to the storage.
|
|
||||||
* ----
|
|
||||||
* Precedures.
|
|
||||||
* ----
|
|
||||||
* - Insert bill transactions to the storage.
|
|
||||||
* - Insert bill entries to the storage.
|
|
||||||
* - Increment the given vendor id.
|
|
||||||
* - Record bill journal transactions on the given accounts.
|
|
||||||
* - Record bill items inventory transactions.
|
|
||||||
* ----
|
|
||||||
* @param {number} tenantId - The given tenant id.
|
|
||||||
* @param {IBillDTO} billDTO -
|
|
||||||
* @return {Promise<IBill>}
|
|
||||||
*/
|
|
||||||
public async createBill(
|
|
||||||
tenantId: number,
|
|
||||||
billDTO: IBillDTO,
|
|
||||||
authorizedUser: ISystemUser
|
|
||||||
): Promise<IBill> {
|
|
||||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retrieves the given bill vendor or throw not found error.
|
|
||||||
const vendor = await Contact.query()
|
|
||||||
.modify('vendor')
|
|
||||||
.findById(billDTO.vendorId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Validate the bill number uniqiness on the storage.
|
|
||||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
|
|
||||||
|
|
||||||
// Validate items IDs existance.
|
|
||||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
|
||||||
tenantId,
|
|
||||||
billDTO.entries
|
|
||||||
);
|
|
||||||
// Validate non-purchasable items.
|
|
||||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
|
||||||
tenantId,
|
|
||||||
billDTO.entries
|
|
||||||
);
|
|
||||||
// Validates the cost entries should be with inventory items.
|
|
||||||
await this.validateCostEntriesShouldBeInventoryItems(
|
|
||||||
tenantId,
|
|
||||||
billDTO.entries
|
|
||||||
);
|
|
||||||
// Transform the bill DTO to model object.
|
|
||||||
const billObj = await this.billDTOToModel(
|
|
||||||
tenantId,
|
|
||||||
billDTO,
|
|
||||||
vendor,
|
|
||||||
authorizedUser
|
|
||||||
);
|
|
||||||
// Write new bill transaction with associated transactions under UOW env.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBillCreating` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bill.onCreating, {
|
|
||||||
trx,
|
|
||||||
billDTO,
|
|
||||||
tenantId,
|
|
||||||
} as IBillCreatingPayload);
|
|
||||||
|
|
||||||
// Inserts the bill graph object to the storage.
|
|
||||||
const bill = await Bill.query(trx).upsertGraph(billObj);
|
|
||||||
|
|
||||||
// Triggers `onBillCreated` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bill.onCreated, {
|
|
||||||
tenantId,
|
|
||||||
bill,
|
|
||||||
billId: bill.id,
|
|
||||||
trx,
|
|
||||||
} as IBillCreatedPayload);
|
|
||||||
|
|
||||||
return bill;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edits details of the given bill id with associated entries.
|
|
||||||
*
|
|
||||||
* Precedures:
|
|
||||||
* -------
|
|
||||||
* - Update the bill transaction on the storage.
|
|
||||||
* - Update the bill entries on the storage and insert the not have id and delete
|
|
||||||
* once that not presented.
|
|
||||||
* - Increment the diff amount on the given vendor id.
|
|
||||||
* - Re-write the inventory transactions.
|
|
||||||
* - Re-write the bill journal transactions.
|
|
||||||
* ------
|
|
||||||
* @param {number} tenantId - The given tenant id.
|
|
||||||
* @param {Integer} billId - The given bill id.
|
|
||||||
* @param {IBillEditDTO} billDTO - The given new bill details.
|
|
||||||
* @return {Promise<IBill>}
|
|
||||||
*/
|
|
||||||
public async editBill(
|
|
||||||
tenantId: number,
|
|
||||||
billId: number,
|
|
||||||
billDTO: IBillEditDTO,
|
|
||||||
authorizedUser: ISystemUser
|
|
||||||
): Promise<IBill> {
|
|
||||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
|
||||||
|
|
||||||
// Retrieve vendor details or throw not found service error.
|
|
||||||
const vendor = await Contact.query()
|
|
||||||
.findById(billDTO.vendorId)
|
|
||||||
.modify('vendor')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
// Validate bill number uniqiness on the storage.
|
|
||||||
if (billDTO.billNumber) {
|
|
||||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
|
|
||||||
}
|
|
||||||
// Validate the entries ids existance.
|
|
||||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
'Bill',
|
|
||||||
billDTO.entries
|
|
||||||
);
|
|
||||||
// Validate the items ids existance on the storage.
|
|
||||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
|
||||||
tenantId,
|
|
||||||
billDTO.entries
|
|
||||||
);
|
|
||||||
// Accept the purchasable items only.
|
|
||||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
|
||||||
tenantId,
|
|
||||||
billDTO.entries
|
|
||||||
);
|
|
||||||
// Transforms the bill DTO to model object.
|
|
||||||
const billObj = await this.billDTOToModel(
|
|
||||||
tenantId,
|
|
||||||
billDTO,
|
|
||||||
vendor,
|
|
||||||
authorizedUser,
|
|
||||||
oldBill
|
|
||||||
);
|
|
||||||
// Validate landed cost entries that have allocated cost could not be deleted.
|
|
||||||
await this.entriesService.validateLandedCostEntriesNotDeleted(
|
|
||||||
oldBill.entries,
|
|
||||||
billObj.entries
|
|
||||||
);
|
|
||||||
// Validate new landed cost entries should be bigger than new entries.
|
|
||||||
await this.entriesService.validateLocatedCostEntriesSmallerThanNewEntries(
|
|
||||||
oldBill.entries,
|
|
||||||
billObj.entries
|
|
||||||
);
|
|
||||||
// Edits bill transactions and associated transactions under UOW envirement.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBillEditing` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bill.onEditing, {
|
|
||||||
trx,
|
|
||||||
tenantId,
|
|
||||||
oldBill,
|
|
||||||
billDTO,
|
|
||||||
} as IBillEditingPayload);
|
|
||||||
|
|
||||||
// Update the bill transaction.
|
|
||||||
const bill = await Bill.query(trx).upsertGraph({
|
|
||||||
id: billId,
|
|
||||||
...billObj,
|
|
||||||
});
|
|
||||||
// Triggers event `onBillEdited`.
|
|
||||||
await this.eventPublisher.emitAsync(events.bill.onEdited, {
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
oldBill,
|
|
||||||
bill,
|
|
||||||
trx,
|
|
||||||
} as IBillEditedPayload);
|
|
||||||
|
|
||||||
return bill;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the bill with associated entries.
|
|
||||||
* @param {Integer} billId
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
public async deleteBill(tenantId: number, billId: number) {
|
|
||||||
const { ItemEntry, Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retrieve the given bill or throw not found error.
|
|
||||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
|
||||||
|
|
||||||
// Validate the givne bill has no associated landed cost transactions.
|
|
||||||
await this.validateBillHasNoLandedCost(tenantId, billId);
|
|
||||||
|
|
||||||
// Validate the purchase bill has no assocaited payments transactions.
|
|
||||||
await this.validateBillHasNoEntries(tenantId, billId);
|
|
||||||
|
|
||||||
// Validate the given bill has no associated reconciled with vendor credits.
|
|
||||||
await this.validateBillHasNoAppliedToCredit(tenantId, billId);
|
|
||||||
|
|
||||||
// Deletes bill transaction with associated transactions under
|
|
||||||
// unit-of-work envirement.
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
|
||||||
// Triggers `onBillDeleting` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bill.onDeleting, {
|
|
||||||
trx,
|
|
||||||
tenantId,
|
|
||||||
oldBill,
|
|
||||||
} as IBillEventDeletingPayload);
|
|
||||||
|
|
||||||
// Delete all associated bill entries.
|
|
||||||
await ItemEntry.query(trx)
|
|
||||||
.where('reference_type', 'Bill')
|
|
||||||
.where('reference_id', billId)
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
// Delete the bill transaction.
|
|
||||||
await Bill.query(trx).findById(billId).delete();
|
|
||||||
|
|
||||||
// Triggers `onBillDeleted` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.bill.onDeleted, {
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
oldBill,
|
|
||||||
trx,
|
|
||||||
} as IBIllEventDeletedPayload);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
validateBillHasNoAppliedToCredit = async (
|
|
||||||
tenantId: number,
|
|
||||||
billId: number
|
|
||||||
) => {
|
|
||||||
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const appliedTransactions = await VendorCreditAppliedBill.query().where(
|
|
||||||
'billId',
|
|
||||||
billId
|
|
||||||
);
|
|
||||||
if (appliedTransactions.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_HAS_APPLIED_TO_VENDOR_CREDIT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses bills list filter DTO.
|
|
||||||
* @param filterDTO -
|
|
||||||
*/
|
|
||||||
private parseListFilterDTO(filterDTO) {
|
|
||||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve bills data table list.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {IBillsFilter} billsFilter -
|
|
||||||
*/
|
|
||||||
public async getBills(
|
|
||||||
tenantId: number,
|
|
||||||
filterDTO: IBillsFilter
|
|
||||||
): Promise<{
|
|
||||||
bills: IBill;
|
|
||||||
pagination: IPaginationMeta;
|
|
||||||
filterMeta: IFilterMeta;
|
|
||||||
}> {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Parses bills list filter DTO.
|
|
||||||
const filter = this.parseListFilterDTO(filterDTO);
|
|
||||||
|
|
||||||
// Dynamic list service.
|
|
||||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
|
||||||
tenantId,
|
|
||||||
Bill,
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
const { results, pagination } = await Bill.query()
|
|
||||||
.onBuild((builder) => {
|
|
||||||
builder.withGraphFetched('vendor');
|
|
||||||
dynamicFilter.buildQuery()(builder);
|
|
||||||
})
|
|
||||||
.pagination(filter.page - 1, filter.pageSize);
|
|
||||||
|
|
||||||
// Tranform the bills to POJO.
|
|
||||||
const bills = await this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
results,
|
|
||||||
new PurchaseInvoiceTransformer()
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
bills,
|
|
||||||
pagination,
|
|
||||||
filterMeta: dynamicFilter.getResponseMeta(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve all due bills or for specific given vendor id.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {number} vendorId -
|
|
||||||
*/
|
|
||||||
public async getDueBills(
|
|
||||||
tenantId: number,
|
|
||||||
vendorId?: number
|
|
||||||
): Promise<IBill[]> {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const dueBills = await Bill.query().onBuild((query) => {
|
|
||||||
query.orderBy('bill_date', 'DESC');
|
|
||||||
query.modify('dueBills');
|
|
||||||
|
|
||||||
if (vendorId) {
|
|
||||||
query.where('vendor_id', vendorId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return dueBills;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the given bill details with associated items entries.
|
|
||||||
* @param {Integer} billId - Specific bill.
|
|
||||||
* @returns {Promise<IBill>}
|
|
||||||
*/
|
|
||||||
public async getBill(tenantId: number, billId: number): Promise<IBill> {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const bill = await Bill.query()
|
|
||||||
.findById(billId)
|
|
||||||
.withGraphFetched('vendor')
|
|
||||||
.withGraphFetched('entries.item')
|
|
||||||
.withGraphFetched('branch');
|
|
||||||
|
|
||||||
if (!bill) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return this.transformer.transform(
|
|
||||||
tenantId,
|
|
||||||
bill,
|
|
||||||
new PurchaseInvoiceTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark the bill as open.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} billId
|
|
||||||
*/
|
|
||||||
public async openBill(tenantId: number, billId: number): Promise<void> {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retrieve the given bill or throw not found error.
|
|
||||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
|
||||||
|
|
||||||
if (oldBill.isOpen) {
|
|
||||||
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
|
||||||
// Record the bill opened at on the storage.
|
|
||||||
await Bill.query(trx).findById(billId).patch({
|
|
||||||
openedAt: moment().toMySqlDateTime(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the inventory transactions from the given bill input.
|
|
||||||
* @param {Bill} bill - Bill model object.
|
|
||||||
* @param {number} billId - Bill id.
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async recordInventoryTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
billId: number,
|
|
||||||
override?: boolean,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
// Retireve bill with assocaited entries and allocated cost entries.
|
|
||||||
const bill = await Bill.query(trx)
|
|
||||||
.findById(billId)
|
|
||||||
.withGraphFetched('entries.allocatedCostEntries');
|
|
||||||
|
|
||||||
// Loads the inventory items entries of the given sale invoice.
|
|
||||||
const inventoryEntries =
|
|
||||||
await this.itemsEntriesService.filterInventoryEntries(
|
|
||||||
tenantId,
|
|
||||||
bill.entries
|
|
||||||
);
|
|
||||||
const transaction = {
|
|
||||||
transactionId: bill.id,
|
|
||||||
transactionType: 'Bill',
|
|
||||||
exchangeRate: bill.exchangeRate,
|
|
||||||
|
|
||||||
date: bill.billDate,
|
|
||||||
direction: 'IN',
|
|
||||||
entries: inventoryEntries,
|
|
||||||
createdAt: bill.createdAt,
|
|
||||||
|
|
||||||
warehouseId: bill.warehouseId,
|
|
||||||
};
|
|
||||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
|
||||||
tenantId,
|
|
||||||
transaction,
|
|
||||||
override,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverts the inventory transactions of the given bill id.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {number} billId - Bill id.
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
public async revertInventoryTransactions(
|
|
||||||
tenantId: number,
|
|
||||||
billId: number,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
// Deletes the inventory transactions by the given reference id and type.
|
|
||||||
await this.inventoryService.deleteInventoryTransactions(
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
'Bill',
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given vendor has no associated bills transactions.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} vendorId - Vendor id.
|
|
||||||
*/
|
|
||||||
public async validateVendorHasNoBills(tenantId: number, vendorId: number) {
|
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const bills = await Bill.query().where('vendor_id', vendorId);
|
|
||||||
|
|
||||||
if (bills.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.VENDOR_HAS_BILLS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { omit, sumBy } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import composeAsync from 'async/compose';
|
||||||
|
import { formatDateFields } from 'utils';
|
||||||
|
import {
|
||||||
|
IBillDTO,
|
||||||
|
IBill,
|
||||||
|
ISystemUser,
|
||||||
|
IVendor,
|
||||||
|
IItemEntry,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||||
|
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillDTOTransformer {
|
||||||
|
@Inject()
|
||||||
|
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the bill entries total.
|
||||||
|
* @param {IItemEntry[]} entries
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
private getBillEntriesTotal(tenantId: number, entries: IItemEntry[]): number {
|
||||||
|
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the bill landed cost amount.
|
||||||
|
* @param {IBillDTO} billDTO
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
private getBillLandedCostAmount(tenantId: number, billDTO: IBillDTO): number {
|
||||||
|
const costEntries = billDTO.entries.filter((entry) => entry.landedCost);
|
||||||
|
|
||||||
|
return this.getBillEntriesTotal(tenantId, costEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts create bill DTO to model.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillDTO} billDTO
|
||||||
|
* @param {IBill} oldBill
|
||||||
|
* @returns {IBill}
|
||||||
|
*/
|
||||||
|
public async billDTOToModel(
|
||||||
|
tenantId: number,
|
||||||
|
billDTO: IBillDTO,
|
||||||
|
vendor: IVendor,
|
||||||
|
authorizedUser: ISystemUser,
|
||||||
|
oldBill?: IBill
|
||||||
|
) {
|
||||||
|
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const amount = sumBy(billDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||||
|
|
||||||
|
// Retrieve the landed cost amount from landed cost entries.
|
||||||
|
const landedCostAmount = this.getBillLandedCostAmount(tenantId, billDTO);
|
||||||
|
|
||||||
|
// Bill number from DTO or from auto-increment.
|
||||||
|
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
||||||
|
|
||||||
|
const initialEntries = billDTO.entries.map((entry) => ({
|
||||||
|
reference_type: 'Bill',
|
||||||
|
...omit(entry, ['amount']),
|
||||||
|
}));
|
||||||
|
const entries = await composeAsync(
|
||||||
|
// Sets the default cost account to the bill entries.
|
||||||
|
this.setBillEntriesDefaultAccounts(tenantId)
|
||||||
|
)(initialEntries);
|
||||||
|
|
||||||
|
const initialDTO = {
|
||||||
|
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||||
|
'billDate',
|
||||||
|
'dueDate',
|
||||||
|
]),
|
||||||
|
amount,
|
||||||
|
landedCostAmount,
|
||||||
|
currencyCode: vendor.currencyCode,
|
||||||
|
exchangeRate: billDTO.exchangeRate || 1,
|
||||||
|
billNumber,
|
||||||
|
entries,
|
||||||
|
// Avoid rewrite the open date in edit mode when already opened.
|
||||||
|
...(billDTO.open &&
|
||||||
|
!oldBill?.openedAt && {
|
||||||
|
openedAt: moment().toMySqlDateTime(),
|
||||||
|
}),
|
||||||
|
userId: authorizedUser.id,
|
||||||
|
};
|
||||||
|
return R.compose(
|
||||||
|
this.branchDTOTransform.transformDTO(tenantId),
|
||||||
|
this.warehouseDTOTransform.transformDTO(tenantId)
|
||||||
|
)(initialDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default cost account to the bill entries.
|
||||||
|
*/
|
||||||
|
private setBillEntriesDefaultAccounts(tenantId: number) {
|
||||||
|
return async (entries: IItemEntry[]) => {
|
||||||
|
const { Item } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||||
|
const items = await Item.query().whereIn('id', entriesItemsIds);
|
||||||
|
|
||||||
|
return entries.map((entry) => {
|
||||||
|
const item = items.find((i) => i.id === entry.itemId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...entry,
|
||||||
|
...(item.type !== 'inventory' && {
|
||||||
|
costAccountId: entry.costAccountId || item.costAccountId,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import BillsService from '@/services/Purchases/Bills';
|
|
||||||
import {
|
import {
|
||||||
IBillCreatedPayload,
|
IBillCreatedPayload,
|
||||||
IBillEditedPayload,
|
IBillEditedPayload,
|
||||||
@@ -12,15 +10,12 @@ import { BillGLEntries } from './BillGLEntries';
|
|||||||
@Service()
|
@Service()
|
||||||
export class BillGLEntriesSubscriber {
|
export class BillGLEntriesSubscriber {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
private billGLEntries: BillGLEntries;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
billGLEntries: BillGLEntries;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attachs events with handles.
|
* Attachs events with handles.
|
||||||
*/
|
*/
|
||||||
attach(bus) {
|
public attach(bus) {
|
||||||
bus.subscribe(
|
bus.subscribe(
|
||||||
events.bill.onCreated,
|
events.bill.onCreated,
|
||||||
this.handlerWriteJournalEntriesOnCreate
|
this.handlerWriteJournalEntriesOnCreate
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import InventoryService from '@/services/Inventory/Inventory';
|
||||||
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillInventoryTransactions {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private itemsEntriesService: ItemsEntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private inventoryService: InventoryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the inventory transactions from the given bill input.
|
||||||
|
* @param {Bill} bill - Bill model object.
|
||||||
|
* @param {number} billId - Bill id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async recordInventoryTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
billId: number,
|
||||||
|
override?: boolean,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retireve bill with assocaited entries and allocated cost entries.
|
||||||
|
const bill = await Bill.query(trx)
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries.allocatedCostEntries');
|
||||||
|
|
||||||
|
// Loads the inventory items entries of the given sale invoice.
|
||||||
|
const inventoryEntries =
|
||||||
|
await this.itemsEntriesService.filterInventoryEntries(
|
||||||
|
tenantId,
|
||||||
|
bill.entries
|
||||||
|
);
|
||||||
|
const transaction = {
|
||||||
|
transactionId: bill.id,
|
||||||
|
transactionType: 'Bill',
|
||||||
|
exchangeRate: bill.exchangeRate,
|
||||||
|
|
||||||
|
date: bill.billDate,
|
||||||
|
direction: 'IN',
|
||||||
|
entries: inventoryEntries,
|
||||||
|
createdAt: bill.createdAt,
|
||||||
|
|
||||||
|
warehouseId: bill.warehouseId,
|
||||||
|
};
|
||||||
|
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||||
|
tenantId,
|
||||||
|
transaction,
|
||||||
|
override,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the inventory transactions of the given bill id.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} billId - Bill id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async revertInventoryTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
billId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
// Deletes the inventory transactions by the given reference id and type.
|
||||||
|
await this.inventoryService.deleteInventoryTransactions(
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
'Bill',
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ export class BillPaymentsGLEntriesRewriteSubscriber {
|
|||||||
/**
|
/**
|
||||||
* Attachs events with handles.
|
* Attachs events with handles.
|
||||||
*/
|
*/
|
||||||
attach(bus) {
|
public attach(bus) {
|
||||||
bus.subscribe(
|
bus.subscribe(
|
||||||
events.bill.onEdited,
|
events.bill.onEdited,
|
||||||
this.handlerRewritePaymentsGLOnBillEdited
|
this.handlerRewritePaymentsGLOnBillEdited
|
||||||
|
|||||||
147
packages/server/src/services/Purchases/Bills/BillsApplication.ts
Normal file
147
packages/server/src/services/Purchases/Bills/BillsApplication.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { CreateBill } from './CreateBill';
|
||||||
|
import { EditBill } from './EditBill';
|
||||||
|
import { GetBill } from './GetBill';
|
||||||
|
import { GetBills } from './GetBills';
|
||||||
|
import { DeleteBill } from './DeleteBill';
|
||||||
|
import {
|
||||||
|
IBill,
|
||||||
|
IBillDTO,
|
||||||
|
IBillEditDTO,
|
||||||
|
IBillsFilter,
|
||||||
|
IFilterMeta,
|
||||||
|
IPaginationMeta,
|
||||||
|
ISystemUser,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { GetDueBills } from './GetDueBills';
|
||||||
|
import { OpenBill } from './OpenBill';
|
||||||
|
import { GetBillPayments } from './GetBillPayments';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillsApplication {
|
||||||
|
@Inject()
|
||||||
|
private createBillService: CreateBill;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private editBillService: EditBill;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getBillService: GetBill;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getBillsService: GetBills;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private deleteBillService: DeleteBill;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getDueBillsService: GetDueBills;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private openBillService: OpenBill;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getBillPaymentsService: GetBillPayments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new bill with associated GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillDTO} billDTO
|
||||||
|
* @param {ISystemUser} authorizedUser
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public createBill(
|
||||||
|
tenantId: number,
|
||||||
|
billDTO: IBillDTO,
|
||||||
|
authorizedUser: ISystemUser
|
||||||
|
): Promise<IBill> {
|
||||||
|
return this.createBillService.createBill(tenantId, billDTO, authorizedUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the given bill with associated GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
* @param {IBillEditDTO} billDTO
|
||||||
|
* @param {ISystemUser} authorizedUser
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public editBill(
|
||||||
|
tenantId: number,
|
||||||
|
billId: number,
|
||||||
|
billDTO: IBillEditDTO,
|
||||||
|
authorizedUser: ISystemUser
|
||||||
|
): Promise<IBill> {
|
||||||
|
return this.editBillService.editBill(
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
billDTO,
|
||||||
|
authorizedUser
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given bill with associated GL entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public deleteBill(tenantId: number, billId: number) {
|
||||||
|
return this.deleteBillService.deleteBill(tenantId, billId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve bills data table list.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IBillsFilter} billsFilter -
|
||||||
|
*/
|
||||||
|
public getBills(
|
||||||
|
tenantId: number,
|
||||||
|
filterDTO: IBillsFilter
|
||||||
|
): Promise<{
|
||||||
|
bills: IBill;
|
||||||
|
pagination: IPaginationMeta;
|
||||||
|
filterMeta: IFilterMeta;
|
||||||
|
}> {
|
||||||
|
return this.getBillsService.getBills(tenantId, filterDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the given bill details.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getBill(tenantId: number, billId: number): Promise<IBill> {
|
||||||
|
return this.getBillService.getBill(tenantId, billId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the given bill.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public openBill(tenantId: number, billId: number): Promise<void> {
|
||||||
|
return this.openBillService.openBill(tenantId, billId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves due bills of the given vendor.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} vendorId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getDueBills(tenantId: number, vendorId?: number) {
|
||||||
|
return this.getDueBillsService.getDueBills(tenantId, vendorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the specific bill associated payment transactions.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
*/
|
||||||
|
public getBillPayments = async (tenantId: number, billId: number) => {
|
||||||
|
return this.getBillPaymentsService.getBillPayments(tenantId, billId);
|
||||||
|
};
|
||||||
|
}
|
||||||
154
packages/server/src/services/Purchases/Bills/BillsValidators.ts
Normal file
154
packages/server/src/services/Purchases/Bills/BillsValidators.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import { IItemEntryDTO } from '@/interfaces';
|
||||||
|
import { transformToMap } from '@/utils';
|
||||||
|
import { Bill } from '@/models';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BillsValidators {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the bill existance.
|
||||||
|
* @param {Bill | undefined | null} bill
|
||||||
|
*/
|
||||||
|
public validateBillExistance(bill: Bill | undefined | null) {
|
||||||
|
if (!bill) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the bill number existance.
|
||||||
|
*/
|
||||||
|
public async validateBillNumberExists(
|
||||||
|
tenantId: number,
|
||||||
|
billNumber: string,
|
||||||
|
notBillId?: number
|
||||||
|
) {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
const foundBills = await Bill.query()
|
||||||
|
.where('bill_number', billNumber)
|
||||||
|
.onBuild((builder) => {
|
||||||
|
if (notBillId) {
|
||||||
|
builder.whereNot('id', notBillId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundBills.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the bill has no payment entries.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} billId - Bill id.
|
||||||
|
*/
|
||||||
|
public async validateBillHasNoEntries(tenantId, billId: number) {
|
||||||
|
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retireve the bill associate payment made entries.
|
||||||
|
const entries = await BillPaymentEntry.query().where('bill_id', billId);
|
||||||
|
|
||||||
|
if (entries.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES);
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the bill number require.
|
||||||
|
* @param {string} billNo -
|
||||||
|
*/
|
||||||
|
public validateBillNoRequire(billNo: string) {
|
||||||
|
if (!billNo) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_NO_IS_REQUIRED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate bill transaction has no associated allocated landed cost transactions.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
*/
|
||||||
|
public async validateBillHasNoLandedCost(tenantId: number, billId: number) {
|
||||||
|
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const billLandedCosts = await BillLandedCost.query().where(
|
||||||
|
'billId',
|
||||||
|
billId
|
||||||
|
);
|
||||||
|
if (billLandedCosts.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_LANDED_COSTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate transaction entries that have landed cost type should not be
|
||||||
|
* inventory items.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IItemEntryDTO[]} newEntriesDTO -
|
||||||
|
*/
|
||||||
|
public async validateCostEntriesShouldBeInventoryItems(
|
||||||
|
tenantId: number,
|
||||||
|
newEntriesDTO: IItemEntryDTO[]
|
||||||
|
) {
|
||||||
|
const { Item } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const entriesItemsIds = newEntriesDTO.map((e) => e.itemId);
|
||||||
|
const entriesItems = await Item.query().whereIn('id', entriesItemsIds);
|
||||||
|
|
||||||
|
const entriesItemsById = transformToMap(entriesItems, 'id');
|
||||||
|
|
||||||
|
// Filter the landed cost entries that not associated with inventory item.
|
||||||
|
const nonInventoryHasCost = newEntriesDTO.filter((entry) => {
|
||||||
|
const item = entriesItemsById.get(entry.itemId);
|
||||||
|
|
||||||
|
return entry.landedCost && item.type !== 'inventory';
|
||||||
|
});
|
||||||
|
if (nonInventoryHasCost.length > 0) {
|
||||||
|
throw new ServiceError(
|
||||||
|
ERRORS.LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
*/
|
||||||
|
public validateBillHasNoAppliedToCredit = async (
|
||||||
|
tenantId: number,
|
||||||
|
billId: number
|
||||||
|
) => {
|
||||||
|
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const appliedTransactions = await VendorCreditAppliedBill.query().where(
|
||||||
|
'billId',
|
||||||
|
billId
|
||||||
|
);
|
||||||
|
if (appliedTransactions.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_HAS_APPLIED_TO_VENDOR_CREDIT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the given vendor has no associated bills transactions.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} vendorId - Vendor id.
|
||||||
|
*/
|
||||||
|
public async validateVendorHasNoBills(tenantId: number, vendorId: number) {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const bills = await Bill.query().where('vendor_id', vendorId);
|
||||||
|
|
||||||
|
if (bills.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.VENDOR_HAS_BILLS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
packages/server/src/services/Purchases/Bills/CreateBill.ts
Normal file
116
packages/server/src/services/Purchases/Bills/CreateBill.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
IBillDTO,
|
||||||
|
IBill,
|
||||||
|
ISystemUser,
|
||||||
|
IBillCreatedPayload,
|
||||||
|
IBillCreatingPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { BillsValidators } from './BillsValidators';
|
||||||
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
|
import { BillDTOTransformer } from './BillDTOTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreateBill {
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillsValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private itemsEntriesService: ItemsEntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformerDTO: BillDTOTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new bill and stored it to the storage.
|
||||||
|
* ----
|
||||||
|
* Precedures.
|
||||||
|
* ----
|
||||||
|
* - Insert bill transactions to the storage.
|
||||||
|
* - Insert bill entries to the storage.
|
||||||
|
* - Increment the given vendor id.
|
||||||
|
* - Record bill journal transactions on the given accounts.
|
||||||
|
* - Record bill items inventory transactions.
|
||||||
|
* ----
|
||||||
|
* @param {number} tenantId - The given tenant id.
|
||||||
|
* @param {IBillDTO} billDTO -
|
||||||
|
* @return {Promise<IBill>}
|
||||||
|
*/
|
||||||
|
public async createBill(
|
||||||
|
tenantId: number,
|
||||||
|
billDTO: IBillDTO,
|
||||||
|
authorizedUser: ISystemUser
|
||||||
|
): Promise<IBill> {
|
||||||
|
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieves the given bill vendor or throw not found error.
|
||||||
|
const vendor = await Contact.query()
|
||||||
|
.modify('vendor')
|
||||||
|
.findById(billDTO.vendorId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Validate the bill number uniqiness on the storage.
|
||||||
|
await this.validators.validateBillNumberExists(
|
||||||
|
tenantId,
|
||||||
|
billDTO.billNumber
|
||||||
|
);
|
||||||
|
// Validate items IDs existance.
|
||||||
|
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
billDTO.entries
|
||||||
|
);
|
||||||
|
// Validate non-purchasable items.
|
||||||
|
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
||||||
|
tenantId,
|
||||||
|
billDTO.entries
|
||||||
|
);
|
||||||
|
// Validates the cost entries should be with inventory items.
|
||||||
|
await this.validators.validateCostEntriesShouldBeInventoryItems(
|
||||||
|
tenantId,
|
||||||
|
billDTO.entries
|
||||||
|
);
|
||||||
|
// Transform the bill DTO to model object.
|
||||||
|
const billObj = await this.transformerDTO.billDTOToModel(
|
||||||
|
tenantId,
|
||||||
|
billDTO,
|
||||||
|
vendor,
|
||||||
|
authorizedUser
|
||||||
|
);
|
||||||
|
// Write new bill transaction with associated transactions under UOW env.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBillCreating` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onCreating, {
|
||||||
|
trx,
|
||||||
|
billDTO,
|
||||||
|
tenantId,
|
||||||
|
} as IBillCreatingPayload);
|
||||||
|
|
||||||
|
// Inserts the bill graph object to the storage.
|
||||||
|
const bill = await Bill.query(trx).upsertGraph(billObj);
|
||||||
|
|
||||||
|
// Triggers `onBillCreated` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onCreated, {
|
||||||
|
tenantId,
|
||||||
|
bill,
|
||||||
|
billId: bill.id,
|
||||||
|
trx,
|
||||||
|
} as IBillCreatedPayload);
|
||||||
|
|
||||||
|
return bill;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
80
packages/server/src/services/Purchases/Bills/DeleteBill.ts
Normal file
80
packages/server/src/services/Purchases/Bills/DeleteBill.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import {
|
||||||
|
IBIllEventDeletedPayload,
|
||||||
|
IBillEventDeletingPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { BillsValidators } from './BillsValidators';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeleteBill {
|
||||||
|
@Inject()
|
||||||
|
private validators: BillsValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the bill with associated entries.
|
||||||
|
* @param {number} billId
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
public async deleteBill(tenantId: number, billId: number) {
|
||||||
|
const { ItemEntry, Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the given bill or throw not found error.
|
||||||
|
const oldBill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries');
|
||||||
|
|
||||||
|
// Validates the bill existance.
|
||||||
|
this.validators.validateBillExistance(oldBill);
|
||||||
|
|
||||||
|
// Validate the givne bill has no associated landed cost transactions.
|
||||||
|
await this.validators.validateBillHasNoLandedCost(tenantId, billId);
|
||||||
|
|
||||||
|
// Validate the purchase bill has no assocaited payments transactions.
|
||||||
|
await this.validators.validateBillHasNoEntries(tenantId, billId);
|
||||||
|
|
||||||
|
// Validate the given bill has no associated reconciled with vendor credits.
|
||||||
|
await this.validators.validateBillHasNoAppliedToCredit(tenantId, billId);
|
||||||
|
|
||||||
|
// Deletes bill transaction with associated transactions under
|
||||||
|
// unit-of-work envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBillDeleting` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onDeleting, {
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
oldBill,
|
||||||
|
} as IBillEventDeletingPayload);
|
||||||
|
|
||||||
|
// Delete all associated bill entries.
|
||||||
|
await ItemEntry.query(trx)
|
||||||
|
.where('reference_type', 'Bill')
|
||||||
|
.where('reference_id', billId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Delete the bill transaction.
|
||||||
|
await Bill.query(trx).findById(billId).delete();
|
||||||
|
|
||||||
|
// Triggers `onBillDeleted` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onDeleted, {
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
oldBill,
|
||||||
|
trx,
|
||||||
|
} as IBIllEventDeletedPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
151
packages/server/src/services/Purchases/Bills/EditBill.ts
Normal file
151
packages/server/src/services/Purchases/Bills/EditBill.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import {
|
||||||
|
IBill,
|
||||||
|
IBillEditDTO,
|
||||||
|
IBillEditedPayload,
|
||||||
|
IBillEditingPayload,
|
||||||
|
ISystemUser,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { BillsValidators } from './BillsValidators';
|
||||||
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import EntriesService from '@/services/Entries';
|
||||||
|
import { BillDTOTransformer } from './BillDTOTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class EditBill {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillsValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private itemsEntriesService: ItemsEntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private entriesService: EntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformerDTO: BillDTOTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits details of the given bill id with associated entries.
|
||||||
|
*
|
||||||
|
* Precedures:
|
||||||
|
* -------
|
||||||
|
* - Update the bill transaction on the storage.
|
||||||
|
* - Update the bill entries on the storage and insert the not have id and delete
|
||||||
|
* once that not presented.
|
||||||
|
* - Increment the diff amount on the given vendor id.
|
||||||
|
* - Re-write the inventory transactions.
|
||||||
|
* - Re-write the bill journal transactions.
|
||||||
|
* ------
|
||||||
|
* @param {number} tenantId - The given tenant id.
|
||||||
|
* @param {Integer} billId - The given bill id.
|
||||||
|
* @param {IBillEditDTO} billDTO - The given new bill details.
|
||||||
|
* @return {Promise<IBill>}
|
||||||
|
*/
|
||||||
|
public async editBill(
|
||||||
|
tenantId: number,
|
||||||
|
billId: number,
|
||||||
|
billDTO: IBillEditDTO,
|
||||||
|
authorizedUser: ISystemUser
|
||||||
|
): Promise<IBill> {
|
||||||
|
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the given bill or throw not found error.
|
||||||
|
const oldBill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries');
|
||||||
|
|
||||||
|
// Validate bill existance.
|
||||||
|
this.validators.validateBillExistance(oldBill);
|
||||||
|
|
||||||
|
// Retrieve vendor details or throw not found service error.
|
||||||
|
const vendor = await Contact.query()
|
||||||
|
.findById(billDTO.vendorId)
|
||||||
|
.modify('vendor')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Validate bill number uniqiness on the storage.
|
||||||
|
if (billDTO.billNumber) {
|
||||||
|
await this.validators.validateBillNumberExists(
|
||||||
|
tenantId,
|
||||||
|
billDTO.billNumber,
|
||||||
|
billId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Validate the entries ids existance.
|
||||||
|
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
'Bill',
|
||||||
|
billDTO.entries
|
||||||
|
);
|
||||||
|
// Validate the items ids existance on the storage.
|
||||||
|
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
billDTO.entries
|
||||||
|
);
|
||||||
|
// Accept the purchasable items only.
|
||||||
|
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
||||||
|
tenantId,
|
||||||
|
billDTO.entries
|
||||||
|
);
|
||||||
|
// Transforms the bill DTO to model object.
|
||||||
|
const billObj = await this.transformerDTO.billDTOToModel(
|
||||||
|
tenantId,
|
||||||
|
billDTO,
|
||||||
|
vendor,
|
||||||
|
authorizedUser,
|
||||||
|
oldBill
|
||||||
|
);
|
||||||
|
// Validate landed cost entries that have allocated cost could not be deleted.
|
||||||
|
await this.entriesService.validateLandedCostEntriesNotDeleted(
|
||||||
|
oldBill.entries,
|
||||||
|
billObj.entries
|
||||||
|
);
|
||||||
|
// Validate new landed cost entries should be bigger than new entries.
|
||||||
|
await this.entriesService.validateLocatedCostEntriesSmallerThanNewEntries(
|
||||||
|
oldBill.entries,
|
||||||
|
billObj.entries
|
||||||
|
);
|
||||||
|
// Edits bill transactions and associated transactions under UOW envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onBillEditing` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onEditing, {
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
oldBill,
|
||||||
|
billDTO,
|
||||||
|
} as IBillEditingPayload);
|
||||||
|
|
||||||
|
// Update the bill transaction.
|
||||||
|
const bill = await Bill.query(trx).upsertGraph({
|
||||||
|
id: billId,
|
||||||
|
...billObj,
|
||||||
|
});
|
||||||
|
// Triggers event `onBillEdited`.
|
||||||
|
await this.eventPublisher.emitAsync(events.bill.onEdited, {
|
||||||
|
tenantId,
|
||||||
|
billId,
|
||||||
|
oldBill,
|
||||||
|
bill,
|
||||||
|
trx,
|
||||||
|
} as IBillEditedPayload);
|
||||||
|
|
||||||
|
return bill;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
42
packages/server/src/services/Purchases/Bills/GetBill.ts
Normal file
42
packages/server/src/services/Purchases/Bills/GetBill.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { IBill } from '@/interfaces';
|
||||||
|
import { BillsValidators } from './BillsValidators';
|
||||||
|
import { PurchaseInvoiceTransformer } from './PurchaseInvoiceTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetBill {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillsValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given bill details with associated items entries.
|
||||||
|
* @param {Integer} billId - Specific bill.
|
||||||
|
* @returns {Promise<IBill>}
|
||||||
|
*/
|
||||||
|
public async getBill(tenantId: number, billId: number): Promise<IBill> {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const bill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('vendor')
|
||||||
|
.withGraphFetched('entries.item')
|
||||||
|
.withGraphFetched('branch');
|
||||||
|
|
||||||
|
// Validates the bill existance.
|
||||||
|
this.validators.validateBillExistance(bill);
|
||||||
|
|
||||||
|
return this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
bill,
|
||||||
|
new PurchaseInvoiceTransformer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { BillPaymentTransactionTransformer } from './BillPayments/BillPaymentTransactionTransformer';
|
import { BillPaymentTransactionTransformer } from '../BillPayments/BillPaymentTransactionTransformer';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class BillPaymentsService {
|
export class GetBillPayments {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: TenancyService;
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
76
packages/server/src/services/Purchases/Bills/GetBills.ts
Normal file
76
packages/server/src/services/Purchases/Bills/GetBills.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
IBill,
|
||||||
|
IBillsFilter,
|
||||||
|
IFilterMeta,
|
||||||
|
IPaginationMeta,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { PurchaseInvoiceTransformer } from './PurchaseInvoiceTransformer';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetBills {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve bills data table list.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IBillsFilter} billsFilter -
|
||||||
|
*/
|
||||||
|
public async getBills(
|
||||||
|
tenantId: number,
|
||||||
|
filterDTO: IBillsFilter
|
||||||
|
): Promise<{
|
||||||
|
bills: IBill;
|
||||||
|
pagination: IPaginationMeta;
|
||||||
|
filterMeta: IFilterMeta;
|
||||||
|
}> {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses bills list filter DTO.
|
||||||
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
Bill,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
const { results, pagination } = await Bill.query()
|
||||||
|
.onBuild((builder) => {
|
||||||
|
builder.withGraphFetched('vendor');
|
||||||
|
dynamicFilter.buildQuery()(builder);
|
||||||
|
})
|
||||||
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
|
// Tranform the bills to POJO.
|
||||||
|
const bills = await this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
results,
|
||||||
|
new PurchaseInvoiceTransformer()
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
bills,
|
||||||
|
pagination,
|
||||||
|
filterMeta: dynamicFilter.getResponseMeta(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses bills list filter DTO.
|
||||||
|
* @param filterDTO -
|
||||||
|
*/
|
||||||
|
private parseListFilterDTO(filterDTO) {
|
||||||
|
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
packages/server/src/services/Purchases/Bills/GetDueBills.ts
Normal file
32
packages/server/src/services/Purchases/Bills/GetDueBills.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { IBill } from '@/interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetDueBills {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all due bills or for specific given vendor id.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} vendorId -
|
||||||
|
*/
|
||||||
|
public async getDueBills(
|
||||||
|
tenantId: number,
|
||||||
|
vendorId?: number
|
||||||
|
): Promise<IBill[]> {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const dueBills = await Bill.query().onBuild((query) => {
|
||||||
|
query.orderBy('bill_date', 'DESC');
|
||||||
|
query.modify('dueBills');
|
||||||
|
|
||||||
|
if (vendorId) {
|
||||||
|
query.where('vendor_id', vendorId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dueBills;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
packages/server/src/services/Purchases/Bills/OpenBill.ts
Normal file
46
packages/server/src/services/Purchases/Bills/OpenBill.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { BillsValidators } from './BillsValidators';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class OpenBill {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: BillsValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the bill as open.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} billId
|
||||||
|
*/
|
||||||
|
public async openBill(tenantId: number, billId: number): Promise<void> {
|
||||||
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the given bill or throw not found error.
|
||||||
|
const oldBill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries');
|
||||||
|
|
||||||
|
// Validates the bill existance.
|
||||||
|
this.validators.validateBillExistance(oldBill);
|
||||||
|
|
||||||
|
if (oldBill.isOpen) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
|
||||||
|
}
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||||
|
// Record the bill opened at on the storage.
|
||||||
|
await Bill.query(trx).findById(billId).patch({
|
||||||
|
openedAt: moment().toMySqlDateTime(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,13 +41,16 @@ export default class AllocateLandedCost extends BaseLandedCostService {
|
|||||||
allocateCostDTO: ILandedCostDTO,
|
allocateCostDTO: ILandedCostDTO,
|
||||||
billId: number
|
billId: number
|
||||||
): Promise<IBillLandedCost> => {
|
): Promise<IBillLandedCost> => {
|
||||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
const { BillLandedCost, Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Retrieve total cost of allocated items.
|
// Retrieve total cost of allocated items.
|
||||||
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
||||||
|
|
||||||
// Retrieve the purchase invoice or throw not found error.
|
// Retrieve the purchase invoice or throw not found error.
|
||||||
const bill = await this.billsService.getBillOrThrowError(tenantId, billId);
|
const bill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
// Retrieve landed cost transaction or throw not found service error.
|
// Retrieve landed cost transaction or throw not found service error.
|
||||||
const costTransaction = await this.getLandedCostOrThrowError(
|
const costTransaction = await this.getLandedCostOrThrowError(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { difference, sumBy } from 'lodash';
|
import { difference, sumBy } from 'lodash';
|
||||||
import BillsService from '../Bills';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import {
|
import {
|
||||||
IItemEntry,
|
IItemEntry,
|
||||||
@@ -13,14 +12,10 @@ import {
|
|||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import TransactionLandedCost from './TransctionLandedCost';
|
import TransactionLandedCost from './TransctionLandedCost';
|
||||||
import { ERRORS } from './utils';
|
import { ERRORS, CONFIG } from './utils';
|
||||||
import { CONFIG } from './utils';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class BaseLandedCostService {
|
export default class BaseLandedCostService {
|
||||||
@Inject()
|
|
||||||
public billsService: BillsService;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
public tenancy: HasTenancyService;
|
public tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import LandedCostSyncCostTransactions from './LandedCostSyncCostTransactions';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class LandedCostSyncCostTransactionsSubscriber {
|
export default class LandedCostSyncCostTransactionsSubscriber {
|
||||||
@Inject()
|
@Inject()
|
||||||
landedCostSyncCostTransaction: LandedCostSyncCostTransactions;
|
private landedCostSyncCostTransaction: LandedCostSyncCostTransactions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches events with handlers.
|
* Attaches events with handlers.
|
||||||
|
|||||||
@@ -9,20 +9,12 @@ import {
|
|||||||
ILandedCostTransactionEntryDOJO,
|
ILandedCostTransactionEntryDOJO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import TransactionLandedCost from './TransctionLandedCost';
|
import TransactionLandedCost from './TransctionLandedCost';
|
||||||
import BillsService from '../Bills';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import { formatNumber } from 'utils';
|
import { formatNumber } from 'utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class LandedCostTranasctions {
|
export default class LandedCostTranasctions {
|
||||||
@Inject()
|
@Inject()
|
||||||
transactionLandedCost: TransactionLandedCost;
|
private transactionLandedCost: TransactionLandedCost;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
billsService: BillsService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the landed costs based on the given query.
|
* Retrieve the landed costs based on the given query.
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import { ERRORS } from './utils';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class TransactionLandedCost {
|
export default class TransactionLandedCost {
|
||||||
@Inject()
|
@Inject()
|
||||||
billLandedCost: BillLandedCost;
|
private billLandedCost: BillLandedCost;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
expenseLandedCost: ExpenseLandedCost;
|
private expenseLandedCost: ExpenseLandedCost;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the cost transaction code model.
|
* Retrieve the cost transaction code model.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import Knex from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { sumBy } from 'lodash';
|
import { sumBy } from 'lodash';
|
||||||
import {
|
import {
|
||||||
IVendorCredit,
|
IVendorCredit,
|
||||||
@@ -9,27 +9,27 @@ import {
|
|||||||
IBill,
|
IBill,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import VendorCredit from '../BaseVendorCredit';
|
import VendorCredit from '../BaseVendorCredit';
|
||||||
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { BillPaymentValidators } from '../../BillPayments/BillPaymentValidators';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { ERRORS } from '../constants';
|
import { ERRORS } from '../constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ApplyVendorCreditToBills extends VendorCredit {
|
export default class ApplyVendorCreditToBills extends VendorCredit {
|
||||||
@Inject('PaymentReceives')
|
@Inject()
|
||||||
paymentReceive: PaymentReceiveService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
uow: UnitOfWork;
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
eventPublisher: EventPublisher;
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
billPayment: BillPaymentsService;
|
private billPaymentValidators: BillPaymentValidators;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply credit note to the given invoices.
|
* Apply credit note to the given invoices.
|
||||||
@@ -55,11 +55,12 @@ export default class ApplyVendorCreditToBills extends VendorCredit {
|
|||||||
vendorCredit
|
vendorCredit
|
||||||
);
|
);
|
||||||
// Validate bills entries existance.
|
// Validate bills entries existance.
|
||||||
const appliedBills = await this.billPayment.validateBillsExistance(
|
const appliedBills =
|
||||||
tenantId,
|
await this.billPaymentValidators.validateBillsExistance(
|
||||||
vendorCreditAppliedModel.entries,
|
tenantId,
|
||||||
vendorCredit.vendorId
|
vendorCreditAppliedModel.entries,
|
||||||
);
|
vendorCredit.vendorId
|
||||||
|
);
|
||||||
// Validate bills has remaining amount to apply.
|
// Validate bills has remaining amount to apply.
|
||||||
this.validateBillsRemainingAmount(
|
this.validateBillsRemainingAmount(
|
||||||
appliedBills,
|
appliedBills,
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import {
|
||||||
|
ISaleEstimateApprovedEvent,
|
||||||
|
ISaleEstimateApprovingEvent,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ApproveSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the sale estimate as approved from the customer.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} saleEstimateId
|
||||||
|
*/
|
||||||
|
public async approveSaleEstimate(
|
||||||
|
tenantId: number,
|
||||||
|
saleEstimateId: number
|
||||||
|
): Promise<void> {
|
||||||
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve details of the given sale estimate id.
|
||||||
|
const oldSaleEstimate = await SaleEstimate.query()
|
||||||
|
.findById(saleEstimateId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Throws error in case the sale estimate still not delivered to customer.
|
||||||
|
if (!oldSaleEstimate.isDelivered) {
|
||||||
|
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_DELIVERED);
|
||||||
|
}
|
||||||
|
// Throws error in case the sale estimate already approved.
|
||||||
|
if (oldSaleEstimate.isApproved) {
|
||||||
|
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_APPROVED);
|
||||||
|
}
|
||||||
|
// Triggers `onSaleEstimateApproving` event.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onSaleEstimateApproving` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onApproving, {
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
oldSaleEstimate,
|
||||||
|
} as ISaleEstimateApprovingEvent);
|
||||||
|
|
||||||
|
// Update estimate as approved.
|
||||||
|
const saleEstimate = await SaleEstimate.query(trx)
|
||||||
|
.where('id', saleEstimateId)
|
||||||
|
.patch({
|
||||||
|
approvedAt: moment().toMySqlDateTime(),
|
||||||
|
rejectedAt: null,
|
||||||
|
});
|
||||||
|
// Triggers `onSaleEstimateApproved` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onApproved, {
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
oldSaleEstimate,
|
||||||
|
saleEstimate,
|
||||||
|
} as ISaleEstimateApprovedEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ConvertSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts estimate to invoice.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} estimateId -
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async convertEstimateToInvoice(
|
||||||
|
tenantId: number,
|
||||||
|
estimateId: number,
|
||||||
|
invoiceId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> {
|
||||||
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve details of the given sale estimate.
|
||||||
|
const saleEstimate = await SaleEstimate.query()
|
||||||
|
.findById(estimateId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Marks the estimate as converted from the givne invoice.
|
||||||
|
await SaleEstimate.query(trx).where('id', estimateId).patch({
|
||||||
|
convertedToInvoiceId: invoiceId,
|
||||||
|
convertedToInvoiceAt: moment().toMySqlDateTime(),
|
||||||
|
});
|
||||||
|
// Triggers `onSaleEstimateConvertedToInvoice` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleEstimate.onConvertedToInvoice,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import {
|
||||||
|
ISaleEstimate,
|
||||||
|
ISaleEstimateCreatedPayload,
|
||||||
|
ISaleEstimateCreatingPayload,
|
||||||
|
ISaleEstimateDTO,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { SaleEstimateDTOTransformer } from './SaleEstimateDTOTransformer';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CreateSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private itemsEntriesService: ItemsEntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformerDTO: SaleEstimateDTOTransformer;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: SaleEstimateValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new estimate with associated entries.
|
||||||
|
* @async
|
||||||
|
* @param {number} tenantId - The tenant id.
|
||||||
|
* @param {EstimateDTO} estimate
|
||||||
|
* @return {Promise<ISaleEstimate>}
|
||||||
|
*/
|
||||||
|
public async createEstimate(
|
||||||
|
tenantId: number,
|
||||||
|
estimateDTO: ISaleEstimateDTO
|
||||||
|
): Promise<ISaleEstimate> {
|
||||||
|
const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve the given customer or throw not found service error.
|
||||||
|
const customer = await Contact.query()
|
||||||
|
.modify('customer')
|
||||||
|
.findById(estimateDTO.customerId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Transform DTO object ot model object.
|
||||||
|
const estimateObj = await this.transformerDTO.transformDTOToModel(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO,
|
||||||
|
customer
|
||||||
|
);
|
||||||
|
// Validate estimate number uniquiness on the storage.
|
||||||
|
await this.validators.validateEstimateNumberExistance(
|
||||||
|
tenantId,
|
||||||
|
estimateObj.estimateNumber
|
||||||
|
);
|
||||||
|
// Validate items IDs existance on the storage.
|
||||||
|
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO.entries
|
||||||
|
);
|
||||||
|
// Validate non-sellable items.
|
||||||
|
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO.entries
|
||||||
|
);
|
||||||
|
// Creates a sale estimate transaction with associated transactions as UOW.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onSaleEstimateCreating` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, {
|
||||||
|
estimateDTO,
|
||||||
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateCreatingPayload);
|
||||||
|
|
||||||
|
// Upsert the sale estimate graph to the storage.
|
||||||
|
const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({
|
||||||
|
...estimateObj,
|
||||||
|
});
|
||||||
|
// Triggers `onSaleEstimateCreated` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onCreated, {
|
||||||
|
tenantId,
|
||||||
|
saleEstimate,
|
||||||
|
saleEstimateId: saleEstimate.id,
|
||||||
|
saleEstimateDTO: estimateDTO,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateCreatedPayload);
|
||||||
|
|
||||||
|
return saleEstimate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import {
|
||||||
|
ISaleEstimateDeletedPayload,
|
||||||
|
ISaleEstimateDeletingPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeleteSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given estimate id with associated entries.
|
||||||
|
* @async
|
||||||
|
* @param {number} tenantId - The tenant id.
|
||||||
|
* @param {IEstimate} estimateId
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
public async deleteEstimate(
|
||||||
|
tenantId: number,
|
||||||
|
estimateId: number
|
||||||
|
): Promise<void> {
|
||||||
|
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve sale estimate or throw not found service error.
|
||||||
|
const oldSaleEstimate = await SaleEstimate.query()
|
||||||
|
.findById(estimateId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Throw error if the sale estimate converted to sale invoice.
|
||||||
|
if (oldSaleEstimate.convertedToInvoiceId) {
|
||||||
|
throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE);
|
||||||
|
}
|
||||||
|
// Deletes the estimate with associated transactions under UOW enivrement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onSaleEstimatedDeleting` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onDeleting, {
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
oldSaleEstimate,
|
||||||
|
} as ISaleEstimateDeletingPayload);
|
||||||
|
|
||||||
|
// Delete sale estimate entries.
|
||||||
|
await ItemEntry.query(trx)
|
||||||
|
.where('reference_id', estimateId)
|
||||||
|
.where('reference_type', 'SaleEstimate')
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Delete sale estimate transaction.
|
||||||
|
await SaleEstimate.query(trx).where('id', estimateId).delete();
|
||||||
|
|
||||||
|
// Triggers `onSaleEstimatedDeleted` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onDeleted, {
|
||||||
|
tenantId,
|
||||||
|
saleEstimateId: estimateId,
|
||||||
|
oldSaleEstimate,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateDeletedPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import {
|
||||||
|
ISaleEstimateEventDeliveredPayload,
|
||||||
|
ISaleEstimateEventDeliveringPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class DeliverSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the sale estimate as delivered.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} saleEstimateId - Sale estimate id.
|
||||||
|
*/
|
||||||
|
public async deliverSaleEstimate(
|
||||||
|
tenantId: number,
|
||||||
|
saleEstimateId: number
|
||||||
|
): Promise<void> {
|
||||||
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve details of the given sale estimate id.
|
||||||
|
const oldSaleEstimate = await SaleEstimate.query()
|
||||||
|
.findById(saleEstimateId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Throws error in case the sale estimate already published.
|
||||||
|
if (oldSaleEstimate.isDelivered) {
|
||||||
|
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_DELIVERED);
|
||||||
|
}
|
||||||
|
// Updates the sale estimate transaction with assocaited transactions
|
||||||
|
// under UOW envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Triggers `onSaleEstimateDelivering` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onDelivering, {
|
||||||
|
oldSaleEstimate,
|
||||||
|
trx,
|
||||||
|
tenantId,
|
||||||
|
} as ISaleEstimateEventDeliveringPayload);
|
||||||
|
|
||||||
|
// Record the delivered at on the storage.
|
||||||
|
const saleEstimate = await SaleEstimate.query(trx).patchAndFetchById(
|
||||||
|
saleEstimateId,
|
||||||
|
{
|
||||||
|
deliveredAt: moment().toMySqlDateTime(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Triggers `onSaleEstimateDelivered` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onDelivered, {
|
||||||
|
tenantId,
|
||||||
|
saleEstimate,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateEventDeliveredPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
123
packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts
Normal file
123
packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import {
|
||||||
|
ISaleEstimate,
|
||||||
|
ISaleEstimateDTO,
|
||||||
|
ISaleEstimateEditedPayload,
|
||||||
|
ISaleEstimateEditingPayload,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { SaleEstimateDTOTransformer } from './SaleEstimateDTOTransformer';
|
||||||
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class EditSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private validators: SaleEstimateValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private itemsEntriesService: ItemsEntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformerDTO: SaleEstimateDTOTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit details of the given estimate with associated entries.
|
||||||
|
* @async
|
||||||
|
* @param {number} tenantId - The tenant id.
|
||||||
|
* @param {Integer} estimateId
|
||||||
|
* @param {EstimateDTO} estimate
|
||||||
|
* @return {Promise<ISaleEstimate>}
|
||||||
|
*/
|
||||||
|
public async editEstimate(
|
||||||
|
tenantId: number,
|
||||||
|
estimateId: number,
|
||||||
|
estimateDTO: ISaleEstimateDTO
|
||||||
|
): Promise<ISaleEstimate> {
|
||||||
|
const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve details of the given sale estimate id.
|
||||||
|
const oldSaleEstimate = await SaleEstimate.query().findById(estimateId);
|
||||||
|
|
||||||
|
// Validates the given estimate existance.
|
||||||
|
this.validators.validateEstimateExistance(oldSaleEstimate);
|
||||||
|
|
||||||
|
// Retrieve the given customer or throw not found service error.
|
||||||
|
const customer = await Contact.query()
|
||||||
|
.modify('customer')
|
||||||
|
.findById(estimateDTO.customerId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Transform DTO object ot model object.
|
||||||
|
const estimateObj = await this.transformerDTO.transformDTOToModel(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO,
|
||||||
|
oldSaleEstimate,
|
||||||
|
customer
|
||||||
|
);
|
||||||
|
// Validate estimate number uniquiness on the storage.
|
||||||
|
if (estimateDTO.estimateNumber) {
|
||||||
|
await this.validators.validateEstimateNumberExistance(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO.estimateNumber,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Validate sale estimate entries existance.
|
||||||
|
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
estimateId,
|
||||||
|
'SaleEstimate',
|
||||||
|
estimateDTO.entries
|
||||||
|
);
|
||||||
|
// Validate items IDs existance on the storage.
|
||||||
|
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO.entries
|
||||||
|
);
|
||||||
|
// Validate non-sellable items.
|
||||||
|
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO.entries
|
||||||
|
);
|
||||||
|
// Edits estimate transaction with associated transactions
|
||||||
|
// under unit-of-work envirement.
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||||
|
// Trigger `onSaleEstimateEditing` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onEditing, {
|
||||||
|
tenantId,
|
||||||
|
oldSaleEstimate,
|
||||||
|
estimateDTO,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateEditingPayload);
|
||||||
|
|
||||||
|
// Upsert the estimate graph to the storage.
|
||||||
|
const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({
|
||||||
|
id: estimateId,
|
||||||
|
...estimateObj,
|
||||||
|
});
|
||||||
|
// Trigger `onSaleEstimateEdited` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onEdited, {
|
||||||
|
tenantId,
|
||||||
|
estimateId,
|
||||||
|
saleEstimate,
|
||||||
|
oldSaleEstimate,
|
||||||
|
trx,
|
||||||
|
} as ISaleEstimateEditedPayload);
|
||||||
|
|
||||||
|
return saleEstimate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { SaleEstimateTransfromer } from './SaleEstimateTransformer';
|
||||||
|
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: SaleEstimateValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the estimate details with associated entries.
|
||||||
|
* @async
|
||||||
|
* @param {number} tenantId - The tenant id.
|
||||||
|
* @param {Integer} estimateId
|
||||||
|
*/
|
||||||
|
public async getEstimate(tenantId: number, estimateId: number) {
|
||||||
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const estimate = await SaleEstimate.query()
|
||||||
|
.findById(estimateId)
|
||||||
|
.withGraphFetched('entries.item')
|
||||||
|
.withGraphFetched('customer')
|
||||||
|
.withGraphFetched('branch');
|
||||||
|
|
||||||
|
// Validates the estimate existance.
|
||||||
|
this.validators.validateEstimateExistance(estimate);
|
||||||
|
|
||||||
|
// Transformes sale estimate model to POJO.
|
||||||
|
return this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
estimate,
|
||||||
|
new SaleEstimateTransfromer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import {
|
||||||
|
IFilterMeta,
|
||||||
|
IPaginationMeta,
|
||||||
|
ISaleEstimate,
|
||||||
|
ISalesEstimatesFilter,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import { SaleEstimateDTOTransformer } from './SaleEstimateDTOTransformer';
|
||||||
|
import { SaleEstimateTransfromer } from './SaleEstimateTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetSaleEstimates {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves estimates filterable and paginated list.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IEstimatesFilter} estimatesFilter -
|
||||||
|
*/
|
||||||
|
public async getEstimates(
|
||||||
|
tenantId: number,
|
||||||
|
filterDTO: ISalesEstimatesFilter
|
||||||
|
): Promise<{
|
||||||
|
salesEstimates: ISaleEstimate[];
|
||||||
|
pagination: IPaginationMeta;
|
||||||
|
filterMeta: IFilterMeta;
|
||||||
|
}> {
|
||||||
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses filter DTO.
|
||||||
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
SaleEstimate,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
const { results, pagination } = await SaleEstimate.query()
|
||||||
|
.onBuild((builder) => {
|
||||||
|
builder.withGraphFetched('customer');
|
||||||
|
builder.withGraphFetched('entries');
|
||||||
|
dynamicFilter.buildQuery()(builder);
|
||||||
|
})
|
||||||
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
|
const transformedEstimates = await this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
results,
|
||||||
|
new SaleEstimateTransfromer()
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
salesEstimates: transformedEstimates,
|
||||||
|
pagination,
|
||||||
|
filterMeta: dynamicFilter.getResponseMeta(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the sale receipts list filter DTO.
|
||||||
|
* @param filterDTO
|
||||||
|
*/
|
||||||
|
private parseListFilterDTO(filterDTO) {
|
||||||
|
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class RejectSaleEstimate {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private uow: UnitOfWork;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the sale estimate as rejected from the customer.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} saleEstimateId
|
||||||
|
*/
|
||||||
|
public async rejectSaleEstimate(
|
||||||
|
tenantId: number,
|
||||||
|
saleEstimateId: number
|
||||||
|
): Promise<void> {
|
||||||
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve details of the given sale estimate id.
|
||||||
|
const saleEstimate = await SaleEstimate.query()
|
||||||
|
.findById(saleEstimateId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Throws error in case the sale estimate still not delivered to customer.
|
||||||
|
if (!saleEstimate.isDelivered) {
|
||||||
|
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_DELIVERED);
|
||||||
|
}
|
||||||
|
// Throws error in case the sale estimate already rejected.
|
||||||
|
if (saleEstimate.isRejected) {
|
||||||
|
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_REJECTED);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
|
// Mark the sale estimate as reject on the storage.
|
||||||
|
await SaleEstimate.query(trx).where('id', saleEstimateId).patch({
|
||||||
|
rejectedAt: moment().toMySqlDateTime(),
|
||||||
|
approvedAt: null,
|
||||||
|
});
|
||||||
|
// Triggers `onSaleEstimateRejected` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.saleEstimate.onRejected, {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user