diff --git a/.drone.yml b/.drone.yml index 6e375af..6eda51d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,38 +1,35 @@ kind: pipeline type: docker -name: build +name: server steps: - - name: build-dev - image: plugins/docker + - name: build + image: appleboy/drone-ssh settings: - registry: registry.eigen.co.id - repo: registry.eigen.co.id/eigen/${DRONE_REPO_NAME} - build_args: - - env_target=env.development - tags: latest - custom_dns: 172.10.10.16 + host: + - 172.10.10.10 + username: eigen + key: + from_secret: DEVOPS_SSH_PRIVATE_OPEN + port: 22 + script: + - cd /home/eigen/PROJECT/POS/POS.DEV/BE + - sh build.sh + - name: send-message + image: plugins/webhook + settings: + urls: https://mattermost.eigen.co.id/api/v4/posts + content_type: application/json + headers: + - Authorization=Bearer 5zubexudb38uuradfa36qy98ca + template: | + { + "channel_id": "s1ekqde1c3du5p35g6budnuotc", + "message": "Build {{repo.name}} sudah selesai" + } trigger: ref: - refs/tags/devel_* + - refs/tags/*-alpha.* event: exclude: - promote ---- -kind: pipeline -type: docker -name: deployment -steps: - - name: deployment - image: alpine - failure: ignore - commands: - - apk add --no-cache curl - - curl -X POST https://manager.sky.eigen.co.id/api/webhooks/806de7e2-1d3e-4889-b472-a59af0a5eb33 -trigger: - ref: - - refs/tags/devel_* - event: - exclude: - - promote -depends_on: - - build diff --git a/.drone.yml.old b/.drone.yml.old new file mode 100644 index 0000000..6e375af --- /dev/null +++ b/.drone.yml.old @@ -0,0 +1,38 @@ +kind: pipeline +type: docker +name: build +steps: + - name: build-dev + image: plugins/docker + settings: + registry: registry.eigen.co.id + repo: registry.eigen.co.id/eigen/${DRONE_REPO_NAME} + build_args: + - env_target=env.development + tags: latest + custom_dns: 172.10.10.16 +trigger: + ref: + - refs/tags/devel_* + event: + exclude: + - promote +--- +kind: pipeline +type: docker +name: deployment +steps: + - name: deployment + image: alpine + failure: ignore + commands: + - apk add --no-cache curl + - curl -X POST https://manager.sky.eigen.co.id/api/webhooks/806de7e2-1d3e-4889-b472-a59af0a5eb33 +trigger: + ref: + - refs/tags/devel_* + event: + exclude: + - promote +depends_on: + - build diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1961d9f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Nest Framework", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"], + "autoAttachChildProcesses": true, + "restart": true, + "sourceMaps": true, + "stopOnEntry": false, + "console": "integratedTerminal" + } + ] +} diff --git a/Dockerfile b/Dockerfile index 1aa89d7..cb09013 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,5 +9,6 @@ FROM node:18.17-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist +COPY --from=builder /app/assets ./assets COPY --from=builder /app/package.json ./package.json CMD ["node", "--max-old-space-size=8192","--max-http-header-size", "512000", "-r", "dotenv/config", "dist/main"] \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html b/assets/email-template/change-date-information.html similarity index 84% rename from src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html rename to assets/email-template/change-date-information.html index daa023e..c3827f5 100644 --- a/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html +++ b/assets/email-template/change-date-information.html @@ -4,7 +4,7 @@ - Email Ibunda + Email Confirmation + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

Thank you fot choosing us! We're absolutelty thrilled and can't wait to embark on this exciting day with you. See you soon for fun times ahead

+ +

Here's a quick recap of your invoice:

+

Booking Date: {{booking_date}}

+

Total Invoice: {{payment_total}}

+ +

Just a friendly reminder that your invoice will expire on {{expire_date}}

+

To keep things running smoothly, please ensure your payment is completed before this data

+ +

+ For your convenience, here is a list of our account details: +

    + {{#each payment_methods}} +
  • +

    {{issuer_name}} {{account_number}} a/n >{{account_name}}

    +
  • + {{/each}} +
+

+ +

Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly

+

If you have any questions or need assistance, feel free to reach out to our support team at

+ {{phone_cs}} + +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html b/assets/email-template/invoice-expired.html similarity index 88% rename from src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html rename to assets/email-template/invoice-expired.html index b1cc797..9b046ca 100644 --- a/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html +++ b/assets/email-template/invoice-expired.html @@ -4,7 +4,7 @@ - Email Ibunda + Email Confirmation + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

Thank you fot choosing us! We're absolutelty thrilled and can't wait to embark on this exciting day with you. See you soon for fun times ahead

+ +

Here's a quick recap of your invoice:

+

Booking Date: {{booking_date}}

+

Total Invoice: {{payment_total}}

+ +

Just a friendly reminder that your invoice will expire on {{expire_date}}

+

To keep things running smoothly, please ensure your payment is completed before this data

+ +

For your convenience, here is a list of our account details:

+ {{payment_midtrans_url}} +

+ +

Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly

+

If you have any questions or need assistance, feel free to reach out to our support team at

+ {{phone_cs}} + +

+ Best Regrads,
+ WEplayground +
+
+ + + + + + +
+
 
+ + + \ No newline at end of file diff --git a/assets/email-template/payment-confirmation.html b/assets/email-template/payment-confirmation.html new file mode 100644 index 0000000..e9bb48a --- /dev/null +++ b/assets/email-template/payment-confirmation.html @@ -0,0 +1,412 @@ + + + + + + + Email Confirmation + + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

We are excited to inform you that your payment has been successfully received!

+

Attached to this email, you will find your confirmatin receipt

+

Please keep this safe as you will need to show it at the entrance upon your arrival

+

It's your golden ticket to all the fun and excitement awaiting you!

+ +
+

Here's a quick recap:

+ +

Booking Date: {{booking_date}}

+

Invoice Code: {{invoice_code}}

+

Payment Date: {{payment_date}}

+

Payment Code: {{payment_code}}

+

Payment Via: {{payment_via}}

+

Account No: {{account_no}}

+

On Behalf Of: {{account_name}}

+
+ +

If you have any questions or need assistance, feel free to reach out to our support team at

+ {{phone_cs}} +
+ +

Font forget to bring a smile and your confirmation receipt (attached) for a smooth entry

+

We can't wait to see you and ensure you have an amazing time with us!

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/assets/email-template/refund-confirmation.html b/assets/email-template/refund-confirmation.html new file mode 100644 index 0000000..a938560 --- /dev/null +++ b/assets/email-template/refund-confirmation.html @@ -0,0 +1,415 @@ + + + + + + + Email Confirmation + + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

Good News!

+

We've successfully processed your refund for:

+ +

Here are the details of your refund:

+

Transaction Date: {{booking_date}}

+

Transaction Code: {{invoice_code}}

+

Total Refund: {{refund.refund_total}}

+

{{{refund_items}}}

+ +

Transaction Number: {{invoice_code}}

+

Refund Processed Date: {{refund.refund_date}}

+

Bank Account: {{refund.bank_name}}

+

Account Number: {{refund.bank_account_number}}

+

Account Name: {{refund.bank_account_name}}

+ +

We hope this helps make things right, and we're here to assist if you need anything else

+

You should see the refund in your account within 3 business days

+ + +

Thank you for your patience and understanding

+

If you have any questions or need further assistance, don't hesitate to reach out us at

+ {{phone_cs}} + +
+ +

Thank you and we can't wait to see you and make sure you have an amazing time!

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/assets/email-template/refund-request.html b/assets/email-template/refund-request.html new file mode 100644 index 0000000..1c36420 --- /dev/null +++ b/assets/email-template/refund-request.html @@ -0,0 +1,409 @@ + + + + + + + Email Confirmation + + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

We're trully sorry for any inconvenience that led to this request

+

We've received your refund request for :

+ +

Transaction Date: {{booking_date}}

+

Transaction Code: {{invoice_code}}

+

Refund Code: {{refund.code}}

+

Total Refund: {{refund.refund_total}}

+

{{{refund_items}}}

+ +

Your satisfaction is important to us, and we're commited to resolving this as quickly as possible

+

Our team is already on it and will process your refund request promptly

+

We'll keep you updated and notify you once the refund has been processed

+ +

If you have any questions or need assistance, feel free to reach out to our support team at

+ {{phone_cs}} + +
+ +

Thank you for your patience and understanding

+

We appriciate your feedback and are here to make things right

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/assets/fonts/Roboto-Black.ttf b/assets/fonts/Roboto-Black.ttf new file mode 100644 index 0000000..58fa175 Binary files /dev/null and b/assets/fonts/Roboto-Black.ttf differ diff --git a/assets/fonts/Roboto-BlackItalic.ttf b/assets/fonts/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..0a4dfd0 Binary files /dev/null and b/assets/fonts/Roboto-BlackItalic.ttf differ diff --git a/assets/fonts/Roboto-Bold.ttf b/assets/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..e64db79 Binary files /dev/null and b/assets/fonts/Roboto-Bold.ttf differ diff --git a/assets/fonts/Roboto-BoldItalic.ttf b/assets/fonts/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..5e39ae9 Binary files /dev/null and b/assets/fonts/Roboto-BoldItalic.ttf differ diff --git a/assets/fonts/Roboto-Italic.ttf b/assets/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..65498ee Binary files /dev/null and b/assets/fonts/Roboto-Italic.ttf differ diff --git a/assets/fonts/Roboto-Light.ttf b/assets/fonts/Roboto-Light.ttf new file mode 100644 index 0000000..a7e0284 Binary files /dev/null and b/assets/fonts/Roboto-Light.ttf differ diff --git a/assets/fonts/Roboto-LightItalic.ttf b/assets/fonts/Roboto-LightItalic.ttf new file mode 100644 index 0000000..867b76d Binary files /dev/null and b/assets/fonts/Roboto-LightItalic.ttf differ diff --git a/assets/fonts/Roboto-Medium.ttf b/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000..0707e15 Binary files /dev/null and b/assets/fonts/Roboto-Medium.ttf differ diff --git a/assets/fonts/Roboto-MediumItalic.ttf b/assets/fonts/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..4e3bf0d Binary files /dev/null and b/assets/fonts/Roboto-MediumItalic.ttf differ diff --git a/assets/fonts/Roboto-Regular.ttf b/assets/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..2d116d9 Binary files /dev/null and b/assets/fonts/Roboto-Regular.ttf differ diff --git a/assets/fonts/Roboto-Thin.ttf b/assets/fonts/Roboto-Thin.ttf new file mode 100644 index 0000000..ab68508 Binary files /dev/null and b/assets/fonts/Roboto-Thin.ttf differ diff --git a/assets/fonts/Roboto-ThinItalic.ttf b/assets/fonts/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..b2c3933 Binary files /dev/null and b/assets/fonts/Roboto-ThinItalic.ttf differ diff --git a/assets/image/logo.jpeg b/assets/image/logo.jpeg new file mode 100644 index 0000000..d46fdff Binary files /dev/null and b/assets/image/logo.jpeg differ diff --git a/assets/json/google-credential.json b/assets/json/google-credential.json new file mode 100644 index 0000000..2ba7c21 --- /dev/null +++ b/assets/json/google-credential.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "weplayground-app", + "private_key_id": "e3ed1a4430140ac589c6e9e7ce125d16d8f7304a", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCmEl90K7ojdx1\nnJv5BKq3THI+l+pgC4dlqOuEV4sc2SFXECgyEgEYAFH6U8eH9TTl5wW5hFhvpiWl\nHSxZA2nMa1ojp97mkufzaGgsJbcB4ni9ydoJyN9Hqs2Wz+JiBtjscGOrmOP1bNyn\nBO9RhHInh0bfTNMrtsIicr4DPNIfM2sl95v6pCDGt7Cfu2NoEnDhId50d73KVONI\n0+rf90WhehEMwoZEzYI0gLmSVbnPEm1j4/OOQfQl7FjaFKyle+A5BWaiRsIqiSue\n0jvZz0DlGmjeHx1yjBIKpq5omOku7aYi4kTNEZKKxzs5HhRFKi2KuYNK/WD5ApQg\ncIhGhhCfAgMBAAECggEANX+LmNjh9VJm/Tigkt4LFxifwgCe8WfKAhNmKHyu5K/3\nIAnzmwxjG5ee8gzNat3pfJk+dCnj7FIHwHScSB6NnCMZZXsV51sVBNC77wMxZIXA\nPyE63fzJEdlt6xvc96k9QweFB1yhs0wJ/6r2JnmcrqxcujBTUA3PIoxcG+TBOc08\ndo5Rcbeq6/3txjGlFM1820WViuFSQQiL6PgNVb+l0JrQ8rAOflKYFOkUb8wux9LX\nnD4vJMwa0j+GRvH5BCcZCguIQZn2JR3rTgcavWtcaHiTNsc49Lsj/hGGOsbkFROo\nGWaSgXE169xiVR/MMEblzqpSXq1qXF2iUeaqyUFIZQKBgQDxxrNlDs1qMfcaQ0S2\nVVtU/f1NfY+kCjQaC4CoYJaaoZINs5ODPs8/2DGnHuhNXMtnPeQ+SzNaK1e1eLbw\nmvq1+n3aGZTvUq2L3b+v7JJ6TQmQ4eBLZBzNjxrxC3EkCULTuROtsAhfzORuE0mE\nwnhR5LpPraEBrPi0re9yDDXVHQKBgQDOCwGw1gNVLh622qR65Zhx5rs2q6ktPxq2\neiUV0KDug6/7QbJzg1pNeoVQmadJR86H0fzKMsN5C7t7z3MIkqXc0+T1NmdN2fPm\ndLthnR1grCDYykoet/CITbAfiip27/o3TJ7YIYItefyZ4GnNH82R/4z3LBDnXB9f\n565hbUj76wKBgEnNMpOFijSBXgFZSU8zDPcLtNeDnWYgazkMC9DZ8v7ulOuzxjKI\n6LB/aOCvsY9z5O712IcfY2SB2HsfhxA47pDADsyVhH3tSeZo4QttdmT4wRPFrza0\nL4qbxUiRCo9KeGiylQwusM+1doEXSBjLV/j/jdOml4AwcZaNhYrVqVUNAoGAU0uD\nzXdXNZJFfGp7X+t9a155hKp05APEyswqPd1vkbzO4eY3PBd35CaJyoGzbR6IUcQE\nS8Gl4ENr8at1t5uBTfqjbrYloQVhYmMCdX3MqI4tYTa2LCD0LkYp0zZJ4Hc3Ui+5\nb2psc/ICujpMy032DvWeiTXZR46oaF8C0gQaIy0CgYEAmKCP4CXmPlWoWqebFp3W\nz2eKWUfASioQ+ZGUVNEge4a6iutciydQJZxBfg9ZXWqDfI0FoRSPfs2zUZFO0AcM\n6oaPGiFnTnH8FGcSHu3p0YysevyoSY6tgsAhb3IiKjJd4e7btsYzpPZbIfyfUVHK\nQFOOSkE+x4J5ts+XO6isQ+w=\n-----END PRIVATE KEY-----\n", + "client_email": "weplayground@weplayground-app.iam.gserviceaccount.com", + "client_id": "106351339097550564510", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/weplayground%40weplayground-app.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/env/env.development b/env/env.development index db61ef4..dd1debc 100644 --- a/env/env.development +++ b/env/env.development @@ -22,15 +22,21 @@ CRON_MIDNIGHT="55 11 * * *" CRON_EVERY_MINUTE="55 11 * * *" CRON_EVERY_HOUR="0 * * * *" -EMAIL_HOST="sandbox.smtp.mailtrap.io" +EMAIL_HOST=smtp.gmail.com EMAIL_POST=465 -EMAIL_USER="developer@eigen.co.id" -EMAIL_TOKEN="bitqkbkzjzfywxqx" +EMAIL_USER=weplayground.app@gmail.com +EMAIL_TOKEN="sonv vwiu khse vtmv" + +// nama email yang akan muncul ke user sebagai pengirim +EMAIL_SENDER=no-reply@eigen.co.id MIDTRANS_URL=https://app.sandbox.midtrans.com MIDTRANS_PRODUCTION=false -MIDTRANS_SERVER_KEY= -MIDTRANS_CLIENT_KEY= +MIDTRANS_SERVER_KEY=SB-Mid-server-kH9_RBZrTwaUkxSrC5vOVaeG +MIDTRANS_CLIENT_KEY=SB-Mid-client-7XLwqG5cgjUmZj-7 EXPORT_LIMIT_PARTITION=200 -ASSETS="https://asset.sky.eigen.co.id/" \ No newline at end of file +ASSETS="https://asset.sky.eigen.co.id/" + +GOOGLE_CALENDAR_KEY="AIzaSyCSg4P3uC9Z7kD1P4f3rf1BbBaz4Q-M55o" +GOOGLE_CALENDAR_ID="326464ac296874c7121825f5ef2e2799baa90b51da240f0045aae22beec10bd5@group.calendar.google.com" \ No newline at end of file diff --git a/env/env.production b/env/env.production index b389dbc..e9c9e82 100644 --- a/env/env.production +++ b/env/env.production @@ -22,15 +22,18 @@ CRON_MIDNIGHT="55 11 * * *" CRON_EVERY_MINUTE="55 11 * * *" CRON_EVERY_HOUR="0 * * * *" -EMAIL_HOST="sandbox.smtp.mailtrap.io" +EMAIL_HOST=smtp.gmail.com EMAIL_POST=465 -EMAIL_USER= -EMAIL_TOKEN= +EMAIL_USER=weplayground.app@gmail.com +EMAIL_TOKEN="sonv vwiu khse vtmv" MIDTRANS_URL=https://app.midtrans.com MIDTRANS_PRODUCTION=true -MIDTRANS_SERVER_KEY= -MIDTRANS_CLIENT_KEY= +MIDTRANS_SERVER_KEY=Mid-server-BZlPCcrWHDuSxW48oxBs5uAl +MIDTRANS_CLIENT_KEY=Mid-client-YhOPuo0NZPNZfiKq EXPORT_LIMIT_PARTITION=200 -ASSETS="https://asset.sky.eigen.co.id/" \ No newline at end of file +ASSETS="https://asset.sky.eigen.co.id/" + +GOOGLE_CALENDAR_KEY="AIzaSyCSg4P3uC9Z7kD1P4f3rf1BbBaz4Q-M55o" +GOOGLE_CALENDAR_ID="326464ac296874c7121825f5ef2e2799baa90b51da240f0045aae22beec10bd5@group.calendar.google.com" \ No newline at end of file diff --git a/package.json b/package.json index eeee924..b20c84e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "moment": "^2.30.1", "nano": "^10.1.3", "nodemailer": "^6.9.14", + "pdfmake": "^0.2.10", "pg": "^8.11.5", "plop": "^4.0.1", "reflect-metadata": "^0.2.0", diff --git a/src/app.module.ts b/src/app.module.ts index 08b50d9..920256f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -69,6 +69,8 @@ import { NewsModel } from './modules/web-information/news/data/models/news.model import { BannerModule } from './modules/web-information/banner/banner.module'; import { BannerModel } from './modules/web-information/banner/data/models/banner.model'; import { MailModule } from './modules/configuration/mail/mail.module'; +import { PosLogModel } from './modules/configuration/log/data/models/pos-log.model'; +import { ExportModule } from './modules/configuration/export/export.module'; @Module({ imports: [ @@ -96,6 +98,7 @@ import { MailModule } from './modules/configuration/mail/mail.module'; LogModel, NewsModel, PaymentMethodModel, + PosLogModel, RefundModel, RefundItemModel, SalesPriceFormulaModel, @@ -121,6 +124,7 @@ import { MailModule } from './modules/configuration/mail/mail.module'; CqrsModule, CouchModule, CronModule, + ExportModule, GoogleCalendarModule, LogModule, MailModule, diff --git a/src/core/guards/domain/roles.guard.ts b/src/core/guards/domain/roles.guard.ts index ab353fb..f52fa79 100644 --- a/src/core/guards/domain/roles.guard.ts +++ b/src/core/guards/domain/roles.guard.ts @@ -25,7 +25,7 @@ export class RolesGuard extends JWTGuard { if (isNotAllow) { throw new ForbiddenException({ statusCode: 10003, - message: `Forbidden Access, you don't have access to this module!`, + message: `Akses Terlarang, anda tidak punya akses ke module ini!`, error: 'ACCESS_FORBIDDEN', }); } diff --git a/src/core/guards/domain/services/privilege.service.ts b/src/core/guards/domain/services/privilege.service.ts index 6cb2917..4d891d7 100644 --- a/src/core/guards/domain/services/privilege.service.ts +++ b/src/core/guards/domain/services/privilege.service.ts @@ -52,7 +52,7 @@ export class PrivilegeService { if (!moduleKey) { throw new ForbiddenException({ statusCode: 10005, - message: `Forbidden Access, access Module is Require!`, + message: `Akses Terlarang, anda tidak punya akses ke module ini!`, error: 'MODULE_KEY_NOT_FOUND', }); } diff --git a/src/core/helpers/query/check-duplicate.helpers.ts b/src/core/helpers/query/check-duplicate.helpers.ts index 2faf617..cdc1207 100644 --- a/src/core/helpers/query/check-duplicate.helpers.ts +++ b/src/core/helpers/query/check-duplicate.helpers.ts @@ -43,9 +43,9 @@ export class CheckDuplicateHelper { if (data_exists > 0) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Entity with ${columnCheck.column} : ${ + message: `Gagal! Data dengan ${columnCheck.column} : ${ this.entity[columnCheck.column] - } already exist`, + } telah ada`, error: 'Unprocessable Entity', }); } diff --git a/src/core/helpers/query/specific-search.helper.ts b/src/core/helpers/query/specific-search.helper.ts index 6ad02d0..4e8d463 100644 --- a/src/core/helpers/query/specific-search.helper.ts +++ b/src/core/helpers/query/specific-search.helper.ts @@ -20,12 +20,16 @@ export class SpecificSearchFilter { new Brackets((qb) => { params.forEach((param) => { const { cols, data, additional, leftJoin } = param; + const columns = cols.split('.'); + let arr = data; - const arr = data?.map((el) => - el.includes("'") - ? `'%${el.trim().replace(/'/g, "''").replace(/\s+/g, ' ')}%'` - : `'%${el.trim().replace(/\s+/g, ' ')}%'`, - ); + if (!columns.includes('status::text')) { + arr = data?.map((el) => + el.includes("'") + ? `'%${el.trim().replace(/'/g, "''").replace(/\s+/g, ' ')}%'` + : `'%${el.trim().replace(/\s+/g, ' ')}%'`, + ); + } const aliases = !cols.match(/\./g) ? this.table.concat(`.${cols}`) diff --git a/src/core/modules/data/service/base-data.service.ts b/src/core/modules/data/service/base-data.service.ts index e60c841..f4eb4c3 100644 --- a/src/core/modules/data/service/base-data.service.ts +++ b/src/core/modules/data/service/base-data.service.ts @@ -17,8 +17,8 @@ export abstract class BaseDataService { entityTarget: EntityTarget, entity: Entity, ): Promise { - const newEntity = queryRunner.manager.create(entityTarget, entity); - return await queryRunner.manager.save(newEntity); + // const newEntity = this.repository.create(entityTarget, entity); + return await this.repository.save(entity); } async createMany( @@ -26,8 +26,8 @@ export abstract class BaseDataService { entityTarget: EntityTarget, entity: Entity[], ): Promise { - const newEntity = queryRunner.manager.create(entityTarget, entity); - return await queryRunner.manager.save(newEntity); + // const newEntity = this.repository.create(entityTarget, entity); + return await this.repository.save(entity); } async createBatch( @@ -35,8 +35,8 @@ export abstract class BaseDataService { entityTarget: EntityTarget, entity: Entity[], ): Promise { - const newEntity = queryRunner.manager.create(entityTarget, entity); - return await queryRunner.manager.save(newEntity); + // const newEntity = this.repository.create(entityTarget, entity); + return await this.repository.save(entity); } async update( @@ -45,13 +45,13 @@ export abstract class BaseDataService { filterUpdate: any, entity: Entity, ): Promise { - const newEntity = await queryRunner.manager.findOne(entityTarget, { + const newEntity = await this.repository.findOne({ where: filterUpdate, }); if (!newEntity) throw new Error('Data not found!'); Object.assign(newEntity, entity); - return await queryRunner.manager.save(newEntity); + return await this.repository.save(newEntity); } async deleteById( @@ -59,7 +59,15 @@ export abstract class BaseDataService { entityTarget: EntityTarget, id: string, ): Promise { - await queryRunner.manager.delete(entityTarget, { id }); + await this.repository.delete(id); + } + + async deleteByIds( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + ids: string[], + ): Promise { + await this.repository.delete(ids); } async deleteByOptions( @@ -67,11 +75,8 @@ export abstract class BaseDataService { entityTarget: EntityTarget, findManyOptions: FindManyOptions, ): Promise { - const datas = await queryRunner.manager.find(entityTarget, findManyOptions); - await queryRunner.manager.delete( - entityTarget, - datas?.map((item) => item['id']), - ); + const datas = await this.repository.find(findManyOptions); + await this.repository.delete(datas?.map((item) => item['id'])); } async getOneByOptions(findOneOptions): Promise { diff --git a/src/core/modules/domain/entities/base-core.entity.ts b/src/core/modules/domain/entities/base-core.entity.ts index caf7b15..a2b2ce5 100644 --- a/src/core/modules/domain/entities/base-core.entity.ts +++ b/src/core/modules/domain/entities/base-core.entity.ts @@ -1,3 +1,3 @@ export interface BaseCoreEntity { - id: string; + id?: string; } diff --git a/src/core/modules/domain/entities/base-filter.entity.ts b/src/core/modules/domain/entities/base-filter.entity.ts index 357412d..c447c41 100644 --- a/src/core/modules/domain/entities/base-filter.entity.ts +++ b/src/core/modules/domain/entities/base-filter.entity.ts @@ -22,6 +22,7 @@ export interface Param { data: string[]; additional?: any[]; leftJoin?: any[]; + isStatus?: boolean; } export interface RelationParam { diff --git a/src/core/modules/domain/usecase/base.manager.ts b/src/core/modules/domain/usecase/base.manager.ts index 73b77a0..0afe79f 100644 --- a/src/core/modules/domain/usecase/base.manager.ts +++ b/src/core/modules/domain/usecase/base.manager.ts @@ -82,11 +82,11 @@ export abstract class BaseManager { this.baseLog.verbose('commitTransaction'); await this.queryRunner.commitTransaction(); - - await this.queryRunner.release(); } catch (e) { if (e.response) throw new Error(JSON.stringify(e.response)); else throw new Error(e.message); + } finally { + await this.queryRunner.release(); } } diff --git a/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts index 292c45d..acf3dc3 100644 --- a/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts @@ -38,7 +38,7 @@ export abstract class BaseBatchDeleteManager extends BaseManager { if (!entity) { throw new NotFoundException({ statusCode: HttpStatus.NOT_FOUND, - message: `Failed! Entity with id ${id} not found`, + message: `Gagal! data dengan id ${id} tidak ditemukan`, error: 'Entity Not Found', }); } diff --git a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts index 4125276..503b486 100644 --- a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts @@ -15,6 +15,26 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { abstract get entityTarget(): any; setData(ids: string[], status: STATUS): void { + /** + * // TODO: Handle case confirm multiple tabs; + * Pola ids yang dikirim dirubah menjadi data_id___updated_at + * Untuk mendapatkan value id nya saja dan menghindari breaking change + * karena sudah digunakan sebelumnya lakukan split dari data ids yang dikirim + * Example: + * this.dataIds = ids.map((i)=> { + * return i.split('___')[0] + * }) + * + * Simpan data ids yang mempunyai update_at kedalam valiable baru + * Example: + * this.dataIdsWithDate = ids.map((i)=> { + * return { + * id: i.split('___')[0], + * updated_at: i.split('___')[1] + * } + * }) + */ + this.dataIds = ids; this.dataStatus = status; } @@ -32,8 +52,21 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { let totalSuccess = 0; const messages = []; + /** + * // TODO: Handle case confirm multiple tabs; + * Lopping data diambil dari dataIdsWithDate + * exp: for (const item of this.dataIdsWithDate) + */ + for (const id of this.dataIds) { try { + /** + * // TODO: Handle case confirm multiple tabs; + * buat variable: + * const id = item.id + * const updated_at = item.updated_at + */ + const entity = await this.dataService.getOneByOptions({ where: { id: id, @@ -44,19 +77,19 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { if (!entity) { throw new NotFoundException({ statusCode: HttpStatus.NOT_FOUND, - message: `Failed! Entity with id ${id} not found`, + message: `Gagal! data dengan id ${id} tidak ditemukan`, error: 'Entity Not Found', }); } this.oldData = _.cloneDeep(entity); + await this.validateData(entity); + Object.assign(entity, { status: this.dataStatus, editor_id: this.user.id, editor_name: this.user.name, updated_at: new Date().getTime(), }); - - await this.validateData(entity); await new ValidateRelationHelper( id, this.dataService, @@ -64,6 +97,14 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { this.tableName, ).execute(); + /** + * // TODO: Handle case confirm multiple tabs; + * lakukan update data dengan where condition id dan updated_at + * EXPECTATION => status akan berubah jika updated_at yang dikirim dari FE sama dengen yang di database + * IF => updated_at beda tidak perlu melakukan update status dan tidak perlu memanggil eventBus tetapi tetap dihitung sebagai aksi yang berhasil + * IF => FE tidak menambahkan updated_at makan lakukan update dan publishEvent + */ + const result = await this.dataService.update( this.queryRunner, this.entityTarget, diff --git a/src/core/modules/domain/usecase/managers/base-change-position.manager.ts b/src/core/modules/domain/usecase/managers/base-change-position.manager.ts index 5849753..2b911bc 100644 --- a/src/core/modules/domain/usecase/managers/base-change-position.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-change-position.manager.ts @@ -29,7 +29,7 @@ export abstract class BaseChangePosition extends BaseManager { if (!this.data?.end || this.data.start == this.data?.end) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: 'Please drag to another position', + message: 'Gagal! tolong pindahkan ke posisi lain', error: 'Unprocessable Entity', }); } @@ -43,7 +43,7 @@ export abstract class BaseChangePosition extends BaseManager { if (!this.startData) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Entity with id : ${this.data.start} not found`, + message: `Gagal! data dengan id : ${this.data.start} tidak ditemukan`, error: 'Unprocessable Entity', }); } @@ -57,7 +57,7 @@ export abstract class BaseChangePosition extends BaseManager { if (!this.endData) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Entity with id : ${this.data.end} not found`, + message: `Gagal! data dengan id : ${this.data.end} tidak ditemukan`, error: 'Unprocessable Entity', }); } diff --git a/src/core/modules/domain/usecase/managers/base-create.manager.ts b/src/core/modules/domain/usecase/managers/base-create.manager.ts index 92f0019..79bded9 100644 --- a/src/core/modules/domain/usecase/managers/base-create.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-create.manager.ts @@ -97,7 +97,7 @@ export abstract class BaseCreateManager extends BaseManager { this.eventBus.publishAll([ new topic.topic({ - id: data?.['id'] ?? topic?.data?.['id'], + id: this.result['id'], old: null, data: data ?? topic.data, user: this.user, diff --git a/src/core/modules/domain/usecase/managers/base-custom.manager.ts b/src/core/modules/domain/usecase/managers/base-custom.manager.ts index 58215f2..f7f1336 100644 --- a/src/core/modules/domain/usecase/managers/base-custom.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-custom.manager.ts @@ -1,5 +1,6 @@ import { validateRelations } from 'src/core/strings/constants/interface.constants'; import { BaseManager } from '../base.manager'; +import { OPERATION } from 'src/core/strings/constants/base.constants'; export abstract class BaseCustomManager extends BaseManager { protected result: any; @@ -23,4 +24,31 @@ export abstract class BaseCustomManager extends BaseManager { } abstract getResult(): any; + + async publishEvents() { + if (!this.eventTopics.length) return; + for (const topic of this.eventTopics) { + let data; + if (!topic.data) { + data = await this.dataService.getOneByOptions({ + where: { + id: this.result['id'], + }, + relations: topic.relations, + }); + } + + this.eventBus.publishAll([ + new topic.topic({ + id: data?.['id'] ?? topic?.data?.['id'], + old: null, + data: data ?? topic.data, + user: this.user, + description: '', + module: this.tableName, + op: OPERATION.UPDATE, + }), + ]); + } + } } diff --git a/src/core/modules/domain/usecase/managers/base-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-delete.manager.ts index 3f6e484..62719d7 100644 --- a/src/core/modules/domain/usecase/managers/base-delete.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-delete.manager.ts @@ -23,7 +23,7 @@ export abstract class BaseDeleteManager extends BaseManager { if (!this.data) throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Data with id ${this.dataId} not found`, + message: `Gagal! Data denga id ${this.dataId} tidak ditemukan`, error: 'Unprocessable Entity', }); diff --git a/src/core/modules/domain/usecase/managers/base-index.manager.ts b/src/core/modules/domain/usecase/managers/base-index.manager.ts index 89c2f54..fb5ece5 100644 --- a/src/core/modules/domain/usecase/managers/base-index.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-index.manager.ts @@ -50,12 +50,16 @@ export abstract class BaseIndexManager extends BaseReadManager { // jika searching status terdapat dalam enum, maka dia mencari specific data // ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut - return STATUS[statusData.toUpperCase()] ?? statusData; - }); - specificFilter.push({ - cols: `${this.tableName}.status::text`, - data: data, + return `'${STATUS[statusData.toUpperCase()]}'` ?? `'%${statusData}%'`; }); + + const exist = specificFilter.find((item) => item.isStatus); + if (!exist) { + specificFilter.push({ + cols: `${this.tableName}.status::text`, + data: data, + }); + } } new SpecificSearchFilter( diff --git a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts index 3e4e0a9..64516db 100644 --- a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts @@ -14,6 +14,22 @@ export abstract class BaseUpdateStatusManager extends BaseManager { abstract get entityTarget(): any; setData(id: string, status: STATUS): void { + /** + * // TODO: Handle case confirm multiple tabs; + * Pola id yang dikirim dirubah menjadi data_id___updated_at + * Untuk mendapatkan value id nya saja dan menghindari breaking change + * karena sudah digunakan sebelumnya lakukan split dari data ids yang dikirim + * Example: + * this.dataId = id.split('___')[0] + * + * Simpan data id yang mempunyai update_at kedalam valiable baru + * Example: + * this.dataIdsWithDate = { + * id: id.split('___')[0], + * updated_at: id.split('___')[1] + * } + */ + this.dataId = id; this.dataStatus = status; } @@ -43,6 +59,16 @@ export abstract class BaseUpdateStatusManager extends BaseManager { this.tableName, ).execute(); + /** + * // TODO: Handle case confirm multiple tabs; + * IF => updated_at sama dengen data yang di database + * THEN => + * - Lakukan update data dengan where condition id dan updated_at + * - EXPECTATION = > status akan berubah jika updated_at yang dikirim dari FE sama dengen yang di database + * IF => updated_at beda maka retun curent data tanpa harus malakukan update status dan publish event + * IF => FE tidak menambahkan updated_at makan lakukan update dan publishEvent + */ + this.result = await this.dataService.update( this.queryRunner, this.entityTarget, diff --git a/src/core/modules/domain/usecase/managers/base-update.manager.ts b/src/core/modules/domain/usecase/managers/base-update.manager.ts index 29468e0..c3d27a1 100644 --- a/src/core/modules/domain/usecase/managers/base-update.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update.manager.ts @@ -27,7 +27,7 @@ export abstract class BaseUpdateManager extends BaseManager { if (!this.oldData) { throw new NotFoundException({ statusCode: HttpStatus.NOT_FOUND, - message: `Failed! Entity with id ${this.dataId} not found`, + message: `Gagal! Data denga id ${this.dataId} tidak ditemukan`, error: 'Entity Not Found', }); } @@ -102,7 +102,7 @@ export abstract class BaseUpdateManager extends BaseManager { this.eventBus.publishAll([ new topic.topic({ - id: data?.['id'] ?? topic?.data?.['id'], + id: topic.data?.['id'] ?? this.dataId, old: this.oldData, data: data ?? topic.data, user: this.user, diff --git a/src/core/strings/constants/base.constants.ts b/src/core/strings/constants/base.constants.ts index 0ce2413..0e1f7d4 100644 --- a/src/core/strings/constants/base.constants.ts +++ b/src/core/strings/constants/base.constants.ts @@ -20,6 +20,16 @@ export enum ORDER_TYPE { DESC = 'DESC', } +export const DAY = [ + 'minggu', + 'senin', + 'selasa', + 'rabu', + 'kamis', + 'jumat', + 'sabtu', +]; + export enum CONNECTION_NAME { DEFAULT = 'default', } diff --git a/src/core/strings/constants/privilege.constants.ts b/src/core/strings/constants/privilege.constants.ts index 2018bea..7fe0853 100644 --- a/src/core/strings/constants/privilege.constants.ts +++ b/src/core/strings/constants/privilege.constants.ts @@ -51,7 +51,6 @@ export const PrivilegeAdminConstant = [ menu_label: 'Rekonsiliasi', actions: [ PrivilegeAction.VIEW, - PrivilegeAction.CREATE, PrivilegeAction.CONFIRM, PrivilegeAction.DELETE, PrivilegeAction.CANCEL, @@ -137,6 +136,12 @@ export const PrivilegeAdminConstant = [ actions: [PrivilegeAction.CREATE], index: 13, }, + { + menu: 'DOWNLOAD_POS_APP', + menu_label: 'Download POS App', + actions: [PrivilegeAction.VIEW], + index: 20, + }, ]; export const PrivilegePOSConstant = [ @@ -146,8 +151,8 @@ export const PrivilegePOSConstant = [ actions: [ PrivilegeAction.VIEW, PrivilegeAction.CREATE, - PrivilegeAction.DELETE, PrivilegeAction.EDIT, + PrivilegeAction.CANCEL, ], index: 14, }, @@ -160,7 +165,7 @@ export const PrivilegePOSConstant = [ { menu: 'BOOKING', menu_label: 'Pemesanan', - actions: [PrivilegeAction.VIEW, PrivilegeAction.CREATE], + actions: [PrivilegeAction.VIEW], index: 16, }, { @@ -175,4 +180,10 @@ export const PrivilegePOSConstant = [ actions: [PrivilegeAction.CREATE], index: 18, }, + { + menu: 'PRINT_RECEIPT', + menu_label: 'Print Receipt', + actions: [PrivilegeAction.CREATE], + index: 19, + }, ]; diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 8c14c7d..7950937 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -7,6 +7,7 @@ export enum TABLE_NAME { ITEM_RATE = 'item_rates', GATE = 'gates', LOG = 'logs', + LOG_POS = 'logs_pos', NEWS = 'news', PAYMENT_METHOD = 'payment_methods', PRICE_FORMULA = 'price_formulas', diff --git a/src/database/migrations/1721736523991-pos-log.ts b/src/database/migrations/1721736523991-pos-log.ts new file mode 100644 index 0000000..5f89518 --- /dev/null +++ b/src/database/migrations/1721736523991-pos-log.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class PosLog1721736523991 implements MigrationInterface { + name = 'PosLog1721736523991'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "logs_pos" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" character varying NOT NULL DEFAULT 'cash withdrawal', "pos_number" bigint, "total_balance" numeric, "created_at" bigint, "creator_name" character varying, "creator_id" character varying, CONSTRAINT "PK_60df825558a6b6881d7ad770d26" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "logs_pos"`); + } +} diff --git a/src/database/migrations/1721892389807-add-calendar-column-transaction.ts b/src/database/migrations/1721892389807-add-calendar-column-transaction.ts new file mode 100644 index 0000000..fbd387d --- /dev/null +++ b/src/database/migrations/1721892389807-add-calendar-column-transaction.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCalendarColumnTransaction1721892389807 + implements MigrationInterface +{ + name = 'AddCalendarColumnTransaction1721892389807'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ADD "calendar_id" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "calendar_link" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "calendar_id"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "calendar_link"`, + ); + } +} diff --git a/src/database/migrations/1722318939681-add-column-to-refund-table.ts b/src/database/migrations/1722318939681-add-column-to-refund-table.ts new file mode 100644 index 0000000..c731b00 --- /dev/null +++ b/src/database/migrations/1722318939681-add-column-to-refund-table.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnToRefundTable1722318939681 implements MigrationInterface { + name = 'AddColumnToRefundTable1722318939681'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."refunds_refund_reason_type_enum" AS ENUM('weather', 'ride malfunction', 'other')`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ADD "refund_reason_type" "public"."refunds_refund_reason_type_enum" NOT NULL DEFAULT 'ride malfunction'`, + ); + await queryRunner.query(`ALTER TABLE "refunds" ADD "refund_reason" text`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refunds" DROP COLUMN "refund_reason"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" DROP COLUMN "refund_reason_type"`, + ); + await queryRunner.query( + `DROP TYPE "public"."refunds_refund_reason_type_enum"`, + ); + } +} diff --git a/src/database/migrations/1722334034920-update-column-to-transaction-table.ts b/src/database/migrations/1722334034920-update-column-to-transaction-table.ts new file mode 100644 index 0000000..64a329b --- /dev/null +++ b/src/database/migrations/1722334034920-update-column-to-transaction-table.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateColumnToTransactionTable1722334034920 + implements MigrationInterface +{ + name = 'UpdateColumnToTransactionTable1722334034920'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "qr_image_url" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_code" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_code"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "qr_image_url"`, + ); + } +} diff --git a/src/database/migrations/1722509262047-pos_log_add_column_down_by.ts b/src/database/migrations/1722509262047-pos_log_add_column_down_by.ts new file mode 100644 index 0000000..e1e35fc --- /dev/null +++ b/src/database/migrations/1722509262047-pos_log_add_column_down_by.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class PosLogAddColumnDownBy1722509262047 implements MigrationInterface { + name = 'PosLogAddColumnDownBy1722509262047'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "logs_pos" ADD "drawn_by_name" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "logs_pos" ADD "drawn_by_id" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "logs_pos" DROP COLUMN "drawn_by_id"`); + await queryRunner.query( + `ALTER TABLE "logs_pos" DROP COLUMN "drawn_by_name"`, + ); + } +} diff --git a/src/database/migrations/1722581313837-update-relation-table-transaction.ts b/src/database/migrations/1722581313837-update-relation-table-transaction.ts new file mode 100644 index 0000000..8fa4911 --- /dev/null +++ b/src/database/migrations/1722581313837-update-relation-table-transaction.ts @@ -0,0 +1,49 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateRelationTableTransaction1722581313837 + implements MigrationInterface +{ + name = 'UpdateRelationTableTransaction1722581313837'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refunds" DROP CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" DROP CONSTRAINT "REL_8bb3b7579f49990d2e77684acd"`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3"`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "REL_07b481a163c219f5de8fb1c90b"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ADD CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3" FOREIGN KEY ("transaction_item_id") REFERENCES "transaction_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" DROP CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4"`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "REL_07b481a163c219f5de8fb1c90b" UNIQUE ("transaction_item_id")`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3" FOREIGN KEY ("transaction_item_id") REFERENCES "transaction_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ADD CONSTRAINT "REL_8bb3b7579f49990d2e77684acd" UNIQUE ("transaction_id")`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ADD CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } +} diff --git a/src/database/migrations/1722587128195-update-type-column-item-table.ts b/src/database/migrations/1722587128195-update-type-column-item-table.ts new file mode 100644 index 0000000..d19e50d --- /dev/null +++ b/src/database/migrations/1722587128195-update-type-column-item-table.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTypeColumnItemTable1722587128195 + implements MigrationInterface +{ + name = 'UpdateTypeColumnItemTable1722587128195'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "items" DROP COLUMN "sales_margin"`); + await queryRunner.query(`ALTER TABLE "items" ADD "sales_margin" numeric`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "items" DROP COLUMN "sales_margin"`); + await queryRunner.query(`ALTER TABLE "items" ADD "sales_margin" integer`); + } +} diff --git a/src/database/migrations/1722595038215-update-table-transaction.ts b/src/database/migrations/1722595038215-update-table-transaction.ts new file mode 100644 index 0000000..f4a69bc --- /dev/null +++ b/src/database/migrations/1722595038215-update-table-transaction.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTableTransaction1722595038215 implements MigrationInterface { + name = 'UpdateTableTransaction1722595038215'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."transactions_payment_type_counter_enum" AS ENUM('midtrans', 'bank transfer', 'qris', 'counter', 'cash', 'credit card', 'debit', 'e-money')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_type_counter" "public"."transactions_payment_type_counter_enum"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_type_counter"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_payment_type_counter_enum"`, + ); + } +} diff --git a/src/database/migrations/1722693550579-add-column-to-transactions-table.ts b/src/database/migrations/1722693550579-add-column-to-transactions-table.ts new file mode 100644 index 0000000..a80690a --- /dev/null +++ b/src/database/migrations/1722693550579-add-column-to-transactions-table.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnToTransactionsTable1722693550579 + implements MigrationInterface +{ + name = 'AddColumnToTransactionsTable1722693550579'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ADD "booking_date_before" date`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "booking_date_before"`, + ); + } +} diff --git a/src/database/migrations/1722922766205-unique_name_item.ts b/src/database/migrations/1722922766205-unique_name_item.ts new file mode 100644 index 0000000..b9e0430 --- /dev/null +++ b/src/database/migrations/1722922766205-unique_name_item.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UniqueNameItem1722922766205 implements MigrationInterface { + name = 'UniqueNameItem1722922766205'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "item_bundlings" DROP CONSTRAINT "FK_a50e7abf2caba4d0394f3726b86"`, + ); + await queryRunner.query( + `ALTER TABLE "items" ADD CONSTRAINT "UQ_213736582899b3599acaade2cd1" UNIQUE ("name")`, + ); + await queryRunner.query( + `ALTER TABLE "item_bundlings" ADD CONSTRAINT "FK_a50e7abf2caba4d0394f3726b86" FOREIGN KEY ("item_bundling_id") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "item_bundlings" DROP CONSTRAINT "FK_a50e7abf2caba4d0394f3726b86"`, + ); + await queryRunner.query( + `ALTER TABLE "items" DROP CONSTRAINT "UQ_213736582899b3599acaade2cd1"`, + ); + await queryRunner.query( + `ALTER TABLE "item_bundlings" ADD CONSTRAINT "FK_a50e7abf2caba4d0394f3726b86" FOREIGN KEY ("item_bundling_id") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } +} diff --git a/src/modules/configuration/auth/domain/managers/login.manager.ts b/src/modules/configuration/auth/domain/managers/login.manager.ts index ea58a86..d6ab1b3 100644 --- a/src/modules/configuration/auth/domain/managers/login.manager.ts +++ b/src/modules/configuration/auth/domain/managers/login.manager.ts @@ -133,7 +133,7 @@ export class LoginManager extends BaseCustomManager { throwError() { throw new UnauthorizedException({ statusCode: HttpStatus.UNAUTHORIZED, - message: `Failed! You have entered an invalid username or password`, + message: `Gagal! username atau password tidak sesuai`, error: 'Unauthorized', }); } diff --git a/src/modules/configuration/constant/infrastructure/constant.controller.ts b/src/modules/configuration/constant/infrastructure/constant.controller.ts index a388236..06a851b 100644 --- a/src/modules/configuration/constant/infrastructure/constant.controller.ts +++ b/src/modules/configuration/constant/infrastructure/constant.controller.ts @@ -5,8 +5,12 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; import { ItemType } from 'src/modules/item-related/item-category/constants'; import { LimitType } from 'src/modules/item-related/item/constants'; import { PaymentMethodType } from 'src/modules/transaction/payment-method/constants'; -import { RefundType } from 'src/modules/transaction/refund/constants'; +import { + RefundReasonType, + RefundType, +} from 'src/modules/transaction/refund/constants'; import { GateType } from 'src/modules/web-information/gate/constants'; +import { InvoiceType } from '../../export/constants'; @ApiTags('configuration - constant') @Controller('v1/constant') @@ -52,8 +56,18 @@ export class ConstantController { return Object.values(RefundType); } + @Get('refund-reason-type') + async refundReasonType(): Promise { + return Object.values(RefundReasonType); + } + @Get('gate-type') async gateType(): Promise { return Object.values(GateType); } + + @Get('invoice-type') + async invoiceType(): Promise { + return Object.values(InvoiceType); + } } diff --git a/src/modules/configuration/couch/constants.ts b/src/modules/configuration/couch/constants.ts index 031c3c4..8171752 100644 --- a/src/modules/configuration/couch/constants.ts +++ b/src/modules/configuration/couch/constants.ts @@ -1 +1,6 @@ -export const DatabaseListen = ['transaction', 'vip_code']; +export const DatabaseListen = [ + 'transaction', + 'vip_code', + 'pos_activity', + 'pos_cash_activity', +]; diff --git a/src/modules/configuration/couch/couch.module.ts b/src/modules/configuration/couch/couch.module.ts index e3a4b95..8de39ac 100644 --- a/src/modules/configuration/couch/couch.module.ts +++ b/src/modules/configuration/couch/couch.module.ts @@ -17,10 +17,13 @@ import { } from './domain/managers/season-period.handler'; import { ItemDeletedHandler, + ItemPriceUpdatedHandler, + ItemRateUpdatedHandler, ItemUpdatedHandler, } from './domain/managers/item.handler'; import { UserDeletedHandler, + UserPrivilegeUpdateHandler, UserUpdatedHandler, } from './domain/managers/user.handler'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -31,13 +34,22 @@ import { UserDataService } from 'src/modules/user-related/user/data/services/use import { ItemDataService } from 'src/modules/item-related/item/data/services/item-data.service'; import { BookingDeletedEvent, - BookingHandler, + // BookingHandler, + BookingUpdateHandler, + ChangeStatusBookingHandler, } from './domain/managers/booking.handler'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model'; import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; import { VipCodeCreatedHandler } from './domain/managers/vip-code.handler'; +import { ItemRateModel } from 'src/modules/item-related/item-rate/data/models/item-rate.model'; +import { + SeasonTypeDeletedHandler, + SeasonTypeUpdatedHandler, +} from './domain/managers/season-type.handler'; +import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service'; +import { SeasonPeriodModel } from 'src/modules/season-related/season-period/data/models/season-period.model'; @Module({ imports: [ @@ -45,6 +57,8 @@ import { VipCodeCreatedHandler } from './domain/managers/vip-code.handler'; TypeOrmModule.forFeature( [ ItemModel, + ItemRateModel, + SeasonPeriodModel, UserModel, TransactionModel, TransactionTaxModel, @@ -56,7 +70,9 @@ import { VipCodeCreatedHandler } from './domain/managers/vip-code.handler'; ], controllers: [CouchDataController], providers: [ - BookingHandler, + // BookingHandler, + BookingUpdateHandler, + ChangeStatusBookingHandler, BookingDeletedEvent, PaymentMethodDeletedHandler, PaymentMethodUpdatedHandler, @@ -67,9 +83,16 @@ import { VipCodeCreatedHandler } from './domain/managers/vip-code.handler'; SeasonPeriodUpdatedHandler, ItemUpdatedHandler, ItemDeletedHandler, + ItemRateUpdatedHandler, + ItemPriceUpdatedHandler, UserDeletedHandler, UserUpdatedHandler, + UserPrivilegeUpdateHandler, + SeasonTypeDeletedHandler, + SeasonTypeUpdatedHandler, + + SeasonPeriodDataService, TransactionDataService, UserDataService, ItemDataService, diff --git a/src/modules/configuration/couch/data/services/couch.service.ts b/src/modules/configuration/couch/data/services/couch.service.ts index 59cc403..3054cd8 100644 --- a/src/modules/configuration/couch/data/services/couch.service.ts +++ b/src/modules/configuration/couch/data/services/couch.service.ts @@ -3,7 +3,7 @@ import { DatabaseListen } from '../../constants'; import { EventBus } from '@nestjs/cqrs'; import { ChangeDocEvent } from '../../domain/events/change-doc.event'; import { ConfigService } from '@nestjs/config'; - +import { apm } from 'src/core/apm'; import * as Nano from 'nano'; @Injectable() @@ -23,6 +23,10 @@ export class CouchService { for (const database of DatabaseListen) { const db = nano.db.use(database); db.changesReader.start({ includeDocs: true }).on('change', (change) => { + Logger.log( + `Receive Data from ${database}: ${change?.id}`, + 'CouchService', + ); this.changeDoc(change, database); }); @@ -45,7 +49,10 @@ export class CouchService { const nano = this.nanoInstance; const db = nano.use(database); return await db.insert(data); - } catch (error) {} + } catch (error) { + console.log(error); + apm.captureError(error); + } } public async deleteDoc(data, database) { @@ -54,7 +61,10 @@ export class CouchService { const db = nano.use(database); const result = await db.get(data.id); await db.destroy(data.id, result._rev); - } catch (error) {} + } catch (error) { + console.log(error); + apm.captureError(error); + } } public async updateDoc(data, database) { @@ -62,11 +72,26 @@ export class CouchService { const nano = this.nanoInstance; const db = nano.use(database); const result = await db.get(data.id); - console.log(result, 'dsa'); await db.insert({ ...data, _rev: result._rev, }); - } catch (error) {} + } catch (error) { + console.log(error); + apm.captureError(error); + } + } + + public async getDoc(id: string, database: string) { + try { + const nano = this.nanoInstance; + const db = nano.use(database); + const result = await db.get(id); + return result; + } catch (error) { + console.log(error); + apm.captureError(error); + return null; + } } } diff --git a/src/modules/configuration/couch/domain/managers/booking.handler.ts b/src/modules/configuration/couch/domain/managers/booking.handler.ts index 4b64585..8c6d4e4 100644 --- a/src/modules/configuration/couch/domain/managers/booking.handler.ts +++ b/src/modules/configuration/couch/domain/managers/booking.handler.ts @@ -2,8 +2,7 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { CouchService } from '../../data/services/couch.service'; -import { STATUS } from 'src/core/strings/constants/base.constants'; -import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; +// import { STATUS } from 'src/core/strings/constants/base.constants'; import { mappingTransaction } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper'; import { TransactionDeletedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event'; import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event'; @@ -20,13 +19,65 @@ export class BookingDeletedEvent _id: event.data.id, ...event.data.data, }, - 'item', + 'booking', ); } } -@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent) -export class BookingHandler +// @EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent) +// export class BookingHandler +// implements IEventHandler +// { +// constructor( +// private bookingService: TransactionDataService, +// private couchService: CouchService, +// ) {} + +// async handle(event: TransactionChangeStatusEvent) { +// const old_data = event.data.old; +// const data = event.data.data; + +// if ( +// data.payment_type == TransactionPaymentType.COUNTER || +// ([STATUS.ACTIVE, STATUS.SETTLED].includes(data.status) && +// data.payment_type != TransactionPaymentType.COUNTER) +// ) { +// const booking = await this.bookingService.getOneByOptions({ +// where: { +// id: data.id, +// }, +// relations: ['items'], +// }); + +// mappingTransaction(booking); + +// if ( +// (old_data?.status != data.status || +// data.payment_type != TransactionPaymentType.COUNTER) && +// [STATUS.PENDING, STATUS.ACTIVE, STATUS.SETTLED].includes(data.status) +// ) { +// await this.couchService.createDoc( +// { +// _id: booking.id, +// ...booking, +// }, +// 'booking', +// ); +// } else { +// await this.couchService.updateDoc( +// { +// _id: booking.id, +// ...booking, +// }, +// 'booking', +// ); +// } +// } +// } +// } + +@EventsHandler(TransactionChangeStatusEvent) +export class ChangeStatusBookingHandler implements IEventHandler { constructor( @@ -35,24 +86,23 @@ export class BookingHandler ) {} async handle(event: TransactionChangeStatusEvent) { - const old_data = event.data.old; const data = event.data.data; + const dataID = data?.id ?? data?.order_id; - if (data.payment_type != TransactionPaymentType.COUNTER) return; + const couchData = await this.couchService.getDoc(dataID, 'booking'); const booking = await this.bookingService.getOneByOptions({ where: { - id: data.id, + id: dataID, }, relations: ['items'], }); - + console.log('change status', { dataID, couchData, booking }); mappingTransaction(booking); + console.log('after mapping'); - if ( - old_data?.status != data.status && - [STATUS.PENDING, STATUS.ACTIVE].includes(data.status) - ) { + if (!couchData) { + console.log('save data to couch'); await this.couchService.createDoc( { _id: booking.id, @@ -61,6 +111,42 @@ export class BookingHandler 'booking', ); } else { + console.log('update data to couch'); + await this.couchService.updateDoc( + { + _id: booking.id, + ...booking, + }, + 'booking', + ); + } + } +} + +@EventsHandler(TransactionUpdatedEvent) +export class BookingUpdateHandler + implements IEventHandler +{ + constructor( + private bookingService: TransactionDataService, + private couchService: CouchService, + ) {} + + async handle(event: TransactionUpdatedEvent) { + const data = event.data.data; + const dataID = data?.id ?? data?.order_id; + + const couchData = await this.couchService.getDoc(dataID, 'booking'); + console.log('update', { dataID, couchData }); + if (couchData) { + const booking = await this.bookingService.getOneByOptions({ + where: { + id: dataID, + }, + relations: ['items'], + }); + console.log({ booking }); + mappingTransaction(booking); await this.couchService.updateDoc( { _id: booking.id, diff --git a/src/modules/configuration/couch/domain/managers/item.handler.ts b/src/modules/configuration/couch/domain/managers/item.handler.ts index 90dedec..1c02076 100644 --- a/src/modules/configuration/couch/domain/managers/item.handler.ts +++ b/src/modules/configuration/couch/domain/managers/item.handler.ts @@ -5,6 +5,9 @@ import { ItemDeletedEvent } from 'src/modules/item-related/item/domain/entities/ import { ItemUpdatedEvent } from 'src/modules/item-related/item/domain/entities/event/item-updated.event'; import { ItemChangeStatusEvent } from 'src/modules/item-related/item/domain/entities/event/item-change-status.event'; import { ItemDataService } from 'src/modules/item-related/item/data/services/item-data.service'; +import { SeasonPeriodUpdatedEvent } from 'src/modules/season-related/season-period/domain/entities/event/season-period-updated.event'; +import { SeasonPeriodChangeStatusEvent } from 'src/modules/season-related/season-period/domain/entities/event/season-period-change-status.event'; +import { ItemRateUpdatedEvent } from 'src/modules/item-related/item-rate/domain/entities/event/item-rate-updated.event'; @EventsHandler(ItemDeletedEvent) export class ItemDeletedHandler implements IEventHandler { @@ -79,3 +82,85 @@ export class ItemUpdatedHandler } } } + +@EventsHandler(SeasonPeriodChangeStatusEvent, SeasonPeriodUpdatedEvent) +export class ItemPriceUpdatedHandler + implements IEventHandler +{ + constructor( + private couchService: CouchService, + private itemService: ItemDataService, + ) {} + + async handle(event: SeasonPeriodChangeStatusEvent) { + const data = event.data.data; + + // change status to active + if (data.status == STATUS.ACTIVE) { + const dataItems = await this.itemService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + relations: [ + 'item_category', + 'bundling_items', + 'bundling_items.item_category', + 'item_rates', + 'item_rates.item', + 'item_rates.season_period', + 'item_rates.season_period.season_type', + ], + }); + + for (const dataItem of dataItems) { + await this.couchService.updateDoc( + { + _id: dataItem.id, + ...dataItem, + }, + 'item', + ); + } + } + } +} + +@EventsHandler(ItemRateUpdatedEvent) +export class ItemRateUpdatedHandler + implements IEventHandler +{ + constructor( + private couchService: CouchService, + private itemService: ItemDataService, + ) {} + + async handle(event: ItemRateUpdatedEvent) { + const data = event.data.data; + + const dataItems = await this.itemService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + id: data.item?.id, + }, + relations: [ + 'item_category', + 'bundling_items', + 'bundling_items.item_category', + 'item_rates', + 'item_rates.item', + 'item_rates.season_period', + 'item_rates.season_period.season_type', + ], + }); + + for (const dataItem of dataItems) { + await this.couchService.updateDoc( + { + _id: dataItem.id, + ...dataItem, + }, + 'item', + ); + } + } +} diff --git a/src/modules/configuration/couch/domain/managers/season-type.handler.ts b/src/modules/configuration/couch/domain/managers/season-type.handler.ts new file mode 100644 index 0000000..811f6c3 --- /dev/null +++ b/src/modules/configuration/couch/domain/managers/season-type.handler.ts @@ -0,0 +1,52 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { CouchService } from '../../data/services/couch.service'; +import { SeasonTypeDeletedEvent } from 'src/modules/season-related/season-type/domain/entities/event/season-type-deleted.event'; +import { SeasonTypeChangeStatusEvent } from 'src/modules/season-related/season-type/domain/entities/event/season-type-change-status.event'; +import { SeasonTypeUpdatedEvent } from 'src/modules/season-related/season-type/domain/entities/event/season-type-updated.event'; +import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service'; + +@EventsHandler(SeasonTypeDeletedEvent) +export class SeasonTypeDeletedHandler + implements IEventHandler +{ + constructor(private couchService: CouchService) {} + + async handle(event: SeasonTypeDeletedEvent) { + console.log('deleted session type'); + } +} + +@EventsHandler(SeasonTypeChangeStatusEvent, SeasonTypeUpdatedEvent) +export class SeasonTypeUpdatedHandler + implements IEventHandler +{ + constructor( + private couchService: CouchService, + private seasonPeriodService: SeasonPeriodDataService, + ) {} + + async handle(event: SeasonTypeChangeStatusEvent) { + const data = event.data.data; + const typeID = data.id; + const periods = await this.seasonPeriodService.getManyByOptions({ + where: { + season_type_id: typeID, + }, + relations: ['season_type'], + }); + + for (const period of periods) { + const dataID = period.id; + const couchData = await this.couchService.getDoc(dataID, 'season_period'); + if (couchData) { + await this.couchService.updateDoc( + { + _id: dataID, + ...period, + }, + 'season_period', + ); + } + } + } +} diff --git a/src/modules/configuration/couch/domain/managers/user.handler.ts b/src/modules/configuration/couch/domain/managers/user.handler.ts index 97c8e18..08ea3f3 100644 --- a/src/modules/configuration/couch/domain/managers/user.handler.ts +++ b/src/modules/configuration/couch/domain/managers/user.handler.ts @@ -5,6 +5,7 @@ import { UserDeletedEvent } from 'src/modules/user-related/user/domain/entities/ import { UserChangeStatusEvent } from 'src/modules/user-related/user/domain/entities/event/user-change-status.event'; import { UserUpdatedEvent } from 'src/modules/user-related/user/domain/entities/event/user-updated.event'; import { UserDataService } from 'src/modules/user-related/user/data/services/user-data.service'; +import { UserPrivilegeConfigUpdatedEvent } from 'src/modules/user-related/user-privilege/domain/entities/event/user-privilege-configuration-updated.event'; @EventsHandler(UserDeletedEvent) export class UserDeletedHandler implements IEventHandler { @@ -44,14 +45,16 @@ export class UserUpdatedHandler ], }) .then((item) => { - const user_privilege_configurations = item[ - 'user_privilege' - ]?.user_privilege_configurations?.filter( - (config) => config.module == 'POS', - ); - Object.assign(item['user_privilege'], { - user_privilege_configurations: user_privilege_configurations, - }); + if (item.role != 'superadmin') { + const user_privilege_configurations = item[ + 'user_privilege' + ]?.user_privilege_configurations?.filter( + (config) => config.module == 'POS', + ); + Object.assign(item['user_privilege'], { + user_privilege_configurations: user_privilege_configurations, + }); + } return item; }); @@ -86,3 +89,52 @@ export class UserUpdatedHandler } } } + +@EventsHandler(UserPrivilegeConfigUpdatedEvent) +export class UserPrivilegeUpdateHandler + implements IEventHandler +{ + constructor( + private couchService: CouchService, + private userService: UserDataService, + ) {} + + async handle(event: UserPrivilegeConfigUpdatedEvent) { + const data = event.data.data; + + const users = await this.userService + .getManyByOptions({ + where: { + user_privilege_id: data.user_privilege_id ?? data.id, + status: STATUS.ACTIVE, + }, + relations: [ + 'user_privilege', + 'user_privilege.user_privilege_configurations', + ], + }) + .then((items) => { + return items?.map((item) => { + const user_privilege_configurations = item[ + 'user_privilege' + ]?.user_privilege_configurations?.filter( + (config) => config.module == 'POS', + ); + Object.assign(item['user_privilege'], { + user_privilege_configurations: user_privilege_configurations, + }); + return item; + }); + }); + + for (const user of users) { + await this.couchService.updateDoc( + { + _id: user.id, + ...user, + }, + 'user', + ); + } + } +} diff --git a/src/modules/configuration/export/constants.ts b/src/modules/configuration/export/constants.ts new file mode 100644 index 0000000..41fb902 --- /dev/null +++ b/src/modules/configuration/export/constants.ts @@ -0,0 +1,10 @@ +export enum InvoiceType { + BOOKING_INVOICE = 'this is your invoice', + PAYMENT_CONFIRMATION = 'payment confirmation', + INVOICE_EXPIRED = 'invoice has expired', + REFUND_REQUEST = 'your refund request', + REFUND_CONFIRMATION = 'refund confirmation', + BOOKING_DATE_CHANGE = 'booking date change', +} + +export const PhoneNumber = '088'; diff --git a/src/modules/configuration/export/domain/managers/pdf-make.manager.ts b/src/modules/configuration/export/domain/managers/pdf-make.manager.ts new file mode 100644 index 0000000..b33065f --- /dev/null +++ b/src/modules/configuration/export/domain/managers/pdf-make.manager.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { EventTopics } from 'src/core/strings/constants/interface.constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { GeneratePdf } from '../templates/helpers/generate-pdf.helper'; + +@Injectable() +export class PdfMakeManager extends BaseCustomManager { + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async process(): Promise { + try { + const transaction = await this.dataService.getOneByOptions({ + where: { + id: this.data.id, + }, + relations: ['items'], + }); + const banks = await this.dataServiceFirstOpt.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); + + this.result = GeneratePdf(transaction, this.data.invoice_type, banks); + } catch (error) { + console.log(error, 'generate pdf'); + } + return; + } + + async afterProcess(): Promise { + return; + } + + getResult() { + return this.result; + } +} diff --git a/src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper.ts b/src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper.ts new file mode 100644 index 0000000..0ea3b6e --- /dev/null +++ b/src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper.ts @@ -0,0 +1,43 @@ +import { InvoiceTemplate } from '../invoice.template'; +import * as PdfPrinter from 'pdfmake'; +import { PassThrough } from 'stream'; + +export async function GeneratePdf(transaction, invoiceType, banks) { + const fonts = { + Roboto: { + normal: './assets/fonts/Roboto-Regular.ttf', + bold: './assets/fonts/Roboto-Medium.ttf', + italics: './assets/fonts/Roboto-Italic.ttf', + bolditalics: './assets/fonts/Roboto-MediumItalic.ttf', + }, + }; + + const printer = new PdfPrinter(fonts); + + const docDefinition = InvoiceTemplate(transaction, invoiceType, banks); + + const createPdfBuffer = (docDefinition) => { + return new Promise((resolve, reject) => { + const pdfDoc = printer.createPdfKitDocument(docDefinition); + const chunks = []; + const stream = new PassThrough(); + + pdfDoc.pipe(stream); + pdfDoc.end(); + + stream.on('data', (chunk) => { + chunks.push(chunk); + }); + + stream.on('end', () => { + const pdfBuffer = Buffer.concat(chunks); + resolve(pdfBuffer); + }); + + stream.on('error', reject); + }); + }; + + const pdfBuffer = await createPdfBuffer(docDefinition); + return pdfBuffer; +} diff --git a/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts b/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts new file mode 100644 index 0000000..904f3c4 --- /dev/null +++ b/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts @@ -0,0 +1,443 @@ +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { InvoiceType, PhoneNumber } from '../../../constants'; +import { PaymentMethodEntity } from 'src/modules/transaction/payment-method/domain/entities/payment-method.entity'; + +export function mappingHeader(transaction: TransactionEntity, invoiceType) { + return [ + { + stack: [ + { + alignment: 'center', + columns: [ + { + width: 100, + image: 'data', + }, + { + width: 'auto', + text: 'have', + fontSize: 50, + bold: true, + color: '#169f54', + padding: [0, 0, 0, 0], + margin: [0, 0, 0, 0], + alignment: 'center', + }, + { + width: 'auto', + text: 'fun', + fontSize: 50, + bold: true, + color: '#61c4eb', + padding: [0, 0, 0, 0], + margin: [0, 0, 0, 0], + // alignment: "center" + }, + ], + }, + { + text: invoiceType.toUpperCase(), + fontSize: 30, + alignment: 'center', + }, + { + columns: [ + { + text: transaction.invoice_code, + }, + { + text: new Date(transaction.booking_date).toDateString(), + alignment: 'right', + }, + ], + }, + ], + }, + { + width: 150, + text: 'Jl, Kolonel Masturi No.KM. 11, \n Kertawangi, Kec. Cisatua, \n Kab. Bandung Barat, \n Jawa Barat 40551 \n 0815-6380-8021', + // color: '#aaaaab', + // bold: true, + fontSize: 11, + margin: [0, 20, 0, 5], + }, + ]; +} + +export function mappingFooter() { + return [ + [ + { + text: 'we commits to prividing an educative, \n playful, and purposeful environment\n this is advantageous for all ages', + border: [false, true, false, false], + margin: [0, 5, 0, 5], + alignment: 'right', + }, + { + text: 'Thank \n You', + border: [false, true, false, false], + fontSize: 20, + bold: true, + color: '#169f54', + margin: [0, 5, 0, 5], + }, + ], + ]; +} + +export function mappingPrice( + transaction: TransactionEntity, + invoiceType: InvoiceType, +) { + const result = []; + const totalData = [ + InvoiceType.REFUND_CONFIRMATION, + InvoiceType.REFUND_REQUEST, + ].includes(invoiceType) + ? transaction['refund'].refund_total + : Number(transaction.payment_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }); + const subTotalData = [ + InvoiceType.REFUND_CONFIRMATION, + InvoiceType.REFUND_REQUEST, + ].includes(invoiceType) + ? transaction['refund'].refund_total + : Number(transaction.payment_sub_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }); + + const sub_total = [ + { + text: 'SUBTOTAL', + alignment: 'right', + bold: true, + margin: [0, 5, 0, 5], + }, + { + text: subTotalData, + margin: [0, 5, 0, 5], + }, + ]; + result.push(sub_total); + + if (Number(transaction.payment_discount_total ?? 0) > 0) { + const discount = [ + { + text: 'DISCOUNT', + bold: true, + alignment: 'right', + margin: [0, 5, 0, 5], + }, + { + text: Number(transaction.payment_discount_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + margin: [0, 5, 0, 5], + }, + ]; + result.push(discount); + } + + const total = [ + { + text: 'TOTAL', + bold: true, + border: [false, false, false, true], + alignment: 'right', + margin: [0, 5, 0, 5], + }, + { + text: totalData, + border: [false, false, false, true], + margin: [0, 5, 0, 5], + }, + ]; + result.push(total); + + return result; +} + +export function mappingItem(transaction, invoiceType: InvoiceType) { + const header = [ + { + text: 'ITEM', + border: [false, true, false, false], + margin: [0, 5, 0, 5], + bold: true, + textTransform: 'uppercase', + }, + { + text: 'QTY', + alignment: 'center', + border: [false, true, false, false], + margin: [0, 5, 0, 5], + bold: true, + textTransform: 'uppercase', + }, + { + text: 'PRICE', + alignment: 'center', + border: [false, true, false, false], + margin: [0, 5, 0, 5], + bold: true, + textTransform: 'uppercase', + }, + { + text: 'AMOUNT', + border: [false, true, false, false], + alignment: 'center', + bold: true, + margin: [0, 5, 0, 5], + textTransform: 'uppercase', + }, + ]; + + const result = []; + + if ( + [InvoiceType.REFUND_CONFIRMATION, InvoiceType.REFUND_REQUEST].includes( + invoiceType, + ) + ) { + transaction.refund?.refund_items + ?.filter((item) => Number(item.qty_refund) > 0) + .forEach((item) => { + const dataRow = [ + { + text: item.transaction_item.item_name, + margin: [0, 5, 0, 5], + alignment: 'left', + }, + { + text: item.qty_refund, + margin: [0, 5, 0, 5], + alignment: 'center', + }, + { + text: Number(item.transaction_item.total_price).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + margin: [0, 5, 0, 5], + alignment: 'right', + }, + { + text: Number(item.refund_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + alignment: 'right', + margin: [0, 5, 0, 5], + }, + ]; + result.push(dataRow); + }); + } else { + transaction.items.forEach((item) => { + const dataRow = [ + { + text: item.item_name, + margin: [0, 5, 0, 5], + alignment: 'left', + }, + { + text: item.qty, + margin: [0, 5, 0, 5], + alignment: 'center', + }, + { + text: Number(item.item_price).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + margin: [0, 5, 0, 5], + alignment: 'right', + }, + { + text: Number(item.total_price).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + alignment: 'right', + margin: [0, 5, 0, 5], + }, + ]; + result.push(dataRow); + }); + } + + const body = [header, ...result]; + + return body; +} + +export function mappingBody( + transaction: TransactionEntity, + invoiceType: InvoiceType, +) { + // booking date change information + if (invoiceType == InvoiceType.BOOKING_DATE_CHANGE) { + return [ + "Great news! We've successfully updated your booking date as per your request. \n We're exited to accommodate your new plans and ensure everything goes smoothly \n\n", + 'Here are your updated booking details:', + { + text: `\n\n Original Booking Date: ${new Date( + transaction['booking_date_before'], + ).toDateString()} \n New Booking Date: ${new Date( + transaction.booking_date, + ).toDateString()} \n\n`, + bold: true, + }, + "Here's a quick recap of your order :", + ]; + } + + // booking invoice + else if (invoiceType == InvoiceType.BOOKING_INVOICE) { + return [ + "Thank you for choosing us! We're absolutely thrilled and can't wait to embark on this exiting day with you. See you soon for fun times ahead!", + ]; + } else if (invoiceType == InvoiceType.PAYMENT_CONFIRMATION) { + // booking payment confirmation + return [ + 'We are exited to inform you that your payment has been successfully received! \n', + 'Attached to this email, you will find your confirmation receipt. \n', + 'Please keep this safe as you will need to show it at the entrance upon your arrival. \n', + "It's your golden ticket to all the fun and excitement awaiting you \n\n", + "Here's a quick recap: \n", + ]; + } + + // expired information invoice + // else if (invoiceType == InvoiceType.INVOICE_EXPIRED) { + + // return [ + // "We hope this message finds you well!", + // "Uh-oh! it looks like your invoice, dated 15 Juli, has officially expired as of 15 Juli. But no worries, we can fix this together \n", + // "To keep the goof times rolling, our friendly support team is just a call away at \n", + // "0564645 \n\n", + // "Here are the detail of the expired invoice: " + + // ] + // } + + // refund information + else if (invoiceType == InvoiceType.REFUND_REQUEST) { + return [ + "We'ew trully sorry for any inconvenience that led to this request. \n", + "We've received your refund request for: \n", + ]; + } + + // refund confirmation + else if (invoiceType == InvoiceType.REFUND_CONFIRMATION) { + return [ + 'Good news! \n', + "We've successfully processed your refund for: \n", + ]; + } +} + +export function mappingBodyBottom( + transaction: TransactionEntity, + invoiceType: InvoiceType, + banks: PaymentMethodEntity[], +) { + if (invoiceType == InvoiceType.BOOKING_DATE_CHANGE) { + // booking date change information + return [ + "For your convenience, we've attached a new confirmation receipt reflecting these changes \n", + 'Please be sure to bring this updated receipt with you on the new date \n\n', + 'If you have any questions or need further assistance, our friendly support team is just a call away at \n\n', + PhoneNumber, + "\nThank you and we can't wait to see you and make sure you hove an amazing time!", + ]; + } else if (invoiceType == InvoiceType.BOOKING_INVOICE) { + // booking invoice + return [ + 'Just a friendly reminder that your invoice will expire on 24 Agustus 2024 \n', + 'To keep things running smoothly, please ensure your payment is completed before this data. \n', + '\nFor youe convenience, here is a list of our account details \n\n', + { + text: [ + banks.forEach((bank) => { + return { + text: `${bank.issuer_name} ${bank.account_number} a/n ${bank.account_name} \n`, + bold: true, + }; + }), + ], + }, + "\n Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly\n", + 'If you have any questions or need assistance, feel free to reach out to our support team at\n', + PhoneNumber, + ]; + } + + // booking payment confirmation + else if (invoiceType == InvoiceType.PAYMENT_CONFIRMATION) { + return [ + 'If you have any questions or need assistance, feel free to reach out to our support team at\n', + PhoneNumber, + "\nDon't forget to bring a smile and your confirmation receipt (attached) for a smooth entry. \n", + "We can't wait to see you and ensure you have an amazing time with us!", + ]; + } + + // expired information invoice + else if (invoiceType == InvoiceType.INVOICE_EXPIRED) { + return []; + } + + // refund information + else if (invoiceType == InvoiceType.REFUND_REQUEST) { + return [ + "Your satisfaction is important to us, and we're commited to resolving this as quickly as possible.\n", + 'Our team is already on it and will process your refund request promptly. \n', + "We'll keep you updated and notify you once the refund has been processed. \n", + "If you have any questions or need futher assistance, don't hestitate to reach out us at \n", + PhoneNumber, + '\n\n', + 'Thank you for your patience and understanding \n', + 'We appreciate your feedback and here to make things right!', + ]; + } + + // refund confirmation + else if (invoiceType == InvoiceType.REFUND_CONFIRMATION) { + return [ + 'Here are the details of your refund: \n\n', + { + text: `Transaction Number: ${transaction.invoice_code} \n`, + }, + { + text: `Refund Processed: ${transaction['refund'].refund_date}\n`, + }, + { + text: `Bank Account: ${transaction['refund'].bank_name} \n`, + }, + { + text: `Account Number: ${transaction['refund'].bank_account_number} \n`, + }, + { + text: `Account Name: ${transaction['refund'].bank_account_name} \n`, + }, + "\n We hope this helps make things righ, and we're he to assist if you need anything else. \n", + 'You should see the refund reflected in your account within 3 Business days \n\n', + 'Thank you for your patience and understanding\n', + 'If you have any questions or need assistance, feel free to reach out to our support team at \n', + PhoneNumber, + "\nWe're alyways here to help!", + ]; + } +} diff --git a/src/modules/configuration/export/domain/templates/invoice.template.ts b/src/modules/configuration/export/domain/templates/invoice.template.ts new file mode 100644 index 0000000..48da0c7 --- /dev/null +++ b/src/modules/configuration/export/domain/templates/invoice.template.ts @@ -0,0 +1,215 @@ +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { + mappingBody, + mappingBodyBottom, + mappingFooter, + mappingHeader, + mappingItem, + mappingPrice, +} from './helpers/invoice-mapping.helper'; +import { InvoiceType } from '../../constants'; +import { PaymentMethodEntity } from 'src/modules/transaction/payment-method/domain/entities/payment-method.entity'; +import * as fs from 'fs'; + +export function InvoiceTemplate( + transaction: TransactionEntity, + invoiceType: InvoiceType, + banks: PaymentMethodEntity[], +) { + const filePath = './assets/image/logo.jpeg'; + const imageBase64 = fs.readFileSync(filePath).toString('base64'); + const imageUrl = `data:image/png;base64,${imageBase64}`; + + return { + content: [ + { + alignment: 'justify', + columns: mappingHeader(transaction, invoiceType), + }, + + '\n\n', + + // tipe booking date change tidak ada + { + columns: [ + { + text: `Dear, \n ${transaction.customer_name} \n ${ + transaction.customer_phone + } \n Booking Date: ${new Date( + transaction.booking_date, + ).toDateString()}`, + bold: true, + }, + ], + }, + + '\n', + { + text: mappingBody(transaction, invoiceType), + }, + '\n', + + { + layout: { + defaultBorder: false, + hLineWidth: function () { + return 1; + }, + vLineWidth: function () { + return 1; + }, + hLineColor: function (i) { + return i === 0 ? '#000000' : '#eaeaea'; + }, + vLineColor: function () { + return '#eaeaea'; + }, + hLineStyle: function () { + // if (i === 0 || i === node.table.body.length) { + return null; + //} + }, + // vLineStyle: function (i, node) { return {dash: { length: 10, space: 4 }}; }, + paddingLeft: function () { + return 10; + }, + paddingRight: function () { + return 10; + }, + paddingTop: function () { + return 3; + }, + paddingBottom: function () { + return 3; + }, + fillColor: function () { + return '#fff'; + }, + }, + table: { + widths: ['*', 'auto', 'auto', 100], + body: mappingItem(transaction, invoiceType), + }, + }, + '\n', + { + layout: { + defaultBorder: false, + hLineWidth: function () { + return 1; + }, + vLineWidth: function () { + return 1; + }, + hLineColor: function (i) { + return i === 3 ? '#000000' : '#eaeaea'; + }, + vLineColor: function () { + return '#eaeaea'; + }, + hLineStyle: function () { + // if (i === 0 || i === node.table.body.length) { + return null; + //} + }, + // vLineStyle: function () { return {dash: { length: 10, space: 4 }}; }, + paddingLeft: function () { + return 10; + }, + paddingRight: function () { + return 10; + }, + paddingTop: function () { + return 3; + }, + paddingBottom: function (i) { + return i === 2 ? 20 : 3; + }, + fillColor: function () { + return '#fff'; + }, + }, + table: { + headerRows: 1, + widths: ['*', 100], + body: mappingPrice(transaction, invoiceType), + }, + }, + + '\n', + { + text: mappingBodyBottom(transaction, invoiceType, banks), + }, + '\n', + + { + layout: { + defaultBorder: false, + hLineWidth: function () { + return 1; + }, + vLineWidth: function () { + return 1; + }, + hLineColor: function (i) { + return i === 0 ? '#000000' : '#eaeaea'; + }, + vLineColor: function () { + return '#eaeaea'; + }, + hLineStyle: function () { + // if (i === 0 || i === node.table.body.length) { + return null; + //} + }, + // vLineStyle: function () { return {dash: { length: 10, space: 4 }}; }, + paddingLeft: function () { + return 10; + }, + paddingRight: function () { + return 10; + }, + paddingTop: function () { + return 20; + }, + paddingBottom: function () { + return 3; + }, + fillColor: function () { + return '#fff'; + }, + }, + table: { + widths: ['*', 200], + body: mappingFooter(), + }, + }, + ], + styles: { + notesTitle: { + fontSize: 10, + bold: true, + margin: [0, 50, 0, 3], + }, + notesText: { + fontSize: 10, + }, + tableExample: { + margin: [0, 5, 0, 15], + headerRows: 1, + widths: ['*', 100], + }, + tableHeader: { + bold: true, + fontSize: 13, + color: 'black', + }, + }, + defaultStyle: { + columnGap: 20, + }, + images: { + data: imageUrl, + }, + }; +} diff --git a/src/modules/configuration/export/export.module.ts b/src/modules/configuration/export/export.module.ts new file mode 100644 index 0000000..e4ed0a6 --- /dev/null +++ b/src/modules/configuration/export/export.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { PdfMakeManager } from './domain/managers/pdf-make.manager'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; +import { PaymentMethodModel } from 'src/modules/transaction/payment-method/data/models/payment-method.model'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([TransactionModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [], + providers: [PdfMakeManager], +}) +export class ExportModule {} diff --git a/src/modules/configuration/export/infrastructure/export.controller.ts b/src/modules/configuration/export/infrastructure/export.controller.ts new file mode 100644 index 0000000..75034c5 --- /dev/null +++ b/src/modules/configuration/export/infrastructure/export.controller.ts @@ -0,0 +1,16 @@ +import { Controller, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Public } from 'src/core/guards'; + +@ApiTags('export') +@Controller('export') +@Public(true) +export class ExportController { + constructor() {} + + // @Post('pdf/example') + // async exportPdf( + // ): Promise { + // return PdfMaker(); + // } +} diff --git a/src/modules/configuration/google-calendar/domain/usecases/managers/helpers/create-event-calanedar.helper.ts b/src/modules/configuration/google-calendar/domain/usecases/managers/helpers/create-event-calanedar.helper.ts new file mode 100644 index 0000000..23f82f1 --- /dev/null +++ b/src/modules/configuration/google-calendar/domain/usecases/managers/helpers/create-event-calanedar.helper.ts @@ -0,0 +1,77 @@ +import { google } from 'googleapis'; +import * as fs from 'fs'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; + +export async function CreateEventCalendarHelper( + transaction: TransactionEntity, + isDelete = false, +) { + let result; + + const filePath = './assets/json/google-credential.json'; + const credential = JSON.parse(fs.readFileSync(filePath, 'utf8')); + + const client = new google.auth.JWT({ + email: credential.client_email, + key: credential.private_key, + scopes: ['https://www.googleapis.com/auth/calendar'], + }); + + const calendar = google.calendar({ + version: 'v3', + auth: client, + }); + + const eventData = mappingData(transaction); + + if (transaction.calendar_id) { + result = await calendar.events.update( + { + calendarId: process.env.GOOGLE_CALENDAR_ID, + eventId: transaction.calendar_id, + requestBody: eventData, + }, + {}, + ); + } else if (!isDelete) { + result = await calendar.events.insert( + { + calendarId: process.env.GOOGLE_CALENDAR_ID, + requestBody: eventData, + }, + {}, + ); + } else { + result = await calendar.events.delete( + { + calendarId: process.env.GOOGLE_CALENDAR_ID, + eventId: transaction.calendar_id, + }, + {}, + ); + } + + return result?.data; +} + +function mappingData(transaction) { + return { + summary: transaction.customer_name ?? transaction.invoice_code, + description: `Booking for invoice ${ + transaction.invoice_code + }

List Items :

    ${transaction.items.map( + (item) => `
  • ${item.item_name}
  • `, + )}
`, + start: { + dateTime: new Date(transaction.booking_date).toISOString(), + timeZone: 'Asia/Jakarta', + }, + end: { + dateTime: new Date(transaction.booking_date).toISOString(), + timeZone: 'Asia/Jakarta', + }, + reminders: { + useDefault: false, + }, + }; +} diff --git a/src/modules/configuration/google-calendar/domain/usecases/managers/index-holiday-google-calendar.manager.ts b/src/modules/configuration/google-calendar/domain/usecases/managers/index-holiday-google-calendar.manager.ts index 6124380..d3c4454 100644 --- a/src/modules/configuration/google-calendar/domain/usecases/managers/index-holiday-google-calendar.manager.ts +++ b/src/modules/configuration/google-calendar/domain/usecases/managers/index-holiday-google-calendar.manager.ts @@ -8,7 +8,7 @@ export class IndexHolidayCalendarManager { const events = []; const calendar = google.calendar({ version: 'v3', - auth: 'AIzaSyCsCt6PDd6uYLkahvtdvCoMWf-1_QaLiNM', + auth: process.env.GOOGLE_CALENDAR_KEY, }); const calendarId = 'id.indonesian#holiday@group.v.calendar.google.com'; diff --git a/src/modules/configuration/google-calendar/google-calendar.module.ts b/src/modules/configuration/google-calendar/google-calendar.module.ts index e3659cc..24fde4b 100644 --- a/src/modules/configuration/google-calendar/google-calendar.module.ts +++ b/src/modules/configuration/google-calendar/google-calendar.module.ts @@ -4,10 +4,29 @@ import { CqrsModule } from '@nestjs/cqrs'; import { IndexHolidayCalendarManager } from '../../configuration/google-calendar/domain/usecases/managers/index-holiday-google-calendar.manager'; import { GoogleCalendarController } from './infrastructure/google-calendar.controller'; import { GoogleCalendarOrchestrator } from './domain/usecases/google-calendar.orchestrator'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; +import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model'; @Module({ - imports: [ConfigModule.forRoot(), CqrsModule], + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [TransactionModel, TransactionItemModel, TransactionTaxModel], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], controllers: [GoogleCalendarController], - providers: [IndexHolidayCalendarManager, GoogleCalendarOrchestrator], + providers: [ + IndexHolidayCalendarManager, + + TransactionDataService, + + GoogleCalendarOrchestrator, + ], }) export class GoogleCalendarModule {} diff --git a/src/modules/configuration/google-calendar/infrastructure/google-calendar.controller.ts b/src/modules/configuration/google-calendar/infrastructure/google-calendar.controller.ts index e8c8b6b..500609f 100644 --- a/src/modules/configuration/google-calendar/infrastructure/google-calendar.controller.ts +++ b/src/modules/configuration/google-calendar/infrastructure/google-calendar.controller.ts @@ -1,5 +1,5 @@ import { GoogleCalendarOrchestrator } from './../domain/usecases/google-calendar.orchestrator'; -import { Controller, Get, Query } from '@nestjs/common'; +import { Controller, Get, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/guards'; import { FilterGoogleCalendarDto } from './dto/filter-google-calendar.dto'; diff --git a/src/modules/configuration/log/data/models/pos-log.model.ts b/src/modules/configuration/log/data/models/pos-log.model.ts new file mode 100644 index 0000000..fd8631f --- /dev/null +++ b/src/modules/configuration/log/data/models/pos-log.model.ts @@ -0,0 +1,34 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { Column, Entity } from 'typeorm'; +import { PosLogEntity, PosLogType } from '../../domain/entities/pos-log.entity'; +import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; + +@Entity(TABLE_NAME.LOG_POS) +export class PosLogModel + extends BaseCoreModel + implements PosLogEntity +{ + @Column('varchar', { name: 'type', default: PosLogType.cash_witdrawal }) + type: PosLogType; + + @Column('bigint', { name: 'pos_number', nullable: true }) + pos_number: number; + + @Column('decimal', { name: 'total_balance', nullable: true }) + total_balance: number; + + @Column('bigint', { name: 'created_at', nullable: true }) + created_at: number; + + @Column('varchar', { name: 'creator_name', nullable: true }) + creator_name: string; + + @Column('varchar', { name: 'creator_id', nullable: true }) + creator_id: string; + + @Column('varchar', { name: 'drawn_by_name', nullable: true }) + drawn_by_name: string; + + @Column('varchar', { name: 'drawn_by_id', nullable: true }) + drawn_by_id: string; +} diff --git a/src/modules/configuration/log/data/services/pos-log.service.ts b/src/modules/configuration/log/data/services/pos-log.service.ts new file mode 100644 index 0000000..745b929 --- /dev/null +++ b/src/modules/configuration/log/data/services/pos-log.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { PosLogEntity } from '../../domain/entities/pos-log.entity'; +import { PosLogModel } from '../models/pos-log.model'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class PosLogService extends BaseDataService { + constructor( + @InjectRepository(PosLogModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/configuration/log/domain/entities/pos-log.entity.ts b/src/modules/configuration/log/domain/entities/pos-log.entity.ts new file mode 100644 index 0000000..f29f780 --- /dev/null +++ b/src/modules/configuration/log/domain/entities/pos-log.entity.ts @@ -0,0 +1,17 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; + +export interface PosLogEntity extends BaseCoreEntity { + type: PosLogType; + pos_number: number; + total_balance: number; + created_at: number; + creator_name: string; + creator_id: string; +} + +export enum PosLogType { + cash_witdrawal = 'cash withdrawal', + opening_cash = 'opening_cash', + login = 'login', + logout = 'logout', +} diff --git a/src/modules/configuration/log/domain/handlers/log.handler.ts b/src/modules/configuration/log/domain/handlers/log.handler.ts index 1e4fafb..73817e9 100644 --- a/src/modules/configuration/log/domain/handlers/log.handler.ts +++ b/src/modules/configuration/log/domain/handlers/log.handler.ts @@ -1,9 +1,33 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { RecordLog } from '../entities/log.event'; +import { LogService } from '../../data/services/log.service'; +import { LogModel } from '../../data/models/log.model'; @EventsHandler(RecordLog) export class RecordLogHandler implements IEventHandler { + constructor(private dataService: LogService) {} + async handle(event: RecordLog) { - // TODO: Implement logic here + const data = event.data; + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + const log = new LogModel(); + + Object.assign(log, { + data_id: data.id, + module: data.module, + description: data.description, + process: data.op, + old_data: data.old, + data: data.data, + created_at: new Date().getTime(), + creator_name: data.user.name, + creator_id: data.user.id, + }); + + await this.dataService.create(queryRunner, LogModel, log); } } diff --git a/src/modules/configuration/log/domain/handlers/pos-log.handler.ts b/src/modules/configuration/log/domain/handlers/pos-log.handler.ts new file mode 100644 index 0000000..192247f --- /dev/null +++ b/src/modules/configuration/log/domain/handlers/pos-log.handler.ts @@ -0,0 +1,41 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { ChangeDocEvent } from 'src/modules/configuration/couch/domain/events/change-doc.event'; +import { PosLogService } from '../../data/services/pos-log.service'; +import { PosLogModel } from '../../data/models/pos-log.model'; +import { PosLogType } from '../entities/pos-log.entity'; + +@EventsHandler(ChangeDocEvent) +export class RecordPosLogHandler implements IEventHandler { + constructor(private dataService: PosLogService) {} + + async handle(event: ChangeDocEvent) { + try { + const database = event.data.database; + const data = event.data.data; + + if (!['pos_cash_activity', 'pos_activity'].includes(database)) return; + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + const activity = new PosLogModel(); + + Object.assign(activity, { + id: data._id, + type: PosLogType[data.type], + total_balance: data.withdrawal_cash ?? data.opening_cash_balance, + pos_number: data.pos_number, + creator_id: data.pos_admin?.id, + creator_name: data.pos_admin?.name ?? data.pos_admin?.username, + drawn_by_id: data.withdraw_user?.id, + drawn_by_name: data.withdraw_user?.name ?? data.withdraw_user?.username, + created_at: data.created_at, + }); + + await this.dataService.create(queryRunner, PosLogModel, activity); + } catch (error) { + console.log('error handling pos activity couch'); + } + } +} diff --git a/src/modules/configuration/log/log.module.ts b/src/modules/configuration/log/log.module.ts index 14c8eef..06002f9 100644 --- a/src/modules/configuration/log/log.module.ts +++ b/src/modules/configuration/log/log.module.ts @@ -9,12 +9,15 @@ import { RecordErrorLogHandler } from './domain/handlers/error-log.handler'; import { RecordLogHandler } from './domain/handlers/log.handler'; import { ErrorLogService } from './data/services/error-log.service'; import { LogService } from './data/services/log.service'; +import { PosLogModel } from './data/models/pos-log.model'; +import { PosLogService } from './data/services/pos-log.service'; +import { RecordPosLogHandler } from './domain/handlers/pos-log.handler'; @Module({ imports: [ ConfigModule.forRoot(), TypeOrmModule.forFeature( - [LogModel, ErrorLogModel], + [LogModel, ErrorLogModel, PosLogModel], CONNECTION_NAME.DEFAULT, ), CqrsModule, @@ -22,9 +25,11 @@ import { LogService } from './data/services/log.service'; controllers: [], providers: [ RecordLogHandler, + RecordPosLogHandler, RecordErrorLogHandler, LogService, + PosLogService, ErrorLogService, ], }) diff --git a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts index e3fb8ad..355c75b 100644 --- a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts +++ b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts @@ -4,9 +4,20 @@ import { PaymentMethodDataService } from 'src/modules/transaction/payment-method import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { sendEmail } from '../helpers/send-email.helper'; -import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; +import { GeneratePdf } from 'src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper'; +import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event'; +import { RefundChangeStatusEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-change-status.event'; +import { RefundCreatedEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-created.event'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { Logger } from '@nestjs/common'; -@EventsHandler(TransactionChangeStatusEvent) +@EventsHandler( + TransactionChangeStatusEvent, + TransactionUpdatedEvent, + RefundChangeStatusEvent, + RefundCreatedEvent, +) export class PaymentTransactionHandler implements IEventHandler { @@ -16,46 +27,272 @@ export class PaymentTransactionHandler ) {} async handle(event: TransactionChangeStatusEvent) { - const data_id = event.data.id; - const old_data = event.data.old; - const current_data = event.data.data; - let payments = []; + try { + const old_data = event.data.old; + const current_data = event.data.data; + const data_id = current_data.transaction_id ?? event.data.id; + const from_refund = event.data.module == TABLE_NAME.REFUND; + console.log('payment handlet', { data_id }); - if ( - old_data.status == STATUS.DRAFT && - current_data.status == STATUS.PENDING && - current_data.payment_type != TransactionPaymentType.COUNTER && - !!current_data.customer_email - ) { - if (current_data.payment_type != TransactionPaymentType.MIDTRANS) { - payments = await this.paymentService.getManyByOptions({ - where: { - status: STATUS.ACTIVE, - }, - }); - } + const payments = await this.paymentService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); - const transaction = await this.dataService.getOneByOptions({ + let transaction = await this.dataService.getOneByOptions({ where: { id: data_id, }, - relations: ['items'], + relations: [ + 'items', + 'refunds', + 'refunds.refund_items', + 'refunds.refund_items.transaction_item', + ], }); - try { + if (!transaction) { + transaction = await this.dataService.getOneByOptions({ + where: { + id: event.data.id, + }, + relations: [ + 'items', + 'refunds', + 'refunds.refund_items', + 'refunds.refund_items.transaction_item', + ], + }); + } + + Object.assign(transaction, { + booking_date: new Date(transaction.booking_date).toDateString(), + booking_date_before: new Date( + transaction.booking_date_before, + ).toDateString(), + email: transaction.customer_email, + payment_methods: payments, + }); + + const refund = transaction?.['refunds']?.find( + (refund) => ![STATUS.CANCEL].includes(refund.status), + ); + if (refund) { + Object.assign(refund, { + refund_date: new Date(refund?.refund_date).toDateString(), + request_date: new Date(refund?.request_date).toDateString(), + refund_total: Number(refund?.refund_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + }); + + Object.assign(transaction, { + refund: refund, + }); + } + + if (transaction?.['refund']?.refund_items.length > 0) { + Object.assign(transaction, { + refund_items: ` +

Refund Items:

+
    + ${transaction?.['refund']?.refund_items + ?.filter((item) => Number(item.qty_refund) > 0) + .map((item) => { + return ` +
  • ${item.qty_refund} ${item.transaction_item.item_name}
  • + `; + })} +
`, + }); + } + + if (!transaction.customer_email) return; + + // refund request + if ( + from_refund && + transaction['refund'] && + [STATUS.DRAFT].includes(transaction['refund'].status) + ) { + Logger.verbose('Send Refund Request', 'PaymentTransaction'); + const pdf = await GeneratePdf( + transaction, + InvoiceType.REFUND_REQUEST, + payments, + ); sendEmail( [ { ...transaction, - email: transaction.customer_email, - payment_methods: payments, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), }, ], - 'Payment Confirmation', + InvoiceType.REFUND_REQUEST, + pdf, ); - } catch (error) { - console.log(error); } + + // refund confirmation + else if ( + from_refund && + transaction['refund'] && + transaction['refund'].status == STATUS.REFUNDED + ) { + Logger.verbose('Send Refund Confirmation', 'PaymentTransaction'); + const pdf = await GeneratePdf( + transaction, + InvoiceType.REFUND_CONFIRMATION, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.REFUND_CONFIRMATION, + pdf, + ); + } + + // payment settled + else if ( + !from_refund && + old_data.status != current_data.status && + [STATUS.ACTIVE, STATUS.SETTLED].includes(current_data.status) + ) { + Logger.verbose('Send Payment Settled', 'PaymentTransaction'); + const pdf = await GeneratePdf( + transaction, + InvoiceType.PAYMENT_CONFIRMATION, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.PAYMENT_CONFIRMATION, + pdf, + ); + } + + // payment confirm to pending + else if ( + !from_refund && + old_data.status != current_data.status && + [STATUS.PENDING].includes(current_data.status) + ) { + Logger.verbose('Send Confirmation to Pending', 'PaymentTransaction'); + const pdf = await GeneratePdf( + transaction, + InvoiceType.BOOKING_INVOICE, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.BOOKING_INVOICE, + pdf, + ); + } + // payment expired + else if ( + !from_refund && + old_data.status != current_data.status && + [STATUS.PENDING, STATUS.EXPIRED].includes(current_data.status) + ) { + Logger.verbose('Send Payment Expired', 'PaymentTransaction'); + const pdf = await GeneratePdf( + transaction, + InvoiceType.INVOICE_EXPIRED, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.INVOICE_EXPIRED, + pdf, + ); + } + + // change booking date + else if ( + !from_refund && + old_data.booking_date != current_data.booking_date && + [STATUS.SETTLED, STATUS.ACTIVE, STATUS.PENDING].includes( + current_data.status, + ) + ) { + Logger.verbose('Send Change Booking Date', 'PaymentTransaction'); + const pdf = await GeneratePdf( + transaction, + InvoiceType.BOOKING_DATE_CHANGE, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.BOOKING_DATE_CHANGE, + pdf, + ); + } + } catch (error) { + console.log(error); } } } diff --git a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts index 87dd1ee..ad0532a 100644 --- a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts +++ b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts @@ -1,10 +1,10 @@ import * as nodemailer from 'nodemailer'; import * as handlebars from 'handlebars'; -import * as path from 'path'; import * as fs from 'fs'; import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; -export async function sendEmail(receivers, subject) { +export async function sendEmail(receivers, invoiceType, attachment?) { const smtpTransport = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_POST, @@ -13,38 +13,62 @@ export async function sendEmail(receivers, subject) { pass: process.env.EMAIL_TOKEN, }, }); - let templateName = 'payment-confirmation-bank'; for (const receiver of receivers) { - if (receiver.payment_type == TransactionPaymentType.MIDTRANS) - templateName = 'payment-confirmation-midtrans'; + try { + const templateName = getTemplate(receiver.payment_type, invoiceType); + const templatePath = `./assets/email-template/${templateName}.html`; + const templateSource = fs.readFileSync(templatePath, 'utf8'); - let templatePath = path.resolve( - __dirname, - `../email-template/${templateName}.html`, - ); - templatePath = templatePath.replace(/dist/g, 'src'); - const templateSource = fs.readFileSync(templatePath, 'utf8'); + const template = handlebars.compile(templateSource); + const htmlToSend = template(receiver); - const template = handlebars.compile(templateSource); - const htmlToSend = template(receiver); + const emailContext = { + from: process.env.EMAIL_SENDER ?? 'no-reply@weplayground.app', + to: receiver.email, + subject: invoiceType, + html: htmlToSend, + attachDataUrls: true, + attachments: [ + { + filename: `${invoiceType}.pdf`, + content: attachment, + }, + ], + }; - const emailContext = { - from: 'no-reply@eigen.co.id', - to: receiver.email, - subject: subject, - html: htmlToSend, - attachDataUrls: true, - }; + await new Promise((f) => setTimeout(f, 2000)); - await new Promise((f) => setTimeout(f, 2000)); - - smtpTransport.sendMail(emailContext, function (err, data) { - if (err) { - console.log(`Error occurs on send to ${receiver.email}`); - } else { - console.log(`Email sent to ${receiver.email}`); - } - }); + smtpTransport.sendMail(emailContext, function (err) { + if (err) { + console.log(err, `Error occurs on send to ${receiver.email}`); + } else { + console.log(`Email sent to ${receiver.email}`); + } + }); + } catch (error) { + console.log(error, `Error occurs on send to ${receiver.email}`); + } + } +} + +function getTemplate(transactionType, invoiceType) { + if (invoiceType == InvoiceType.BOOKING_DATE_CHANGE) { + return 'change-date-information'; + } else if (invoiceType == InvoiceType.INVOICE_EXPIRED) { + return 'invoice-expired'; + } else if (invoiceType == InvoiceType.REFUND_REQUEST) { + return 'refund-request'; + } else if (invoiceType == InvoiceType.REFUND_CONFIRMATION) { + return 'refund-confirmation'; + } else if (invoiceType == InvoiceType.PAYMENT_CONFIRMATION) { + return 'payment-confirmation'; + } else if ( + invoiceType == InvoiceType.BOOKING_INVOICE && + transactionType != TransactionPaymentType.MIDTRANS + ) { + return 'invoice-bank'; + } else if (invoiceType == InvoiceType.BOOKING_INVOICE) { + return 'invoice-midtrans'; } } diff --git a/src/modules/configuration/mail/infrastructure/mail.controller.ts b/src/modules/configuration/mail/infrastructure/mail.controller.ts new file mode 100644 index 0000000..c2afa9d --- /dev/null +++ b/src/modules/configuration/mail/infrastructure/mail.controller.ts @@ -0,0 +1,55 @@ +import { Controller, Get, Injectable } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Public } from 'src/core/guards'; +import * as path from 'path'; +import * as fs from 'fs'; + +@ApiTags(`email templates`) +@Controller('v1/email-templates') +@Public() +@Injectable() +export class MailTemplateController { + getTemplate(templateName) { + const templatePath = path.join( + __dirname, + '../../../../../', + `src/modules/configuration/mail/domain/email-template/${templateName}.html`, + ); + return fs.readFileSync(templatePath, 'utf8'); + } + + @Get('date-change') + async getDateChange() { + return this.getTemplate('change-date'); + } + + @Get('invoice') + async getBookingInvoice() { + return this.getTemplate('invoice'); + } + + @Get('payment-confirmation-bank') + async getPaymentConfirmation() { + return this.getTemplate('payment-confirmation-bank'); + } + + @Get('payment-confirmation-midtrans') + async getPaymentConfirmationMidtrans() { + return this.getTemplate('payment-confirmation-midtrans'); + } + + @Get('invoice-expired') + async getInvoiceExpired() { + return this.getTemplate('invoice-expired'); + } + + @Get('refund-confirmation') + async getRefundConfirmation() { + return this.getTemplate('refund-confirmation'); + } + + @Get('refund-request') + async getRefundRequest() { + return this.getTemplate('refund-request'); + } +} diff --git a/src/modules/configuration/mail/mail.module.ts b/src/modules/configuration/mail/mail.module.ts index 597fc9c..412b0c3 100644 --- a/src/modules/configuration/mail/mail.module.ts +++ b/src/modules/configuration/mail/mail.module.ts @@ -8,6 +8,8 @@ import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { PaymentTransactionHandler } from './domain/handlers/payment-transaction.handler'; +import { MailTemplateController } from './infrastructure/mail.controller'; +import { PdfMakeManager } from '../export/domain/managers/pdf-make.manager'; @Module({ imports: [ @@ -18,8 +20,10 @@ import { PaymentTransactionHandler } from './domain/handlers/payment-transaction ), CqrsModule, ], - controllers: [], + controllers: [MailTemplateController], providers: [ + PdfMakeManager, + PaymentTransactionHandler, PaymentMethodDataService, TransactionDataService, diff --git a/src/modules/configuration/midtrans/data/services/midtrans.service.ts b/src/modules/configuration/midtrans/data/services/midtrans.service.ts index 5a3d658..713697b 100644 --- a/src/modules/configuration/midtrans/data/services/midtrans.service.ts +++ b/src/modules/configuration/midtrans/data/services/midtrans.service.ts @@ -1,14 +1,28 @@ import { Injectable } from '@nestjs/common'; -import { EventBus } from '@nestjs/cqrs'; import { mappingMidtransTransaction } from '../../domain/usecases/helpers/mapping-transaction.helper'; -const midtransClient = require('midtrans-client'); +import { Snap } from 'midtrans-client'; +import { MidtransStatus } from '../../domain/entities/midtrans-callback.event'; +import { TransactionReadService } from 'src/modules/transaction/transaction/data/services/transaction-read.service'; + +import * as moment from 'moment'; @Injectable() export class MidtransService { - constructor(private eventBus: EventBus) {} + constructor(private transaction: TransactionReadService) {} + + isMoreThan24HoursAgo(dateString) { + const date = moment(dateString, 'YYYY-MM-DD', true); + if (!date.isValid()) { + return false; + } + + const now = moment(); + const diffInHours = now.diff(date, 'hours'); + return diffInHours > 24; + } get midtransInstance() { - return new midtransClient.Snap({ + return new Snap({ isProduction: false, serverKey: process.env.MIDTRANS_SERVER_KEY, clientKey: process.env.MIDTRANS_CLIENT_KEY, @@ -19,6 +33,32 @@ export class MidtransService { return await this.midtransInstance.transaction.status(orderId); } + async syncPendingStatus(): Promise { + const pendingIds = await this.transaction.getPendingOrderId(); + const responses = []; + + for (const transaction of pendingIds) { + const { id, invoice_date } = transaction; + let status; + try { + status = await this.getStatus(id); + } catch (error) { + status = { + order_id: id, + transaction_status: this.isMoreThan24HoursAgo(invoice_date) + ? 'cancel' + : 'pending', + }; + } + responses.push(status); + } + return responses; + } + + async changeStatus(orderId: string, action: MidtransStatus): Promise { + return await this.midtransInstance.transaction[action](orderId); + } + async create(body): Promise { const data = mappingMidtransTransaction(body); return await this.midtransInstance.createTransaction(data); diff --git a/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts b/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts index 0b77294..b42f0e2 100644 --- a/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts +++ b/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts @@ -6,3 +6,10 @@ export interface IEventMidtrans { id: string; data: any; } + +export enum MidtransStatus { + approve = 'approve', + deny = 'deny', + cancel = 'cancel', + expire = 'expire', +} diff --git a/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts b/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts index 0e6cb4c..5bcc510 100644 --- a/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts +++ b/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts @@ -2,7 +2,7 @@ export function mappingMidtransTransaction(transaction) { const item_details = transaction.items?.map((item) => { return { quantity: Number(item.qty), - price: Number(item.total_price), + price: Number(item.total_price) / Number(item.qty), name: item.item_name, category: item.item_category_name, }; diff --git a/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts b/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts index a00867a..e4a84ad 100644 --- a/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts +++ b/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts @@ -1,9 +1,21 @@ -import { Body, Controller, Get, Injectable, Param, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + Body, + Controller, + Get, + Injectable, + Param, + Post, + Query, + UnprocessableEntityException, +} from '@nestjs/common'; +import { ApiQuery, ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/guards'; import { MidtransService } from '../data/services/midtrans.service'; import { EventBus } from '@nestjs/cqrs'; -import { MidtransCallbackEvent } from '../domain/entities/midtrans-callback.event'; +import { + MidtransCallbackEvent, + MidtransStatus, +} from '../domain/entities/midtrans-callback.event'; import { MidtransDto } from './dto/midtrans.dto'; @ApiTags(`midtrans`) @@ -18,7 +30,58 @@ export class MidtransController { @Get(':id/status') async getStatus(@Param('id') id: string) { - return await this.dataService.getStatus(id); + try { + const data = await this.dataService.getStatus(id); + this.eventBus.publishAll([ + new MidtransCallbackEvent({ + id: id, + data: data, + }), + ]); + + return 'Berhasil update status transaksi'; + } catch (error) { + console.log(error.message); + throw new Error('Gagal update status transaksi'); + } + } + + @Get('sync') + async syncStatus() { + try { + const results = await this.dataService.syncPendingStatus(); + + for (const data of results) { + this.eventBus.publishAll([ + new MidtransCallbackEvent({ + id: data.order_id, + data: data, + }), + ]); + } + + return 'Berhasil update status transaksi'; + } catch (error) { + console.log(error.message); + throw new Error('Gagal update status transaksi'); + } + } + + @Get(':id/change-status') + @ApiQuery({ name: 'status', enum: MidtransStatus }) + async cancel( + @Param('id') id: string, + @Query('status') status = MidtransStatus.cancel, + ) { + try { + return await this.dataService.changeStatus(id, status); + } catch (error) { + const data = + error.ApiResponse?.status_message ?? + error.message ?? + 'Gagal update status transaksi'; + throw new UnprocessableEntityException(data); + } } @Post('callback') diff --git a/src/modules/configuration/midtrans/midtrans.module.ts b/src/modules/configuration/midtrans/midtrans.module.ts index 755a639..6735a95 100644 --- a/src/modules/configuration/midtrans/midtrans.module.ts +++ b/src/modules/configuration/midtrans/midtrans.module.ts @@ -3,12 +3,20 @@ import { CqrsModule } from '@nestjs/cqrs'; import { MidtransController } from './infrastructure/midtrans.controller'; import { MidtransService } from './data/services/midtrans.service'; import { Global, Module } from '@nestjs/common'; +import { TransactionReadService } from 'src/modules/transaction/transaction/data/services/transaction-read.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; @Global() @Module({ - imports: [ConfigModule.forRoot(), CqrsModule], + imports: [ + ConfigModule.forRoot(), + CqrsModule, + TypeOrmModule.forFeature([TransactionModel], CONNECTION_NAME.DEFAULT), + ], controllers: [MidtransController], - providers: [MidtransService], + providers: [MidtransService, TransactionReadService], exports: [MidtransService], }) export class MidtransModule {} diff --git a/src/modules/item-related/item-category/domain/usecases/managers/batch-delete-item-category.manager.ts b/src/modules/item-related/item-category/domain/usecases/managers/batch-delete-item-category.manager.ts index 5e0d7fc..ef68712 100644 --- a/src/modules/item-related/item-category/domain/usecases/managers/batch-delete-item-category.manager.ts +++ b/src/modules/item-related/item-category/domain/usecases/managers/batch-delete-item-category.manager.ts @@ -24,7 +24,13 @@ export class BatchDeleteItemCategoryManager extends BaseBatchDeleteManager +{ + constructor( + private seasonService: SeasonPeriodDataService, + private dataService: ItemRateDataService, + ) {} + + async handle(event: ItemCreatedEvent) { + const rates = []; + const seasons = await this.seasonService.getManyByOptions({ + where: { + id: Not(EMPTY_UUID), + }, + }); + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + if (seasons.length) { + for (const season of seasons) { + const rate = new ItemRateModel(); + rate.item_id = event.data.id; + rate.season_period_id = season.id; + rate.price = + Number(event.data.data.total_price) != 0 + ? event.data.data.total_price + : event.data.data.base_price; + + rates.push(rate); + } + } + + // create batch + await this.dataService.createBatch(queryRunner, ItemRateModel, rates); + } +} diff --git a/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts b/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts index 7c29f07..359acdb 100644 --- a/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts +++ b/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts @@ -6,7 +6,7 @@ import { RelationParam, } from 'src/core/modules/domain/entities/base-filter.entity'; import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; -import { STATUS } from 'src/core/strings/constants/base.constants'; +import { DAY, STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class IndexItemRateManager extends BaseIndexManager { @@ -26,18 +26,32 @@ export class IndexItemRateManager extends BaseIndexManager { d <= new Date(this.filterParam.end_date); d.setDate(d.getDate() + 1) ) { - const rate = item['item_rates']?.find( - (rate) => - rate.season_period?.status == STATUS.ACTIVE && - d >= new Date(rate.season_period.start_date) && - d <= new Date(rate.season_period.end_date), - ); + const day: string = DAY[d.getDay()]; + const rates = item['item_rates'] + ?.filter((rate) => { + const days: string[] = rate.season_period.days ?? []; + if (rate.season_period.priority == 2) { + return ( + rate.season_period?.status == STATUS.ACTIVE && + d >= new Date(rate.season_period.start_date) && + d <= new Date(rate.season_period.end_date) && + days.includes(day) + ); + } else { + return ( + rate.season_period?.status == STATUS.ACTIVE && + d >= new Date(rate.season_period.start_date) && + d <= new Date(rate.season_period.end_date) + ); + } + }) + .sort((a, b) => a.season_period.priority - b.season_period.priority); prices.push({ date: new Date(d), - price: rate?.price ?? item.base_price, - season_type: rate?.season_period?.season_type ?? null, - holiday_name: rate?.season_period?.holiday_name ?? null, + price: rates[0]?.price ?? item.base_price, + season_type: rates[0]?.season_period?.season_type ?? null, + holiday_name: rates[0]?.season_period?.holiday_name ?? null, }); } @@ -72,6 +86,7 @@ export class IndexItemRateManager extends BaseIndexManager { get selects(): string[] { return [ `${this.tableName}.id`, + `${this.tableName}.item_type`, `${this.tableName}.status`, `${this.tableName}.created_at`, `${this.tableName}.name`, @@ -91,6 +106,8 @@ export class IndexItemRateManager extends BaseIndexManager { 'season_period.holiday_name', 'season_period.start_date', 'season_period.end_date', + 'season_period.priority', + 'season_period.days', 'season_type.id', 'season_type.name', diff --git a/src/modules/item-related/item-rate/domain/usecases/managers/update-item-rate.manager.ts b/src/modules/item-related/item-rate/domain/usecases/managers/update-item-rate.manager.ts index 963ea85..42c13c5 100644 --- a/src/modules/item-related/item-rate/domain/usecases/managers/update-item-rate.manager.ts +++ b/src/modules/item-related/item-rate/domain/usecases/managers/update-item-rate.manager.ts @@ -39,7 +39,6 @@ export class UpdateItemRateManager extends BaseUpdateManager { return [ { topic: ItemRateUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/item-related/item-rate/infrastructure/dto/filter-item-rate.dto.ts b/src/modules/item-related/item-rate/infrastructure/dto/filter-item-rate.dto.ts index 2118cfa..1966eab 100644 --- a/src/modules/item-related/item-rate/infrastructure/dto/filter-item-rate.dto.ts +++ b/src/modules/item-related/item-rate/infrastructure/dto/filter-item-rate.dto.ts @@ -2,6 +2,7 @@ import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.d import { FilterItemRateEntity } from '../../domain/entities/filter-item-rate.entity'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; +import { ItemType } from 'src/modules/item-related/item-category/constants'; export class FilterItemRateDto extends BaseFilterDto @@ -21,6 +22,16 @@ export class FilterItemRateDto }) end_date: Date; + @ApiProperty({ + type: ['string'], + required: false, + description: `Select ["${Object.values(ItemType)}"]`, + }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + item_types: ItemType[]; + @ApiProperty({ type: ['string'], required: false }) @Transform((body) => { return Array.isArray(body.value) ? body.value : [body.value]; diff --git a/src/modules/item-related/item-rate/item-rate.module.ts b/src/modules/item-related/item-rate/item-rate.module.ts index 5270a03..c0012e3 100644 --- a/src/modules/item-related/item-rate/item-rate.module.ts +++ b/src/modules/item-related/item-rate/item-rate.module.ts @@ -16,16 +16,24 @@ import { UpdateItemRateManager } from './domain/usecases/managers/update-item-ra import { DetailItemRateManager } from './domain/usecases/managers/detail-item-rate.manager'; import { BatchDeleteItemRateManager } from './domain/usecases/managers/batch-delete-item-rate.manager'; import { ItemRateModel } from './data/models/item-rate.model'; +import { SeasonPeriodHolidayHandler } from './domain/usecases/handlers/item-created.handler'; +import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service'; +import { SeasonPeriodModel } from 'src/modules/season-related/season-period/data/models/season-period.model'; @Global() @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([ItemRateModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [ItemRateModel, SeasonPeriodModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [ItemRateDataController, ItemRateReadController], providers: [ + SeasonPeriodHolidayHandler, + IndexItemRateManager, DetailItemRateManager, CreateItemRateManager, @@ -33,6 +41,7 @@ import { ItemRateModel } from './data/models/item-rate.model'; UpdateItemRateManager, BatchDeleteItemRateManager, + SeasonPeriodDataService, ItemRateDataService, ItemRateReadService, diff --git a/src/modules/item-related/item/data/models/item.model.ts b/src/modules/item-related/item/data/models/item.model.ts index 61b645e..a25633e 100644 --- a/src/modules/item-related/item/data/models/item.model.ts +++ b/src/modules/item-related/item/data/models/item.model.ts @@ -22,7 +22,7 @@ export class ItemModel extends BaseStatusModel implements ItemEntity { - @Column('varchar', { name: 'name' }) + @Column('varchar', { name: 'name', unique: true }) name: string; @Column('varchar', { name: 'image_url', nullable: true }) @@ -38,7 +38,7 @@ export class ItemModel @Column('bigint', { name: 'hpp', nullable: true }) hpp: number; - @Column('int', { name: 'sales_margin', nullable: true }) + @Column('decimal', { name: 'sales_margin', nullable: true }) sales_margin: number; @Column('bigint', { name: 'total_price', nullable: true }) @@ -116,4 +116,19 @@ export class ItemModel onUpdate: 'CASCADE', }) gates: GateModel[]; + + // relasi untuk mendapatkan parent bundling + @ManyToMany(() => ItemModel, (model) => model.bundling_parents) + @JoinTable({ + name: 'item_bundlings', + joinColumn: { + name: 'item_id', + referencedColumnName: 'id', + }, + inverseJoinColumn: { + name: 'item_bundling_id', + referencedColumnName: 'id', + }, + }) + bundling_parents: ItemModel[]; } diff --git a/src/modules/item-related/item/data/services/item-data.service.ts b/src/modules/item-related/item/data/services/item-data.service.ts index 367a883..534d542 100644 --- a/src/modules/item-related/item/data/services/item-data.service.ts +++ b/src/modules/item-related/item/data/services/item-data.service.ts @@ -5,13 +5,30 @@ import { InjectRepository } from '@nestjs/typeorm'; import { ItemModel } from '../models/item.model'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { Repository } from 'typeorm'; +import { ItemRateModel } from 'src/modules/item-related/item-rate/data/models/item-rate.model'; @Injectable() export class ItemDataService extends BaseDataService { constructor( @InjectRepository(ItemModel, CONNECTION_NAME.DEFAULT) private repo: Repository, + + @InjectRepository(ItemRateModel, CONNECTION_NAME.DEFAULT) + private repoItemRate: Repository, ) { super(repo); } + + async updateItemRatePrice( + oldPrice: number, + newPrice: number, + itemId: string, + ): Promise { + console.log({ oldPrice, newPrice, itemId }); + + this.repoItemRate.update( + { item_id: itemId, price: oldPrice }, + { price: newPrice }, + ); + } } diff --git a/src/modules/item-related/item/domain/usecases/managers/active-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/active-item.manager.ts index b5640d9..59da47d 100644 --- a/src/modules/item-related/item/domain/usecases/managers/active-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/active-item.manager.ts @@ -36,7 +36,12 @@ export class ActiveItemManager extends BaseUpdateStatusManager { { relation: 'tenant', singleQuery: ['status', '!=', STATUS.ACTIVE], - message: `Failed! Tenant of item must be active first`, + message: `Gagal! tenant tidak aktif`, + }, + { + relation: 'bundling_items', + singleQuery: ['status', '!=', STATUS.ACTIVE], + message: `Gagal! Terdapat item yang belum aktif`, }, ]; } diff --git a/src/modules/item-related/item/domain/usecases/managers/batch-active-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/batch-active-item.manager.ts index 33e08e0..a36460d 100644 --- a/src/modules/item-related/item/domain/usecases/managers/batch-active-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/batch-active-item.manager.ts @@ -7,11 +7,7 @@ import { import { ItemModel } from '../../../data/models/item.model'; import { ItemChangeStatusEvent } from '../../entities/event/item-change-status.event'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; -import { - HttpStatus, - Injectable, - UnprocessableEntityException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() @@ -33,7 +29,12 @@ export class BatchActiveItemManager extends BaseBatchUpdateStatusManager { @@ -16,6 +17,7 @@ export class BatchDeleteItemManager extends BaseBatchDeleteManager { } async validateData(data: ItemEntity): Promise { + await validateRelation(this.dataService, data.id); return; } @@ -24,7 +26,12 @@ export class BatchDeleteItemManager extends BaseBatchDeleteManager { } get validateRelations(): validateRelations[] { - return [{ relation: 'bundling_items' }]; + return [ + { + relation: 'bundling_parents', + message: `Gagal! Item sudah berelasi dengen bundling`, + }, + ]; } get entityTarget(): any { diff --git a/src/modules/item-related/item/domain/usecases/managers/batch-inactive-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/batch-inactive-item.manager.ts index 19285af..ea00174 100644 --- a/src/modules/item-related/item/domain/usecases/managers/batch-inactive-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/batch-inactive-item.manager.ts @@ -8,10 +8,12 @@ import { ItemModel } from '../../../data/models/item.model'; import { ItemChangeStatusEvent } from '../../entities/event/item-change-status.event'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { Injectable } from '@nestjs/common'; +import { validateRelation } from './helpers/validasi-relation.helper'; @Injectable() export class BatchInactiveItemManager extends BaseBatchUpdateStatusManager { - validateData(data: ItemEntity): Promise { + async validateData(data: ItemEntity): Promise { + await validateRelation(this.dataService, data.id); return; } @@ -24,7 +26,7 @@ export class BatchInactiveItemManager extends BaseBatchUpdateStatusManager { { relation: 'tenant', singleQuery: ['status', '!=', STATUS.ACTIVE], - message: `Failed! Tenant of item must be active first`, + message: `Gagal! tenant tidak aktif`, + }, + { + relation: 'bundling_items', + singleQuery: ['status', '!=', STATUS.ACTIVE], + message: `Gagal! Terdapat item yang belum aktif`, }, ]; } diff --git a/src/modules/item-related/item/domain/usecases/managers/create-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/create-item.manager.ts index aebe725..2b9d9e0 100644 --- a/src/modules/item-related/item/domain/usecases/managers/create-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/create-item.manager.ts @@ -33,7 +33,7 @@ export class CreateItemManager extends BaseCreateManager { } get uniqueColumns(): columnUniques[] { - return []; + return [{ column: 'name' }]; } get eventTopics(): EventTopics[] { diff --git a/src/modules/item-related/item/domain/usecases/managers/delete-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/delete-item.manager.ts index 51db838..cb56206 100644 --- a/src/modules/item-related/item/domain/usecases/managers/delete-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/delete-item.manager.ts @@ -7,6 +7,7 @@ import { } from 'src/core/strings/constants/interface.constants'; import { ItemModel } from '../../../data/models/item.model'; import { ItemDeletedEvent } from '../../entities/event/item-deleted.event'; +import { validateRelation } from './helpers/validasi-relation.helper'; @Injectable() export class DeleteItemManager extends BaseDeleteManager { @@ -15,6 +16,7 @@ export class DeleteItemManager extends BaseDeleteManager { } async validateProcess(): Promise { + await validateRelation(this.dataService, this.dataId); return; } @@ -27,7 +29,12 @@ export class DeleteItemManager extends BaseDeleteManager { } get validateRelations(): validateRelations[] { - return [{ relation: 'bundling_items' }]; + return [ + { + relation: 'bundling_parents', + message: `Gagal! Item sudah berelasi dengen bundling`, + }, + ]; } get entityTarget(): any { diff --git a/src/modules/item-related/item/domain/usecases/managers/helpers/validasi-relation.helper.ts b/src/modules/item-related/item/domain/usecases/managers/helpers/validasi-relation.helper.ts new file mode 100644 index 0000000..c0e8d64 --- /dev/null +++ b/src/modules/item-related/item/domain/usecases/managers/helpers/validasi-relation.helper.ts @@ -0,0 +1,22 @@ +import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { In } from 'typeorm'; + +export async function validateRelation(dataService, id) { + const haveRelation = await dataService.getOneByOptions({ + where: { + status: In([STATUS.ACTIVE]), + bundling_items: { + id: id, + }, + }, + }); + + if (haveRelation) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagal! data telah digunakan untuk bundling`, + error: 'Unprocessable Entity', + }); + } +} diff --git a/src/modules/item-related/item/domain/usecases/managers/inactive-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/inactive-item.manager.ts index aef9e52..fb21936 100644 --- a/src/modules/item-related/item/domain/usecases/managers/inactive-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/inactive-item.manager.ts @@ -7,6 +7,7 @@ import { } from 'src/core/strings/constants/interface.constants'; import { ItemModel } from '../../../data/models/item.model'; import { ItemChangeStatusEvent } from '../../entities/event/item-change-status.event'; +import { validateRelation } from './helpers/validasi-relation.helper'; @Injectable() export class InactiveItemManager extends BaseUpdateStatusManager { @@ -15,6 +16,7 @@ export class InactiveItemManager extends BaseUpdateStatusManager { } async validateProcess(): Promise { + await validateRelation(this.dataService, this.dataId); return; } @@ -27,7 +29,7 @@ export class InactiveItemManager extends BaseUpdateStatusManager { } get validateRelations(): validateRelations[] { - return [{ relation: 'bundling_items' }]; + return []; } get entityTarget(): any { diff --git a/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts b/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts index c18bc97..9306d0f 100644 --- a/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts @@ -19,6 +19,16 @@ export class IndexItemRatesManager extends BaseIndexManager { } async afterProcess(): Promise { + this.result.data?.map((item) => { + const item_price = + Number(item['item']?.total_price ?? 0) == 0 + ? item['item']?.total_price + : item['item']?.base_price; + + Object.assign(item, { + price: item.price ?? item_price, + }); + }); return; } @@ -28,7 +38,7 @@ export class IndexItemRatesManager extends BaseIndexManager { joinRelations: [], // relation join and select (relasi yang ingin ditampilkan), - selectRelations: ['season_period', 'season_period.season_type'], + selectRelations: ['season_period', 'season_period.season_type', 'item'], // relation yang hanya ingin dihitung (akan return number) countRelations: [], @@ -41,6 +51,10 @@ export class IndexItemRatesManager extends BaseIndexManager { `${this.tableName}.item_id`, `${this.tableName}.price`, + 'item.id', + 'item.total_price', + 'item.base_price', + `season_period.id`, `season_period.priority`, `season_period.created_at`, diff --git a/src/modules/item-related/item/domain/usecases/managers/update-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/update-item.manager.ts index eabe32c..12d972a 100644 --- a/src/modules/item-related/item/domain/usecases/managers/update-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/update-item.manager.ts @@ -11,15 +11,26 @@ import { @Injectable() export class UpdateItemManager extends BaseUpdateManager { + protected oldBasePrice: number; + async validateProcess(): Promise { return; } async beforeProcess(): Promise { + this.oldBasePrice = this.oldData.base_price; return; } async afterProcess(): Promise { + const newBasePrice = this.result.base_price; + const itemId = this.result.id; + + await this.dataService.updateItemRatePrice( + this.oldBasePrice, + newBasePrice, + itemId, + ); return; } @@ -39,7 +50,6 @@ export class UpdateItemManager extends BaseUpdateManager { return [ { topic: ItemUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/reports/report-bookmark/report-bookmark.service.ts b/src/modules/reports/report-bookmark/report-bookmark.service.ts index 7bd7823..a09fc6d 100644 --- a/src/modules/reports/report-bookmark/report-bookmark.service.ts +++ b/src/modules/reports/report-bookmark/report-bookmark.service.ts @@ -181,6 +181,8 @@ export class ReportBookmarkService extends BaseReportService { const data = await this.getOne(id); const creator_id = data.creator_id; const type = data.type; + const group_name = data.group_name; + const unique_name = data.unique_name; await this.repo .createQueryBuilder() @@ -188,6 +190,8 @@ export class ReportBookmarkService extends BaseReportService { .set({ applied: false }) .where((query) => { query.andWhere(`id != :id`, { id }); + query.andWhere(`group_name = :group_name`, { group_name }); + query.andWhere(`unique_name = :unique_name`, { unique_name }); query.andWhere(`creator_id = :creator_id`, { creator_id }); query.andWhere(`type = :type`, { type }); }) diff --git a/src/modules/reports/shared/configs/transaction-report/configs/booking.ts b/src/modules/reports/shared/configs/transaction-report/configs/booking.ts index cbc8a36..ffbcab9 100644 --- a/src/modules/reports/shared/configs/transaction-report/configs/booking.ts +++ b/src/modules/reports/shared/configs/transaction-report/configs/booking.ts @@ -16,6 +16,13 @@ export default { table_schema: `transactions AS main LEFT JOIN refunds refund ON refund.transaction_id = main.id`, main_table_alias: 'main', + whereDefaultConditions: [ + { + column: 'main.type', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [TransactionType.ADMIN, TransactionType.ONLINE], + }, + ], defaultOrderBy: [], lowLevelOrderBy: [], filter_period_config: { @@ -41,7 +48,7 @@ export default { { column: 'main__no_of_group', query: 'main.no_of_group', - label: 'Total Group', + label: '#Visitor', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.NUMBER, }, @@ -159,7 +166,7 @@ export default { { column: 'main__updated_at', query: 'main.updated_at', - label: 'Tgl Update', + label: 'Tgl. Update', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.DATE_EPOCH, }, @@ -178,6 +185,12 @@ export default { STATUS.REJECTED, ], }, + { + filed_label: 'Tgl. Booking', + filter_column: 'main__booking_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, { filed_label: 'Sumber', filter_column: 'main__type', @@ -189,5 +202,77 @@ export default { TransactionType.ONLINE, ], }, + { + filed_label: 'Tipe', + filter_column: 'main__customer_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kontak', + filter_column: 'main__customer_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tipe Pembayaran', + filter_column: 'main__payment_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Bank', + filter_column: 'main__payment_type_method_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Invoice', + filter_column: 'main__invoice_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kode Invoice', + filter_column: 'main__invoice_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Settlement', + filter_column: 'main__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Request Refund', + filter_column: 'refund__request_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kode Refund', + filter_column: 'refund__code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Refund', + filter_column: 'refund__refund_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Dibuat Oleh', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Update', + filter_column: 'main__updated_at', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_EPOCH, + }, ], }; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts b/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts index 48616b1..f29b020 100644 --- a/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts +++ b/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts @@ -1,40 +1,101 @@ -import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { PosLogType } from 'src/modules/configuration/log/domain/entities/pos-log.entity'; +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; import { ReportConfigEntity } from '../../../entities/report-config.entity'; export default { group_name: REPORT_GROUP.transaction_report, unique_name: `${REPORT_GROUP.transaction_report}__cash_withdrawals`, label: 'Penarikan Kas', - table_schema: 'season_types main', + table_schema: 'logs_pos main', main_table_alias: 'main', defaultOrderBy: [], lowLevelOrderBy: [], filter_period_config: { hidden: true, }, - + whereDefaultConditions: [ + { + column: 'main.type', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [PosLogType.cash_witdrawal], + }, + ], column_configs: [ { - column: 'main__created_at', + column: 'main__date', query: 'main.created_at', - label: 'Created Date', + label: 'Tanggal', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.DATE_EPOCH, + date_format: 'DD-MM-YYYY', }, { - column: 'main__updated_at', - query: 'main.updated_at', - label: 'Updated Date', + column: 'main__time', + query: 'main.created_at', + label: 'Jam', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.DATE_EPOCH, + date_format: 'HH:mm', }, { - column: 'main__name', - query: 'main.name', - label: 'Name', + column: 'main__drawn_by_name', + query: 'main.drawn_by_name', + label: 'Nama Penarik', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.TEXT, }, + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Nama Kasir', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__pos_number', + query: 'main.pos_number', + label: 'No. PoS', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__total_balance', + query: 'main.total_balance', + label: 'Total Penarikan', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + ], + filter_configs: [ + { + filed_label: 'Tanggal', + filter_column: 'main__date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_EPOCH, + }, + { + filed_label: 'Nama Penarik', + filter_column: 'main__drawn_by_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Kasir', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'No. PoS', + filter_column: 'main__pos_number', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, ], - filter_configs: [], }; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts b/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts index 5ad5d52..c0db8da 100644 --- a/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts +++ b/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts @@ -1,40 +1,89 @@ -import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { PosLogType } from 'src/modules/configuration/log/domain/entities/pos-log.entity'; +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; import { ReportConfigEntity } from '../../../entities/report-config.entity'; export default { group_name: REPORT_GROUP.transaction_report, unique_name: `${REPORT_GROUP.transaction_report}__cashier_log`, label: 'Kasir Log', - table_schema: 'season_types main', + table_schema: 'logs_pos main', main_table_alias: 'main', defaultOrderBy: [], lowLevelOrderBy: [], filter_period_config: { hidden: true, }, - + whereDefaultConditions: [ + { + column: 'main.type', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [PosLogType.login, PosLogType.logout], + }, + ], column_configs: [ { - column: 'main__created_at', + column: 'main__date', query: 'main.created_at', - label: 'Created Date', + label: 'Tanggal', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.DATE_EPOCH, + date_format: 'DD-MM-YYYY', }, { - column: 'main__updated_at', - query: 'main.updated_at', - label: 'Updated Date', + column: 'main__time', + query: 'main.created_at', + label: 'Jam', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.DATE_EPOCH, + date_format: 'HH:mm', }, { - column: 'main__name', - query: 'main.name', - label: 'Name', + column: 'main__type', + query: 'main.type', + label: 'Tipe', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Nama Staff', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__pos_number', + query: 'main.pos_number', + label: 'No. PoS', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.TEXT, }, ], - filter_configs: [], + filter_configs: [ + { + filed_label: 'Tanggal', + filter_column: 'main__date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_EPOCH, + }, + { + filed_label: 'Tipe', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [PosLogType.login, PosLogType.logout], + }, + { + filed_label: 'Nama Staff', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + ], }; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/giving-discounts.ts b/src/modules/reports/shared/configs/transaction-report/configs/giving-discounts.ts new file mode 100644 index 0000000..99e1a3e --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/giving-discounts.ts @@ -0,0 +1,192 @@ +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__giving_discounts`, + label: 'Pemberian Diskon', + table_schema: `transactions main + LEFT JOIN vip_codes vip ON vip.id::text = main.discount_code_id::text + LEFT JOIN vip_categories vip_category ON vip_category.id::text = vip.vip_category_id::text + LEFT JOIN users account ON account.id::text = vip.creator_id::text + LEFT JOIN user_privileges privilege ON privilege.id::text = account.user_privilege_id::text + LEFT JOIN season_types s_period_type ON s_period_type.id::text = main.season_period_type_id`, + main_table_alias: 'main', + whereDefaultConditions: [ + { + column: 'main.status', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [STATUS.SETTLED], + }, + { + column: 'main.type', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [TransactionType.COUNTER], + }, + ], + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__settlement_date', + query: 'main.settlement_date', + label: 'Tanggal Transaksi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 's_period_type__name', + query: 's_period_type.name', + label: 'Tipe Rate', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__invoice_code', + query: 'main.invoice_code', + label: 'Kode Transaksi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_total_profit', + query: 'main.payment_total_profit', + label: 'Total Transaksi', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__discount_code', + query: 'main.discount_code', + label: 'Kode Diskon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__discount_percentage', + query: 'main.discount_percentage', + label: 'Diskon (%)', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__discount_value', + query: 'main.discount_value', + label: 'Diskon (IDR)', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__payment_total', + query: 'main.payment_total', + label: 'Total Pembayaran', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'vip__creator_name', + query: 'vip.creator_name', + label: 'Pemberi Diskon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'privilege__name', + query: 'privilege.name', + label: 'Kategori Pemberi Diskon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_counter_no', + query: 'main.creator_counter_no', + label: 'No. PoS', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_name', + query: 'main.customer_name', + label: 'Nama Pelanggan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_phone', + query: 'main.customer_phone', + label: 'Telepon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Kasir', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [ + { + filed_label: 'Tanggal Transaksi', + filter_column: 'main__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Tipe Rate', + filter_column: 's_period_type__name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Transaksi', + filter_column: 'main__invoice_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Diskon', + filter_column: 'main__discount_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Pemberi Diskon', + filter_column: 'vip__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'No. Pos', + filter_column: 'main__creator_counter_no', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Pelanggan', + filter_column: 'main__customer_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kasir', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + ], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/income-per-item.ts b/src/modules/reports/shared/configs/transaction-report/configs/income-per-item.ts new file mode 100644 index 0000000..ef2e131 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/income-per-item.ts @@ -0,0 +1,299 @@ +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__income_per_item`, + label: 'Pendapatan Per Item', + table_schema: `transactions main + LEFT JOIN transaction_items tr_item ON tr_item.transaction_id::text = main.id::text + LEFT JOIN refunds refund ON refund.transaction_id = main.id + LEFT JOIN items item ON item.id::text = tr_item.item_id::text + LEFT JOIN users tenant ON tenant.id::text = item.tenant_id::text + LEFT JOIN refund_items refund_item ON refund_item.refund_item_id::text = tr_item.item_id::text`, + main_table_alias: 'main', + whereDefaultConditions: [ + { + column: 'main.status', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [STATUS.SETTLED, STATUS.REFUNDED, STATUS.PROCESS_REFUND], + }, + ], + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__settlement_date', + query: 'main.settlement_date', + label: 'Tanggal Pendapatan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'item_owner', + query: `CASE WHEN tenant.name is not null THEN tenant.name ELSE 'Company' END`, + label: 'Kepemilikan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__type', + query: 'main.type', + label: 'Sumber', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__invoice_code', + query: 'main.invoice_code', + label: 'Kode Booking', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_code', + query: 'main.payment_code', + label: 'Kode Pembayaran', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__item_category_name', + query: 'tr_item.item_category_name', + label: 'Kategori Item', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__item_name', + query: 'tr_item.item_name', + label: 'Nama Item', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_type', + query: 'main.customer_type', + label: 'Tipe Pelanggan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_counter_no', + query: 'main.creator_counter_no', + label: 'No.PoS', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__qty', + query: 'tr_item.qty', + label: 'Qty', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.NUMBER, + }, + { + column: 'tr_item__total_hpp', + query: 'tr_item.total_hpp', + label: 'Total HPP', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + // TODO => tambahkan total dpp per item + // TODO => tambahkan total tax + { + column: 'tr_item__total_price', + query: 'tr_item.total_price', + label: 'Total Penjualan', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'refund__refund_date', + query: 'refund.refund_date', + label: 'Tanggal Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'refund__status', + query: 'refund.status', + label: 'Status Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund__code', + query: 'refund.code', + label: 'Kode Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund_item__qty_refund', + query: 'refund_item.qty_refund', + label: 'Qty Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.NUMBER, + }, + { + column: 'refund_item__refund_total', + query: '(refund_item.refund_total * -1)', + label: 'Total Pengembalian', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + + { + column: 'transaction_balance', + query: `CASE WHEN refund.id is null THEN tr_item.total_price ELSE tr_item.total_price - refund_item.refund_total END`, + label: 'Balance', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'tr_item__item_tenant_share_margin', + query: 'tr_item.item_tenant_share_margin', + label: 'Profile Share (IDR)', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'tenant_income', + query: 'tr_item.total_price - tr_item.item_tenant_share_margin', + label: 'Pendapatan Tenant', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.CURRENCY, + }, + + { + column: 'main__customer_name', + query: 'main.customer_name', + label: 'Nama Pelanggan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_description', + query: 'main.customer_description', + label: 'Deskripsi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_phone', + query: 'main.customer_phone', + label: 'Telepon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Dibuat Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [ + { + filed_label: 'Tanggal Pendapatan', + filter_column: 'main__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kepemilikan', + filter_column: 'item_owner', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Sumber', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [...Object.values(TransactionType)], + }, + { + filed_label: 'Kode Booking', + filter_column: 'main__invoice_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Pembayaran', + filter_column: 'main__payment_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kategori Item', + filter_column: 'tr_item__item_category_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Item', + filter_column: 'tr_item__item_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tipe Pelanggan', + filter_column: 'main__customer_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'No. PoS', + filter_column: 'main__creator_counter_no', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tanggal Pengembalian', + filter_column: 'refund__refund_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kode Pengembalian', + filter_column: 'refund__code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Pelanggan', + filter_column: 'main__customer_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Bank/Issuer', + filter_column: 'main__payment_type_method_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Dibuat Oleh', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + ], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/income.ts b/src/modules/reports/shared/configs/transaction-report/configs/income.ts index 551b235..45f4d5d 100644 --- a/src/modules/reports/shared/configs/transaction-report/configs/income.ts +++ b/src/modules/reports/shared/configs/transaction-report/configs/income.ts @@ -6,13 +6,30 @@ import { REPORT_GROUP, } from '../../../constant'; import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; export default { group_name: REPORT_GROUP.transaction_report, unique_name: `${REPORT_GROUP.transaction_report}__income`, label: 'Pendapatan', - table_schema: 'season_types main', + table_schema: `transactions main + LEFT JOIN season_types s_period_type ON s_period_type.id::text = main.season_period_type_id + LEFT JOIN refunds refund ON refund.transaction_id = main.id and refund.status != 'cancel' + LEFT JOIN vip_codes vip ON vip.id::text = main.discount_code_id::text + LEFT JOIN ( + select item.transaction_id, sum(item.total_hpp) AS total_hpp_item + from transaction_items item + group by item.transaction_id +) item ON item.transaction_id = main.id`, main_table_alias: 'main', + whereDefaultConditions: [ + { + column: 'main.status', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [STATUS.SETTLED, STATUS.REFUNDED, STATUS.PROCESS_REFUND], + }, + ], defaultOrderBy: [], lowLevelOrderBy: [], filter_period_config: { @@ -21,47 +38,309 @@ export default { column_configs: [ { - column: 'main__created_at', - query: 'main.created_at', - label: 'Created Date', + column: 'main__settlement_date', + query: 'main.settlement_date', + label: 'Tanggal Pendapatan', type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.DATE_EPOCH, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', }, { - column: 'main__updated_at', - query: 'main.updated_at', - label: 'Updated Date', + column: 'main__type', + query: 'main.type', + label: 'Sumber', type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.DATE_EPOCH, + format: DATA_FORMAT.TEXT, }, { - column: 'main__name', - query: 'main.name', - label: 'Name', + column: 's_period_type__name', + query: 's_period_type.name', + label: 'Tipe Rate', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__invoice_code', + query: 'main.invoice_code', + label: 'Kode Booking', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_code', + query: 'main.payment_code', + label: 'Kode Pembayaran', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_type', + query: 'main.customer_type', + label: 'Tipe Pelanggan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_counter_no', + query: 'main.creator_counter_no', + label: 'No.PoS', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__no_of_group', + query: 'main.no_of_group', + label: '#Visitor', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'item__total_hpp_item', + query: 'item.total_hpp_item', + label: 'Total HPP', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__reconciliation_mdr', + query: 'main.reconciliation_mdr', + label: 'MDR', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__payment_total_dpp', + query: 'main.payment_total_dpp', + label: 'DPP', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__payment_total_tax', + query: 'main.payment_total_tax', + label: 'Total Pajak', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__discount_percentage', + query: 'main.discount_percentage', + label: 'Diskon (%)', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.NUMBER, + }, + { + column: 'main__discount_value', + query: 'main.discount_value', + label: 'Diskon (IDR)', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__payment_total', + query: 'main.payment_total', + label: 'Total Penjualan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'refund__refund_date', + query: 'refund.refund_date', + label: 'Tanggal Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'refund__status', + query: 'refund.status', + label: 'Status Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund__code', + query: 'refund.code', + label: 'Kode Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund__refund_total', + query: '(refund.refund_total * -1)', + label: 'Total Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'transaction_balance', + query: `CASE WHEN refund.id is null THEN main.payment_total ELSE main.payment_total - refund.refund_total END`, + label: 'Balance', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__discount_code', + query: 'main.discount_code', + label: 'Kode Diskon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'vip__creator_name', + query: 'vip.creator_name', + label: 'Diberikan Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_name', + query: 'main.customer_name', + label: 'Nama Pelanggan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_description', + query: 'main.customer_description', + label: 'Deskripsi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_phone', + query: 'main.customer_phone', + label: 'Telepon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_type_method_name', + query: 'main.payment_type_method_name', + label: 'Bank/Issuer', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_card_information', + query: 'main.payment_card_information', + label: 'Information', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Penjualan Dibuat Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__reconciliation_confirm_by', + query: 'main.reconciliation_confirm_by', + label: 'Direkonsiliasi Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund__creator_name', + query: 'refund.creator_name', + label: 'Pengembalian Dibuat Oleh', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.TEXT, }, ], filter_configs: [ { - filed_label: 'Name', - filter_column: 'main__name', - field_type: FILTER_FIELD_TYPE.select, - filter_type: FILTER_TYPE.TEXT_IN_MEMBER, - select_data_source_url: '/v1/season-types', - select_custom_options: [], - select_label_key: 'name', - select_value_key: 'name', + filed_label: 'Tanggal Pendapatan', + filter_column: 'main__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, }, { - filed_label: 'Status', - filter_column: 'main__status', - field_type: FILTER_FIELD_TYPE.input_text, - filter_type: FILTER_TYPE.TEXT_EQUAL, - // select_data_source_url: '/v1/season-types', - // select_custom_options: [], - // select_label_key: 'code', - // select_value_key: 'code', + filed_label: 'Sumber', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [...Object.values(TransactionType)], + }, + { + filed_label: 'Tipe Rate', + filter_column: 's_period_type__name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Booking', + filter_column: 'main__invoice_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Pembayaran', + filter_column: 'main__payment_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tipe Pelanggan', + filter_column: 'main__customer_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'No. PoS', + filter_column: 'main__creator_counter_no', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tanggal Pengembalian', + filter_column: 'refund__refund_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kode Pengembalian', + filter_column: 'refund__code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Diskon', + filter_column: 'main__discount_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Pelanggan', + filter_column: 'main__customer_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Bank/Issuer', + filter_column: 'main__payment_type_method_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Penjualan Dibuat Oleh', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Direkonsiliasi Oleh', + filter_column: 'main__reconciliation_confirm_by', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Pengembalian Dibuat Oleh', + filter_column: 'refund__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, }, ], }; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/reconciliation.ts b/src/modules/reports/shared/configs/transaction-report/configs/reconciliation.ts new file mode 100644 index 0000000..ed553c0 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/reconciliation.ts @@ -0,0 +1,188 @@ +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__reconciliation`, + label: 'Rekonsiliasi', + table_schema: `transactions main + LEFT JOIN payment_methods payment ON payment.id::text = main.payment_type_method_id::text`, + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + column_configs: [ + { + column: 'main__reconciliation_status', + query: 'main.reconciliation_status', + label: 'Status', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.STATUS, + }, + { + column: 'main__type', + query: 'main.type', + label: 'Sumber', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__settlement_date', + query: 'main.settlement_date', + label: 'Tgl. Pendapatan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'main__reconciliation_confirm_date', + query: 'main.reconciliation_confirm_date', + label: 'Tgl. Konfirmasi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'main__payment_code_reference', + query: 'main.payment_code_reference', + label: 'Referensi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_type', + query: 'main.payment_type', + label: 'Metode Pembayaran', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_type_method_name', + query: 'main.payment_type_method_name', + label: 'Bank/Issuer', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'payment__account_number', + query: 'payment.account_number', + label: 'Account No.', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_total', + query: 'main.payment_total', + label: 'Total', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__reconciliation_mdr', + query: 'main.reconciliation_mdr', + label: 'MDR', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'cashier', + query: `CASE WHEN main.type = 'counter' THEN main.creator_name END`, + label: 'Kasir', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__reconciliation_confirm_by', + query: 'main.reconciliation_confirm_by', + label: 'Dikonfirmasi Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [ + { + filed_label: 'Status', + filter_column: 'main__reconciliation_status', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [ + STATUS.PENDING, + STATUS.CONFIRMED, + STATUS.REJECTED, + ], + }, + { + filed_label: 'Sumber', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [...Object.values(TransactionType)], + }, + { + filed_label: 'Tgl. Pendapatan', + filter_column: 'main__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Tgl. Konfirmasi', + filter_column: 'main__reconciliation_confirm_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Referensi', + filter_column: 'main__payment_code_reference', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Metode Pembayaran', + filter_column: 'main__payment_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Issuer', + filter_column: 'main__payment_type_method_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Account No.', + filter_column: 'payment__account_number', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kasir', + filter_column: 'cashier', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Dikonfirmasi Oleh', + filter_column: 'main__reconciliation_confirm_by', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + ], + + whereCondition(filterModel) { + const statusFilter = filterModel?.main__reconciliation_status?.filter ?? []; + if (statusFilter.length === 0) { + return [`main.reconciliation_status NOT IN ('${STATUS.DRAFT}')`]; + } + return []; + }, +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts b/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts index d274ed0..d0b2322 100644 --- a/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts +++ b/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts @@ -7,6 +7,7 @@ import { REPORT_GROUP, } from '../../../constant'; import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { RefundType } from 'src/modules/transaction/refund/constants'; export default { group_name: REPORT_GROUP.transaction_report, @@ -29,6 +30,13 @@ export default { type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.STATUS, }, + { + column: 'main__type', + query: 'main.type', + label: 'Tipe Refund', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, { column: 'main__code', query: 'main.code', @@ -53,15 +61,8 @@ export default { date_format: 'DD/MM/YYYY', }, { - column: 'main__type', - query: 'main.type', - label: 'Tipe Refund', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.TEXT, - }, - { - column: 'tr__invoice_code', - query: 'tr.invoice_code', + column: 'tr__payment_code', + query: 'tr.payment_code', label: 'Kode Settlement', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.TEXT, @@ -89,16 +90,30 @@ export default { format: DATA_FORMAT.CURRENCY, }, { - column: 'tr__payment_type', - query: 'tr.payment_type', - label: 'Tipe Pembayaran', + column: 'main__bank_name', + query: 'main.bank_name', + label: 'Bank Tujuan', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.TEXT, }, { - column: 'tr__payment_type_method_name', - query: 'tr.payment_type_method_name', - label: 'Bank', + column: 'main__bank_account_number', + query: 'main.bank_account_number', + label: 'No. Rek. Tujuan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__bank_account_name', + query: 'main.bank_account_name', + label: 'Atas Nama', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__payment_type', + query: 'tr.payment_type', + label: 'Tipe Pembayaran', type: DATA_TYPE.DIMENSION, format: DATA_FORMAT.TEXT, }, @@ -152,5 +167,90 @@ export default { STATUS.REJECTED, ], }, + { + filed_label: 'Tipe Refund', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [...Object.values(RefundType)], + }, + { + filed_label: 'Kode', + filter_column: 'main__code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Permintaan', + filter_column: 'main__request_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Tgl. Refund', + filter_column: 'main__refund_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kode Settlement', + filter_column: 'tr__payment_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Settlement', + filter_column: 'tr__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Bank Tujuan', + filter_column: 'main__bank_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'No. Rek. Tujuan', + filter_column: 'main__bank_account_number', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Atas Nama', + filter_column: 'main__bank_account_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tipe Pembayaran', + filter_column: 'tr__payment_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kontak', + filter_column: 'tr__customer_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Telepon', + filter_column: 'tr__customer_phone', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Dibuat Oleh', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tgl. Update', + filter_column: 'main__updated_at', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_EPOCH, + }, ], }; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/revenue-per-item.ts b/src/modules/reports/shared/configs/transaction-report/configs/revenue-per-item.ts deleted file mode 100644 index 9145830..0000000 --- a/src/modules/reports/shared/configs/transaction-report/configs/revenue-per-item.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; -import { ReportConfigEntity } from '../../../entities/report-config.entity'; - -export default { - group_name: REPORT_GROUP.transaction_report, - unique_name: `${REPORT_GROUP.transaction_report}__revenue_per_item`, - label: 'Pendapatan per Item', - table_schema: 'season_types main', - main_table_alias: 'main', - defaultOrderBy: [], - lowLevelOrderBy: [], - filter_period_config: { - hidden: true, - }, - - column_configs: [ - { - column: 'main__created_at', - query: 'main.created_at', - label: 'Created Date', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.DATE_EPOCH, - }, - { - column: 'main__updated_at', - query: 'main.updated_at', - label: 'Updated Date', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.DATE_EPOCH, - }, - { - column: 'main__name', - query: 'main.name', - label: 'Name', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.TEXT, - }, - ], - filter_configs: [], -}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/sales-qty-per-item.ts b/src/modules/reports/shared/configs/transaction-report/configs/sales-qty-per-item.ts deleted file mode 100644 index d3a5e1a..0000000 --- a/src/modules/reports/shared/configs/transaction-report/configs/sales-qty-per-item.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; -import { ReportConfigEntity } from '../../../entities/report-config.entity'; - -export default { - group_name: REPORT_GROUP.transaction_report, - unique_name: `${REPORT_GROUP.transaction_report}__sales_qty_per_item`, - label: 'Qty Penjualan per Item', - table_schema: 'season_types main', - main_table_alias: 'main', - defaultOrderBy: [], - lowLevelOrderBy: [], - filter_period_config: { - hidden: true, - }, - - column_configs: [ - { - column: 'main__created_at', - query: 'main.created_at', - label: 'Created Date', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.DATE_EPOCH, - }, - { - column: 'main__updated_at', - query: 'main.updated_at', - label: 'Updated Date', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.DATE_EPOCH, - }, - { - column: 'main__name', - query: 'main.name', - label: 'Name', - type: DATA_TYPE.DIMENSION, - format: DATA_FORMAT.TEXT, - }, - ], - filter_configs: [], -}; diff --git a/src/modules/reports/shared/configs/transaction-report/index.ts b/src/modules/reports/shared/configs/transaction-report/index.ts index d711420..1f3b73a 100644 --- a/src/modules/reports/shared/configs/transaction-report/index.ts +++ b/src/modules/reports/shared/configs/transaction-report/index.ts @@ -1,23 +1,25 @@ import { ReportConfigEntity } from '../../entities/report-config.entity'; import IncomeReport from './configs/income'; -import RevenuePerItemReport from './configs/revenue-per-item'; -import SalesQtyPerItemReport from './configs/sales-qty-per-item'; +import IncomeReportPerItem from './configs/income-per-item'; +import GivingDiscount from './configs/giving-discounts'; import VisitorsPerRideReport from './configs/visitors-per-ride'; import TimePerRideReport from './configs/time-per-ride'; import BookingReport from './configs/booking'; import RefundsReport from './configs/refunds'; import CashierLogReport from './configs/cashier-log'; import CashWithdrawalsReport from './configs/cash-withdrawals'; +import ReconciliationReport from './configs/reconciliation'; export const TransactionReportConfig: ReportConfigEntity[] = [ - // IncomeReport, - // RevenuePerItemReport, - // SalesQtyPerItemReport, + IncomeReport, + IncomeReportPerItem, + GivingDiscount, // VisitorsPerRideReport, // TimePerRideReport, BookingReport, RefundsReport, - // CashierLogReport, - // CashWithdrawalsReport, + CashierLogReport, + CashWithdrawalsReport, + ReconciliationReport, ]; diff --git a/src/modules/reports/shared/entities/report-config.entity.ts b/src/modules/reports/shared/entities/report-config.entity.ts index bca0f5c..71598b8 100644 --- a/src/modules/reports/shared/entities/report-config.entity.ts +++ b/src/modules/reports/shared/entities/report-config.entity.ts @@ -37,11 +37,11 @@ export interface ReportConfigEntity { table_schema: string; main_table_alias?: string; - customVirtualTableSchema?( - filterModel: any, - findQueryConfig: (column: string) => string, - createFilterSql: (key: string, item: any) => string, - ): string; + // customVirtualTableSchema?( + // filterModel: any, + // findQueryConfig: (column: string) => string, + // createFilterSql: (key: string, item: any) => string, + // ): string; whereCondition?(filterModel: any): string[]; whereDefaultConditions?: { column: string; diff --git a/src/modules/season-related/season-period/domain/entities/filter-season-period.entity.ts b/src/modules/season-related/season-period/domain/entities/filter-season-period.entity.ts index 0dc158d..68c36d1 100644 --- a/src/modules/season-related/season-period/domain/entities/filter-season-period.entity.ts +++ b/src/modules/season-related/season-period/domain/entities/filter-season-period.entity.ts @@ -4,6 +4,8 @@ import { EnumDays } from '../../constants'; export interface FilterSeasonPeriodEntity extends BaseFilterEntity { start_date: Date; end_date: Date; + season_type_ids: string[]; + holiday_names: string[]; days: EnumDays[]; } diff --git a/src/modules/season-related/season-period/domain/usecases/handlers/season-period-created.handler.ts b/src/modules/season-related/season-period/domain/usecases/handlers/season-period-created.handler.ts index f2ba866..922a66e 100644 --- a/src/modules/season-related/season-period/domain/usecases/handlers/season-period-created.handler.ts +++ b/src/modules/season-related/season-period/domain/usecases/handlers/season-period-created.handler.ts @@ -13,7 +13,6 @@ export class SeasonPeriodHolidayHandler const queryRunner = this.dataService .getRepository() .manager.connection.createQueryRunner(); - const holidayDates = []; if (event.data.data.holidays?.length) { // foreach holiday @@ -28,19 +27,20 @@ export class SeasonPeriodHolidayHandler holidayDate.creator_name = event.data.data.creator_name; holidayDate.updated_at = event.data.data.updated_at; holidayDate.season_type = event.data.data.season_type; - holidayDate.item_rates = event.data.data.item_rates; + holidayDate.item_rates = event.data.data.item_rates.map((item) => { + delete item['id']; + + return item; + }); holidayDate.priority = 1; - holidayDates.push(holidayDate); + await this.dataService.create( + queryRunner, + SeasonPeriodModel, + holidayDate, + ); } - // create batch - await this.dataService.createBatch( - queryRunner, - SeasonPeriodModel, - holidayDates, - ); - // delete data await this.dataService.deleteById( queryRunner, diff --git a/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts b/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts index 4f10cb0..6e5b274 100644 --- a/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts +++ b/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts @@ -1,8 +1,4 @@ -import { - HttpStatus, - Injectable, - UnprocessableEntityException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { EventTopics, columnUniques, diff --git a/src/modules/season-related/season-period/domain/usecases/managers/get-current-period.manager.ts b/src/modules/season-related/season-period/domain/usecases/managers/get-current-period.manager.ts index ed74d61..f6e34dc 100644 --- a/src/modules/season-related/season-period/domain/usecases/managers/get-current-period.manager.ts +++ b/src/modules/season-related/season-period/domain/usecases/managers/get-current-period.manager.ts @@ -6,7 +6,7 @@ import { Param, RelationParam, } from 'src/core/modules/domain/entities/base-filter.entity'; -import { STATUS } from 'src/core/strings/constants/base.constants'; +import { DAY, STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class CurrentSeasonPeriodManager extends BaseIndexManager { @@ -20,8 +20,19 @@ export class CurrentSeasonPeriodManager extends BaseIndexManager { + const date = new Date(this.filterParam.date); + const day = DAY[date.getDay()]; Object.assign(this.result, { - data: this.result.data.sort((a, b) => a.priority - b.priority), + data: this.result.data + .filter((data) => { + const days: string[] = data.days ?? []; + if (data.priority == 2) { + return days.includes(day); + } else { + return true; + } + }) + .sort((a, b) => a.priority - b.priority), }); return; } diff --git a/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts b/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts index 8a8d7ce..933b807 100644 --- a/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts +++ b/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts @@ -1,5 +1,4 @@ import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; -import { Brackets } from 'typeorm'; import * as _ from 'lodash'; // function ini bergungsi untuk validasi season period yang sama @@ -60,7 +59,7 @@ export async function ValidateSeasonPeriodHelper(dataService, data) { if (datas.length > 0) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! there is another season period in same range date`, + message: `Gagal! terdapat data periode dengan rentang tanggal yang sama`, error: 'Unprocessable Entity', }); } diff --git a/src/modules/season-related/season-period/domain/usecases/managers/index-season-period.manager.ts b/src/modules/season-related/season-period/domain/usecases/managers/index-season-period.manager.ts index 62dec43..bf72913 100644 --- a/src/modules/season-related/season-period/domain/usecases/managers/index-season-period.manager.ts +++ b/src/modules/season-related/season-period/domain/usecases/managers/index-season-period.manager.ts @@ -74,6 +74,12 @@ export class IndexSeasonPeriodManager extends BaseIndexManager { async validateProcess(): Promise { - const priority = await ValidateSeasonPeriodHelper( - this.dataService, - this.data, - ); + if (!this.data.isUpdatePrice) { + const priority = await ValidateSeasonPeriodHelper( + this.dataService, + this.data, + ); - Object.assign(this.data, { - priority: priority, - }); + Object.assign(this.data, { + priority: priority, + }); + } return; } diff --git a/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts b/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts index 8db3a3c..3c25ef5 100644 --- a/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts +++ b/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts @@ -43,8 +43,11 @@ export class SeasonPeriodDataOrchestrator extends BaseDataTransactionOrchestrato return this.createManager.getResult(); } - async update(dataId, data): Promise { - this.updateManager.setData(dataId, data); + async update(dataId, data, updatePrice = false): Promise { + this.updateManager.setData(dataId, { + ...data, + isUpdatePrice: updatePrice, + }); this.updateManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD); await this.updateManager.execute(); return this.updateManager.getResult(); diff --git a/src/modules/season-related/season-period/infrastructure/dto/filter-season-period.dto.ts b/src/modules/season-related/season-period/infrastructure/dto/filter-season-period.dto.ts index b2a438a..bcdee40 100644 --- a/src/modules/season-related/season-period/infrastructure/dto/filter-season-period.dto.ts +++ b/src/modules/season-related/season-period/infrastructure/dto/filter-season-period.dto.ts @@ -25,4 +25,10 @@ export class FilterSeasonPeriodDto return Array.isArray(body.value) ? body.value : [body.value]; }) days: EnumDays[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + season_type_ids: string[]; } diff --git a/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts b/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts index 76c7ed0..e44e0c3 100644 --- a/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts +++ b/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts @@ -6,7 +6,6 @@ import { Patch, Post, Put, - Get, } from '@nestjs/common'; import { SeasonPeriodDataOrchestrator } from '../domain/usecases/season-period-data.orchestrator'; import { SeasonPeriodDto } from './dto/season-period.dto'; @@ -86,7 +85,7 @@ export class SeasonPeriodDataController { @Param('id') dataId: string, @Body() data: UpdateSeasonPeriodItemDto, ): Promise { - return await this.orchestrator.update(dataId, data); + return await this.orchestrator.update(dataId, data, true); } @Delete(':id') diff --git a/src/modules/season-related/season-type/domain/usecases/managers/batch-delete-season-type.manager.ts b/src/modules/season-related/season-type/domain/usecases/managers/batch-delete-season-type.manager.ts index c046507..d000785 100644 --- a/src/modules/season-related/season-type/domain/usecases/managers/batch-delete-season-type.manager.ts +++ b/src/modules/season-related/season-type/domain/usecases/managers/batch-delete-season-type.manager.ts @@ -7,7 +7,11 @@ import { import { SeasonTypeModel } from '../../../data/models/season-type.model'; import { SeasonTypeDeletedEvent } from '../../entities/event/season-type-deleted.event'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; -import { Injectable } from '@nestjs/common'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; @Injectable() export class BatchDeleteSeasonTypeManager extends BaseBatchDeleteManager { @@ -16,6 +20,19 @@ export class BatchDeleteSeasonTypeManager extends BaseBatchDeleteManager { + const relationData = await this.dataServiceFirstOpt.getOneByOptions({ + where: { + season_type_id: data.id, + }, + }); + + if (relationData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagagl! data sudah berelasi dengen season period.`, + error: 'Unprocessable Entity', + }); + } return; } diff --git a/src/modules/season-related/season-type/domain/usecases/managers/batch-inactive-season-type.manager.ts b/src/modules/season-related/season-type/domain/usecases/managers/batch-inactive-season-type.manager.ts index 6d6ea72..5bd5248 100644 --- a/src/modules/season-related/season-type/domain/usecases/managers/batch-inactive-season-type.manager.ts +++ b/src/modules/season-related/season-type/domain/usecases/managers/batch-inactive-season-type.manager.ts @@ -7,11 +7,28 @@ import { import { SeasonTypeModel } from '../../../data/models/season-type.model'; import { SeasonTypeChangeStatusEvent } from '../../entities/event/season-type-change-status.event'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; -import { Injectable } from '@nestjs/common'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; @Injectable() export class BatchInactiveSeasonTypeManager extends BaseBatchUpdateStatusManager { - validateData(data: SeasonTypeEntity): Promise { + async validateData(data: SeasonTypeEntity): Promise { + const relationData = await this.dataServiceFirstOpt.getOneByOptions({ + where: { + season_type_id: data.id, + }, + }); + + if (relationData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagagl! data sudah berelasi dengen season period.`, + error: 'Unprocessable Entity', + }); + } return; } diff --git a/src/modules/season-related/season-type/domain/usecases/managers/delete-season-type.manager.ts b/src/modules/season-related/season-type/domain/usecases/managers/delete-season-type.manager.ts index deffb5c..b392efc 100644 --- a/src/modules/season-related/season-type/domain/usecases/managers/delete-season-type.manager.ts +++ b/src/modules/season-related/season-type/domain/usecases/managers/delete-season-type.manager.ts @@ -1,4 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; import { SeasonTypeEntity } from '../../entities/season-type.entity'; import { @@ -15,6 +19,19 @@ export class DeleteSeasonTypeManager extends BaseDeleteManager } async validateProcess(): Promise { + const relationData = await this.dataServiceFirstOpt.getOneByOptions({ + where: { + season_type_id: this.dataId, + }, + }); + + if (relationData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagagl! data sudah berelasi dengen season period.`, + error: 'Unprocessable Entity', + }); + } return; } diff --git a/src/modules/season-related/season-type/domain/usecases/managers/inactive-season-type.manager.ts b/src/modules/season-related/season-type/domain/usecases/managers/inactive-season-type.manager.ts index a5a1c05..ea2ead8 100644 --- a/src/modules/season-related/season-type/domain/usecases/managers/inactive-season-type.manager.ts +++ b/src/modules/season-related/season-type/domain/usecases/managers/inactive-season-type.manager.ts @@ -1,4 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; import { SeasonTypeEntity } from '../../entities/season-type.entity'; import { @@ -15,6 +19,20 @@ export class InactiveSeasonTypeManager extends BaseUpdateStatusManager { + const relationData = await this.dataServiceFirstOpt.getOneByOptions({ + where: { + season_type_id: this.dataId, + }, + }); + + if (relationData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagagl! data sudah berelasi dengen season period.`, + error: 'Unprocessable Entity', + }); + } + return; } diff --git a/src/modules/season-related/season-type/domain/usecases/managers/update-season-type.manager.ts b/src/modules/season-related/season-type/domain/usecases/managers/update-season-type.manager.ts index 79b1f20..280745f 100644 --- a/src/modules/season-related/season-type/domain/usecases/managers/update-season-type.manager.ts +++ b/src/modules/season-related/season-type/domain/usecases/managers/update-season-type.manager.ts @@ -39,7 +39,6 @@ export class UpdateSeasonTypeManager extends BaseUpdateManager return [ { topic: SeasonTypeUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts b/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts index 52a07fa..5477cb0 100644 --- a/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts +++ b/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts @@ -15,6 +15,7 @@ import { BatchInactiveSeasonTypeManager } from './managers/batch-inactive-season import { BatchActiveSeasonTypeManager } from './managers/batch-active-season-type.manager'; import { BatchDeleteSeasonTypeManager } from './managers/batch-delete-season-type.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service'; @Injectable() export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator { @@ -30,6 +31,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< private batchConfirmManager: BatchConfirmSeasonTypeManager, private batchInactiveManager: BatchInactiveSeasonTypeManager, private serviceData: SeasonTypeDataService, + private servicePeriodData: SeasonPeriodDataService, ) { super(); } @@ -50,7 +52,11 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< async delete(dataId): Promise { this.deleteManager.setData(dataId); - this.deleteManager.setService(this.serviceData, TABLE_NAME.SEASON_TYPE); + this.deleteManager.setService( + this.serviceData, + TABLE_NAME.SEASON_TYPE, + this.servicePeriodData, + ); await this.deleteManager.execute(); return this.deleteManager.getResult(); } @@ -60,6 +66,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< this.batchDeleteManager.setService( this.serviceData, TABLE_NAME.SEASON_TYPE, + this.servicePeriodData, ); await this.batchDeleteManager.execute(); return this.batchDeleteManager.getResult(); @@ -101,7 +108,11 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); - this.inactiveManager.setService(this.serviceData, TABLE_NAME.SEASON_TYPE); + this.inactiveManager.setService( + this.serviceData, + TABLE_NAME.SEASON_TYPE, + this.servicePeriodData, + ); await this.inactiveManager.execute(); return this.inactiveManager.getResult(); } @@ -111,6 +122,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< this.batchInactiveManager.setService( this.serviceData, TABLE_NAME.SEASON_TYPE, + this.servicePeriodData, ); await this.batchInactiveManager.execute(); return this.batchInactiveManager.getResult(); diff --git a/src/modules/season-related/season-type/season-type.module.ts b/src/modules/season-related/season-type/season-type.module.ts index 5ae1ef3..185e30a 100644 --- a/src/modules/season-related/season-type/season-type.module.ts +++ b/src/modules/season-related/season-type/season-type.module.ts @@ -22,11 +22,16 @@ import { BatchActiveSeasonTypeManager } from './domain/usecases/managers/batch-a import { BatchConfirmSeasonTypeManager } from './domain/usecases/managers/batch-confirm-season-type.manager'; import { BatchInactiveSeasonTypeManager } from './domain/usecases/managers/batch-inactive-season-type.manager'; import { SeasonTypeModel } from './data/models/season-type.model'; +import { SeasonPeriodDataService } from '../season-period/data/services/season-period-data.service'; +import { SeasonPeriodModel } from '../season-period/data/models/season-period.model'; @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([SeasonTypeModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [SeasonTypeModel, SeasonPeriodModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [SeasonTypeDataController, SeasonTypeReadController], @@ -49,6 +54,7 @@ import { SeasonTypeModel } from './data/models/season-type.model'; SeasonTypeDataOrchestrator, SeasonTypeReadOrchestrator, + SeasonPeriodDataService, ], }) export class SeasonTypeModule {} diff --git a/src/modules/transaction/payment-method/domain/usecases/managers/update-payment-method.manager.ts b/src/modules/transaction/payment-method/domain/usecases/managers/update-payment-method.manager.ts index f64e351..8973f85 100644 --- a/src/modules/transaction/payment-method/domain/usecases/managers/update-payment-method.manager.ts +++ b/src/modules/transaction/payment-method/domain/usecases/managers/update-payment-method.manager.ts @@ -39,7 +39,6 @@ export class UpdatePaymentMethodManager extends BaseUpdateManager { @@ -22,10 +23,13 @@ export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManag }, }); - if (transaction.status != STATUS.SETTLED) { + if ( + ![STATUS.SETTLED, STATUS.WAITING].includes(transaction.status) && + !data.is_recap_transaction + ) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! cant cancel transaction not settled`, + message: `Gagal! tidak bisa batalkan, karena status transaksi tidak settled atau waiting`, error: 'Unprocessable Entity', }); } @@ -42,7 +46,7 @@ export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManag if (data.is_recap_transaction) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! cant cancel recap data`, + message: `Gagagl! tidak dapat batalkan data rekap`, error: 'Unprocessable Entity', }); } @@ -67,7 +71,11 @@ export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManag } get eventTopics(): EventTopics[] { - return []; + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; } getResult(): BatchResult { diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts index 4adec65..e9c8347 100644 --- a/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts @@ -8,22 +8,18 @@ import { Injectable } from '@nestjs/common'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; +import { generateInvoiceCodeHelper } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper'; @Injectable() export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager { async validateData(data: TransactionEntity): Promise { - const net_profit = data.reconciliation_mdr - ? Number(this.data.payment_total) - Number(this.data.reconciliation_mdr) - : null; - Object.assign(data, { - reconciliation_mdr: this.data.reconciliation_mdr ?? null, reconciliation_confirm_by: this.user.name, reconciliation_confirm_date: new Date().getTime(), status: STATUS.SETTLED, reconciliation_status: this.dataStatus, - payment_total_net_profit: net_profit, - payment_date: this.data.payment_date, + payment_code: await generateInvoiceCodeHelper(this.dataService, 'PMY'), }); return; } @@ -45,7 +41,11 @@ export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusMana } get eventTopics(): EventTopics[] { - return []; + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; } getResult(): BatchResult { diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts index fb48668..a5eb3f3 100644 --- a/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts @@ -10,6 +10,7 @@ import { validateRelations, } from 'src/core/strings/constants/interface.constants'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; @Injectable() @@ -26,16 +27,19 @@ export class CancelReconciliationManager extends BaseUpdateStatusManager { @@ -24,6 +26,11 @@ export class ConfirmReconciliationManager extends BaseUpdateStatusManager { @@ -47,11 +48,15 @@ export class IndexReconciliationManager extends BaseIndexManager { private recapTransactions = {}; - private startOfDay = moment().startOf('day').unix(); - private endOfDay = moment().endOf('day').unix(); + private startOfDay = moment().startOf('day').valueOf(); + private endOfDay = moment().endOf('day').valueOf(); get entityTarget(): any { return TransactionModel; } getResult() { - return; + return 'Berhasil recap data transaksi'; } get eventTopics(): EventTopics[] { @@ -35,23 +35,26 @@ export class RecapReconciliationManager extends BaseCustomManager + parseFloat(recap.payment_total), + ); if (exist) { Object.assign(exist, { - payment_total: _.sumBy(this.recapTransactions[recap], (recap) => - parseFloat(recap.payment_total), - ), - payment_total_net_profit: _.sumBy( - this.recapTransactions[recap], - (recap) => parseFloat(recap.payment_total), - ), + payment_total: total, + payment_total_net_profit: total, editor_id: this.user.id, editor_name: this.user.name, updated_at: new Date().getTime(), @@ -98,19 +104,16 @@ export class RecapReconciliationManager extends BaseCustomManager - parseFloat(recap.payment_total), - ), - payment_total_net_profit: _.sumBy( - this.recapTransactions[recap], - (recap) => parseFloat(recap.payment_total), - ), + payment_total: total, + payment_total_net_profit: total, reconciliation_status: STATUS.PENDING, status: STATUS.SETTLED, type: TransactionType.COUNTER, - booking_date: first_transaction.booking_date, + booking_date: new Date(), + payment_date: new Date(), creator_counter_no: first_transaction.creator_counter_no, - payment_type: first_transaction.payment_type, + payment_type: first_transaction.payment_type_counter, + payment_type_counter: first_transaction.payment_type_counter, payment_type_method_id: first_transaction.payment_type_method_id, payment_type_method_number: first_transaction.payment_type_method_number, diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts index 9a52e45..30d9b15 100644 --- a/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts @@ -6,6 +6,7 @@ import { validateRelations, } from 'src/core/strings/constants/interface.constants'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; @Injectable() @@ -46,6 +47,10 @@ export class UpdateReconciliationManager extends BaseUpdateManager TransactionItemModel, (model) => model.refund, { + @ManyToOne(() => TransactionItemModel, (model) => model.refunds, { onDelete: 'CASCADE', onUpdate: 'CASCADE', }) diff --git a/src/modules/transaction/refund/data/models/refund.model.ts b/src/modules/transaction/refund/data/models/refund.model.ts index 4740870..014a016 100644 --- a/src/modules/transaction/refund/data/models/refund.model.ts +++ b/src/modules/transaction/refund/data/models/refund.model.ts @@ -1,10 +1,10 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { RefundEntity } from '../../domain/entities/refund.entity'; -import { Column, Entity, JoinColumn, OneToMany, OneToOne } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { RefundItemModel } from './refund-item.model'; -import { RefundType } from '../../constants'; +import { RefundReasonType, RefundType } from '../../constants'; @Entity(TABLE_NAME.REFUND) export class RefundModel @@ -33,6 +33,16 @@ export class RefundModel @Column('decimal', { name: 'refund_total', nullable: true }) refund_total: number; + @Column('enum', { + name: 'refund_reason_type', + enum: RefundReasonType, + default: RefundReasonType.RIDE_MALFUNCTION, + }) + refund_reason_type: RefundReasonType; + + @Column('text', { name: 'refund_reason', nullable: true }) + refund_reason: string; + // bank info @Column('varchar', { name: 'bank_name', nullable: true }) bank_name: string; @@ -54,7 +64,7 @@ export class RefundModel // relation to transaction @Column('varchar', { name: 'transaction_id', nullable: true }) transaction_id: string; - @OneToOne(() => TransactionModel, (model) => model.refund, { + @ManyToOne(() => TransactionModel, (model) => model.refunds, { onDelete: 'CASCADE', onUpdate: 'CASCADE', }) diff --git a/src/modules/transaction/refund/domain/entities/refund.entity.ts b/src/modules/transaction/refund/domain/entities/refund.entity.ts index c9bf720..5d9ae53 100644 --- a/src/modules/transaction/refund/domain/entities/refund.entity.ts +++ b/src/modules/transaction/refund/domain/entities/refund.entity.ts @@ -1,5 +1,5 @@ import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; -import { RefundType } from '../../constants'; +import { RefundReasonType, RefundType } from '../../constants'; export interface RefundEntity extends BaseStatusEntity { type: RefundType; @@ -8,6 +8,8 @@ export interface RefundEntity extends BaseStatusEntity { refund_date: Date; refund_sub_total: number; refund_total: number; + refund_reason_type: RefundReasonType; + refund_reason: string; bank_name: string; bank_account_name: string; diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts index 6197a2d..0ab375f 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts @@ -17,10 +17,17 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class BatchCancelRefundManager extends BaseBatchUpdateStatusManager { validateData(data: RefundEntity): Promise { - if (![STATUS.REFUNDED, STATUS.PENDING].includes(data.status)) { + if ( + ![ + STATUS.REFUNDED, + STATUS.PENDING, + STATUS.PROCESS_REFUND, + STATUS.PARTIAL_REFUND, + ].includes(data.status) + ) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data with status ${STATUS.REFUNDED} and ${STATUS.PENDING} can be cancelled`, + message: `Gagal! hanya data dengan status ${STATUS.REFUNDED}, ${STATUS.PROCESS_REFUND} , ${STATUS.PARTIAL_REFUND} dan ${STATUS.PENDING} yang dapat dicancel`, error: 'Unprocessable Entity', }); } diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts index 678566a..8add7f4 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts @@ -17,23 +17,27 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class BatchConfirmRefundManager extends BaseBatchUpdateStatusManager { async validateData(data: RefundEntity): Promise { - if (data?.['transaction']?.status != STATUS.SETTLED) { + if ( + this.data.status == STATUS.DRAFT && + data['trnsaction']?.status != STATUS.SETTLED && + this.data.status == STATUS.PENDING && + data['trnsaction']?.status != STATUS.PROCESS_REFUND + ) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + message: `Gagal! hanya data dengan status ${STATUS.SETTLED} dapat dikembalikan`, error: 'Unprocessable Entity', }); } else if (![STATUS.DRAFT, STATUS.PENDING].includes(data.status)) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, + message: `Gagal! hanya data dengan status ${STATUS.DRAFT} and ${STATUS.PENDING} dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } if (this.data.status == STATUS.DRAFT) { Object.assign(this.data, { - code: `RF-${data?.['transaction']?.invoice_code.split('-')[1]}`, request_date: new Date(), status: STATUS.PENDING, }); diff --git a/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts index ec40de3..8b425ed 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts @@ -20,10 +20,17 @@ export class CancelRefundManager extends BaseUpdateStatusManager { } async validateProcess(): Promise { - if (![STATUS.REFUNDED, STATUS.PENDING].includes(this.data.status)) { + if ( + ![ + STATUS.REFUNDED, + STATUS.PENDING, + STATUS.PROCESS_REFUND, + STATUS.PARTIAL_REFUND, + ].includes(this.oldData.status) + ) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data with status ${STATUS.REFUNDED} and ${STATUS.PENDING} can be cancelled`, + message: `Gagal! hanya data dengan status ${STATUS.REFUNDED}, ${STATUS.PROCESS_REFUND} , ${STATUS.PARTIAL_REFUND} dan ${STATUS.PENDING} yang dapat dicancel`, error: 'Unprocessable Entity', }); } diff --git a/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts index 1de469c..35091c3 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts @@ -23,7 +23,7 @@ export class ConfirmRefundManager extends BaseUpdateStatusManager if (![STATUS.DRAFT, STATUS.PENDING].includes(this.oldData.status)) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, + message: `Gagal! hanya data dengan status ${STATUS.DRAFT} dan ${STATUS.PENDING} dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } @@ -38,17 +38,21 @@ export class ConfirmRefundManager extends BaseUpdateStatusManager relations: ['transaction'], }); - if (data.transaction.status != STATUS.SETTLED) { + if ( + data.status == STATUS.DRAFT && + data.transaction.status != STATUS.SETTLED && + data.status == STATUS.PENDING && + data.transaction.status != STATUS.PROCESS_REFUND + ) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + message: `Gagal! hanya pemesanan dengan status ${STATUS.SETTLED} dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } if (data.status == STATUS.DRAFT) { Object.assign(this.data, { - code: `RF-${data.transaction?.invoice_code?.split('-')[1]}`, request_date: new Date(), status: STATUS.PENDING, }); diff --git a/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts index 8e65106..2d49c00 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts @@ -13,6 +13,8 @@ import { RefundModel } from '../../../data/models/refund.model'; import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; import { RefundCreatedEvent } from '../../entities/event/refund-created.event'; import { STATUS } from 'src/core/strings/constants/base.constants'; +import { In, Not } from 'typeorm'; +import { generateInvoiceCodeHelper } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper'; @Injectable() export class CreateRefundManager extends BaseCreateManager { @@ -26,19 +28,16 @@ export class CreateRefundManager extends BaseCreateManager { }; }); - Object.assign(this.data, { - refund_items: refund_items, - }); - const exist = await this.dataService.getOneByOptions({ where: { transaction_id: this.data.transaction.id, + status: Not(In([STATUS.CANCEL])), }, }); if (exist) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! refund transaction with invoice ${this.data.transaction.invoice_code} already exist`, + message: `Gagal! pengembalian pemesanan dengan nomor invoice ${this.data.transaction.invoice_code} telah dibuat`, error: 'Unprocessable Entity', }); } @@ -53,10 +52,15 @@ export class CreateRefundManager extends BaseCreateManager { if (!transaction) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + message: `Gagal! hanya pemesanan dengan status ${STATUS.SETTLED} dapat dikembalikan`, error: 'Unprocessable Entity', }); } + + Object.assign(this.data, { + refund_items: refund_items, + code: await generateInvoiceCodeHelper(this.dataService, 'RF'), + }); return; } diff --git a/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts index 88c3e89..b5f332e 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts @@ -15,7 +15,7 @@ export class DetailRefundManager extends BaseDetailManager { } async afterProcess(): Promise { - mappingTransaction(this.result['transaction']); + mappingTransaction(this.result['transaction'], this.dataId); return; } @@ -25,7 +25,12 @@ export class DetailRefundManager extends BaseDetailManager { joinRelations: [], // relation join and select (relasi yang ingin ditampilkan), - selectRelations: ['transaction', 'transaction.items', 'items.refund'], + selectRelations: [ + 'transaction', + 'transaction.items', + 'items.refunds item_refunds', + 'item_refunds.refund item_refunds_refund', + ], // relation yang hanya ingin dihitung (akan return number) countRelations: [], @@ -39,6 +44,8 @@ export class DetailRefundManager extends BaseDetailManager { `${this.tableName}.status`, `${this.tableName}.type`, + `${this.tableName}.refund_reason`, + `${this.tableName}.refund_reason_type`, `${this.tableName}.request_date`, `${this.tableName}.refund_date`, `${this.tableName}.created_at`, @@ -53,7 +60,9 @@ export class DetailRefundManager extends BaseDetailManager { 'transaction', 'items', - 'refund', + 'item_refunds', + 'item_refunds_refund.id', + 'item_refunds_refund.status', ]; } diff --git a/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts index 2fd80a0..1be1328 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts @@ -50,6 +50,8 @@ export class IndexRefundManager extends BaseIndexManager { `${this.tableName}.status`, `${this.tableName}.type`, + `${this.tableName}.refund_reason`, + `${this.tableName}.refund_reason_type`, `${this.tableName}.request_date`, `${this.tableName}.refund_date`, `${this.tableName}.created_at`, diff --git a/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts index 86e8602..7d12668 100644 --- a/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts +++ b/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts @@ -12,25 +12,10 @@ import { columnUniques, validateRelations, } from 'src/core/strings/constants/interface.constants'; -import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class UpdateRefundManager extends BaseUpdateManager { async validateProcess(): Promise { - const transaction = await this.dataServiceFirstOpt.getOneByOptions({ - where: { - id: this.data.transaction.id, - status: STATUS.SETTLED, - }, - }); - - if (!transaction) { - throw new UnprocessableEntityException({ - statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, - error: 'Unprocessable Entity', - }); - } return; } @@ -70,7 +55,6 @@ export class UpdateRefundManager extends BaseUpdateManager { return [ { topic: RefundUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts b/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts index 86a3b70..8e89845 100644 --- a/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts +++ b/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts @@ -4,9 +4,25 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNumber, IsString, ValidateIf } from 'class-validator'; import { Exclude } from 'class-transformer'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; -import { RefundType } from '../../constants'; +import { RefundReasonType, RefundType } from '../../constants'; export class RefundDto extends BaseStatusDto implements RefundEntity { + @ApiProperty({ + type: String, + required: true, + example: RefundReasonType.RIDE_MALFUNCTION, + }) + @IsString() + refund_reason_type: RefundReasonType; + + @ApiProperty({ + type: String, + required: false, + example: '', + }) + @ValidateIf((object) => object.refund_reason_type == RefundReasonType.OTHER) + refund_reason: string; + @ApiProperty({ type: String, required: true, diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts index b699e1c..f83e7b0 100644 --- a/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts @@ -9,7 +9,9 @@ export function calculateSalesFormula( throwError = false, ) { try { - let { value, variable, tax_datas } = mappingTaxes(taxes, formula, total); + const mapTaxes = mappingTaxes(taxes, formula, total); + let { value } = mapTaxes; + const { variable, tax_datas } = mapTaxes; const x1 = math.simplify(formula, variable).toString(); console.log('Formula ', x1); @@ -41,13 +43,10 @@ export function calculateProfitFormula( throwError = false, ) { try { - let { value, variable, tax_datas } = mappingTaxes( - taxes, - formula, - total, - profit_share, - ); - + const mapTaxes = mappingTaxes(taxes, formula, total, profit_share); + let { value } = mapTaxes; + const { variable, tax_datas } = mapTaxes; + console.log(formula, variable); const result = math.simplify(formula, variable).toString(); console.log(result, 'formula'); @@ -64,9 +63,9 @@ export function calculateProfitFormula( } function mappingTaxes(taxes, formula, total, profit_share = 0) { - let value = 0; + const value = 0; const variable = {}; - let tax_datas = []; + const tax_datas = []; const const_variable = ['profit_share', 'item_share', 'dpp']; const regex = /([a-zA-Z0-9_]+)/g; @@ -78,14 +77,17 @@ function mappingTaxes(taxes, formula, total, profit_share = 0) { for (const key of keys) { if (!const_variable.includes(key)) { const keyData = taxes.find((tax) => tax.name == key); - variable[key] = keyData.value / 100; - - tax_datas.push({ - tax_id: keyData.id, - tax_name: keyData.name, - tax_value: keyData.value, - tax_total_value: (keyData.value / 100) * Number(total), - }); + if (!keyData) { + variable[key] = key; + } else { + variable[key] = keyData.value / 100; + tax_datas.push({ + tax_id: keyData.id, + tax_name: keyData.name, + tax_value: keyData.value, + tax_total_value: (keyData.value / 100) * Number(total), + }); + } } else { switch (key) { case 'profit_share': @@ -117,7 +119,7 @@ function returnError(throwError, e, taxes) { if (throwError) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! Formula error`, + message: `Gagal! Formula error`, error: 'Unprocessable Entity', }); } else { diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts index 1849e7a..74b4fc3 100644 --- a/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts @@ -49,7 +49,6 @@ export class UpdateSalesPriceFormulaManager extends BaseUpdateManager item.toLowerCase() == name)) throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! tax ${name} already used in ${formula.type} formula`, + message: `Gagal! pajak dengan nama ${name} telah dogunakan pada formula ${formula.type}`, error: 'Unprocessable Entity', }); }); diff --git a/src/modules/transaction/tax/domain/usecases/managers/update-tax.manager.ts b/src/modules/transaction/tax/domain/usecases/managers/update-tax.manager.ts index f72838e..82f1341 100644 --- a/src/modules/transaction/tax/domain/usecases/managers/update-tax.manager.ts +++ b/src/modules/transaction/tax/domain/usecases/managers/update-tax.manager.ts @@ -39,7 +39,6 @@ export class UpdateTaxManager extends BaseUpdateManager { return [ { topic: TaxUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/transaction/transaction/data/models/transaction-item.model.ts b/src/modules/transaction/transaction/data/models/transaction-item.model.ts index d00e800..78807dd 100644 --- a/src/modules/transaction/transaction/data/models/transaction-item.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction-item.model.ts @@ -1,5 +1,5 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity'; import { TransactionModel } from './transaction.model'; @@ -14,6 +14,9 @@ export class TransactionItemModel @Column('varchar', { name: 'item_id', nullable: true }) item_id: string; + @Column('varchar', { name: 'qr_image_url', nullable: true }) + qr_image_url: string; + @Column('varchar', { name: 'item_name', nullable: true }) item_name: string; @@ -77,10 +80,10 @@ export class TransactionItemModel transaction: TransactionModel; // relations to refund - @OneToOne(() => RefundItemModel, (model) => model.transaction_item, { + @OneToMany(() => RefundItemModel, (model) => model.transaction_item, { cascade: true, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - refund: RefundItemModel; + refunds: RefundItemModel[]; } diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index c181d7a..58bdba7 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -1,6 +1,6 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TransactionEntity } from '../../domain/entities/transaction.entity'; -import { Column, Entity, OneToMany, OneToOne } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; import { TransactionType, @@ -80,6 +80,9 @@ export class TransactionModel @Column('date', { name: 'booking_date', nullable: true }) booking_date: Date; + @Column('date', { name: 'booking_date_before', nullable: true }) + booking_date_before: Date; + @Column('date', { name: 'settlement_date', nullable: true }) settlement_date: Date; @@ -107,6 +110,16 @@ export class TransactionModel }) payment_type: TransactionPaymentType; + @Column('enum', { + name: 'payment_type_counter', + enum: TransactionPaymentType, + nullable: true, + }) + payment_type_counter: TransactionPaymentType; + + @Column('varchar', { name: 'payment_code', nullable: true }) + payment_code: string; + @Column('varchar', { name: 'payment_type_method_id', nullable: true }) payment_type_method_id: string; @@ -206,6 +219,13 @@ export class TransactionModel @Column({ name: 'sending_qr_at', type: 'bigint', nullable: true }) sending_qr_at: number; + // calendar + @Column('varchar', { name: 'calendar_id', nullable: true }) + calendar_id: string; + + @Column('varchar', { name: 'calendar_link', nullable: true }) + calendar_link: string; + // relations to item @OneToMany(() => TransactionItemModel, (model) => model.transaction, { cascade: true, @@ -223,10 +243,10 @@ export class TransactionModel taxes: TransactionTaxModel[]; // relations to refund - @OneToOne(() => RefundModel, (model) => model.transaction, { + @OneToMany(() => RefundModel, (model) => model.transaction, { cascade: true, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - refund: RefundModel; + refunds: RefundModel[]; } diff --git a/src/modules/transaction/transaction/data/services/transaction-read.service.ts b/src/modules/transaction/transaction/data/services/transaction-read.service.ts index ddd488a..7efd6bf 100644 --- a/src/modules/transaction/transaction/data/services/transaction-read.service.ts +++ b/src/modules/transaction/transaction/data/services/transaction-read.service.ts @@ -2,9 +2,13 @@ import { Injectable } from '@nestjs/common'; import { TransactionEntity } from '../../domain/entities/transaction.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { TransactionModel } from '../models/transaction.model'; -import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { + CONNECTION_NAME, + STATUS, +} from 'src/core/strings/constants/base.constants'; import { Repository } from 'typeorm'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; +import { TransactionPaymentType } from '../../constants'; @Injectable() export class TransactionReadService extends BaseReadService { @@ -14,4 +18,16 @@ export class TransactionReadService extends BaseReadService { ) { super(repo); } + + async getPendingOrderId() { + const transactions = await this.repo.find({ + where: { + status: STATUS.PENDING, + payment_type: TransactionPaymentType.MIDTRANS, + }, + select: ['id', 'invoice_date'], + }); + + return transactions; + } } diff --git a/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts index 102355a..68d63cb 100644 --- a/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts +++ b/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts @@ -10,6 +10,7 @@ export interface TransactionItemEntity extends BaseCoreEntity { item_category_id: string; item_category_name: string; item_bundlings: string; + qr_image_url: string; // item tenant data item_tenant_id: string; diff --git a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts index 09f1f54..aa1814b 100644 --- a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts +++ b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts @@ -30,6 +30,7 @@ export interface TransactionEntity extends BaseStatusEntity { no_of_group: number; booking_date: Date; // tnaggal untuk booking + booking_date_before: Date; // tnaggal untuk booking settlement_date: Date; // tanggal status berubah menjadi settlement invoice_date: Date; // tanggal invoice terkirim @@ -41,12 +42,14 @@ export interface TransactionEntity extends BaseStatusEntity { // payment data payment_type: TransactionPaymentType; + payment_type_counter: TransactionPaymentType; payment_type_method_id: string; payment_type_method_name: string; payment_type_method_number: string; payment_type_method_qr: string; payment_card_information: string; payment_code_reference: string; + payment_code: string; payment_midtrans_token: string; payment_midtrans_url: string; payment_date: Date; @@ -78,4 +81,7 @@ export interface TransactionEntity extends BaseStatusEntity { sending_invoice_status: STATUS; sending_qr_at: number; sending_qr_status: STATUS; + + calendar_id?: string; + calendar_link?: string; } diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts index ec0d46b..a441d4b 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts @@ -1,29 +1,47 @@ -import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { MidtransCallbackEvent } from 'src/modules/configuration/midtrans/domain/entities/midtrans-callback.event'; import { TransactionDataService } from '../../../data/services/transaction-data.service'; import { TransactionModel } from '../../../data/models/transaction.model'; -import { STATUS } from 'src/core/strings/constants/base.constants'; +import { + BLANK_USER, + OPERATION, + STATUS, +} from 'src/core/strings/constants/base.constants'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import * as _ from 'lodash'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { generateInvoiceCodeHelper } from '../managers/helpers/generate-invoice-code.helper'; @EventsHandler(MidtransCallbackEvent) export class MidtransCallbackHandler implements IEventHandler { - constructor(private dataService: TransactionDataService) {} + constructor( + private dataService: TransactionDataService, + private eventBus: EventBus, + ) {} async handle(event: MidtransCallbackEvent) { + console.log('callbak mid', event); const data_id = event.data.id; const data = event.data.data; + let old_data = undefined; + const transaction = await this.dataService.getOneByOptions({ where: { id: data_id, }, }); + old_data = _.cloneDeep(transaction); if (['capture', 'settlement'].includes(data.transaction_status)) { Object.assign(transaction, { status: STATUS.SETTLED, settlement_date: new Date(data.settlement_time), + payment_code: await generateInvoiceCodeHelper(this.dataService, 'PMY'), payment_code_reference: data.approval_code, + payment_date: data.settlement_time, + payment_type_method_name: `Midtrans/${data.payment_type}`, }); } else if (['pending'].includes(data['transaction_status'])) { Object.assign(transaction, { @@ -40,5 +58,31 @@ export class MidtransCallbackHandler .manager.connection.createQueryRunner(); await this.dataService.create(queryRunner, TransactionModel, transaction); + console.log('update change status to tr', { + id: data_id, + old: old_data, + data: { ...data, status: transaction.status }, + user: BLANK_USER, + description: 'Midtrans Callback', + module: TABLE_NAME.TRANSACTION, + op: OPERATION.UPDATE, + }); + console.log({ data, old_data }); + + this.eventBus.publish( + new TransactionChangeStatusEvent({ + id: data_id, + old: old_data, + data: { + ...data, + status: transaction.status, + booking_date: transaction.booking_date, + }, + user: BLANK_USER, + description: 'Midtrans Callback', + module: TABLE_NAME.TRANSACTION, + op: OPERATION.UPDATE, + }), + ); } } diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts index d79dc6e..e9cbb14 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts @@ -1,13 +1,20 @@ -import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { ChangeDocEvent } from 'src/modules/configuration/couch/domain/events/change-doc.event'; import { TransactionType } from '../../../constants'; import { TransactionDataService } from '../../../data/services/transaction-data.service'; import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service'; import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; -import { STATUS } from 'src/core/strings/constants/base.constants'; +import { + BLANK_USER, + OPERATION, + STATUS, +} from 'src/core/strings/constants/base.constants'; import { TransactionModel } from '../../../data/models/transaction.model'; import { mappingRevertTransaction } from '../managers/helpers/mapping-transaction.helper'; +import { apm } from '../../../../../../core/apm'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; @EventsHandler(ChangeDocEvent) export class PosTransactionHandler implements IEventHandler { @@ -15,12 +22,17 @@ export class PosTransactionHandler implements IEventHandler { private dataService: TransactionDataService, private taxService: TaxDataService, private formulaService: SalesPriceFormulaDataService, + private eventBus: EventBus, ) {} async handle(event: ChangeDocEvent) { + const apmTransactions = apm.startTransaction( + `ChangeDocEvent ${event?.data?.database}`, + 'handler', + ); try { const database = event.data.database; - const data = event.data.data; + const data = { ...event.data.data }; // jika bukan database transaksi, return langsung if (database != 'transaction') return; @@ -74,10 +86,35 @@ export class PosTransactionHandler implements IEventHandler { sales_price_formula: sales_formula.formula_string, }); + apmTransactions.setLabel('Code', data?.code); + await this.dataService.create(queryRunner, TransactionModel, data); + + /** + * When transaction is cancel, set booking to Pending + * And tell the POS to update the "Penjualan" status to Pending + */ + if (data.status == STATUS.PENDING) { + this.eventBus.publish( + new TransactionChangeStatusEvent({ + id: data.id, + old: event.data.data, + data: data, + user: BLANK_USER, + description: 'Cancel Booking', + module: TABLE_NAME.TRANSACTION, + op: OPERATION.UPDATE, + }), + ); + } + apmTransactions.result = 'Success'; } } catch (error) { + apmTransactions.result = 'Failed'; + apm.captureError(error); console.log('error handling pos transaction couch'); + } finally { + apmTransactions.end(); } } } diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts index 7d15c32..6e4061b 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts @@ -1,52 +1,83 @@ -import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { RefundChangeStatusEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-change-status.event'; import { TransactionDataService } from '../../../data/services/transaction-data.service'; -import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants'; +import { + BLANK_USER, + OPERATION, + STATUS, +} from 'src/core/strings/constants/base.constants'; import { TransactionModel } from '../../../data/models/transaction.model'; import { RefundDeletedEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-deleted.event'; +import { RefundCreatedEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-created.event'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -@EventsHandler(RefundChangeStatusEvent, RefundDeletedEvent) +@EventsHandler(RefundChangeStatusEvent, RefundDeletedEvent, RefundCreatedEvent) export class RefundUpdatedHandler implements IEventHandler { - constructor(private dataService: TransactionDataService) {} + constructor( + private dataService: TransactionDataService, + private eventBus: EventBus, + ) {} async handle(event: RefundChangeStatusEvent) { - const old_data = event.data.old; const current_data = event.data.data; let status: STATUS; + //GET TRANSACTION DATA BEFORE UPDATE + const oldData = current_data?.transaction; + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + const data = new TransactionModel(); + const if_full_refund = + Number(current_data.refund_total ?? 0) == + Number(current_data.transaction?.payment_total); + if ( - old_data.status != current_data.data || - (event.data.op == OPERATION.DELETE && current_data.status != STATUS.DRAFT) - ) { - const queryRunner = this.dataService - .getRepository() - .manager.connection.createQueryRunner(); - - const data = new TransactionModel(); - const if_full_refund = - Number(current_data.refund_total ?? 0) == - Number(current_data.transaction?.payment_total); - - if (event.data.op == OPERATION.DELETE) status = STATUS.SETTLED; - else if (current_data.status == STATUS.PENDING) - status = STATUS.PROCESS_REFUND; - else if (current_data.status == STATUS.REFUNDED && if_full_refund) + event.data.op == OPERATION.DELETE || + current_data.status == STATUS.CANCEL + ) + status = STATUS.SETTLED; + else { + if (current_data.status == STATUS.REFUNDED && if_full_refund) status = STATUS.REFUNDED; else if (current_data.status == STATUS.REFUNDED && !if_full_refund) status = STATUS.PARTIAL_REFUND; - else if (current_data.status == STATUS.CANCEL) status = STATUS.SETTLED; - - await this.dataService.update( - queryRunner, - TransactionModel, - { id: current_data.transaction_id }, - { - ...data, - status: status, - }, - ); + else status = STATUS.PROCESS_REFUND; } + + await this.dataService.update( + queryRunner, + TransactionModel, + { id: current_data.transaction_id }, + { + ...data, + status: status, + }, + ); + + //GET TRANSACTION DATA AFTER UPDATE + const newData = await this.dataService.getOneByOptions({ + where: { + id: current_data.transaction_id, + }, + relations: ['items'], + }); + + this.eventBus.publish( + new TransactionChangeStatusEvent({ + id: current_data.transaction_id, + old: oldData, + data: newData, + user: BLANK_USER, + description: 'Refund Callback', + module: TABLE_NAME.TRANSACTION, + op: OPERATION.UPDATE, + }), + ); } } diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts index f26b6b5..0bf4e4d 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts @@ -7,8 +7,10 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; import { TransactionDataService } from '../../../data/services/transaction-data.service'; import { TransactionModel } from '../../../data/models/transaction.model'; import { calculateSalesFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper'; +import { CreateEventCalendarHelper } from 'src/modules/configuration/google-calendar/domain/usecases/managers/helpers/create-event-calanedar.helper'; +import { TransactionUpdatedEvent } from '../../entities/event/transaction-updated.event'; -@EventsHandler(TransactionChangeStatusEvent) +@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent) export class SettledTransactionHandler implements IEventHandler { @@ -21,56 +23,73 @@ export class SettledTransactionHandler async handle(event: TransactionChangeStatusEvent) { const old_data = event.data.old; const current_data = event.data.data; - - if ( - old_data.status == current_data.status || - ![STATUS.ACTIVE, STATUS.SETTLED].includes(current_data.status) - ) - return; + const settled = [STATUS.ACTIVE, STATUS.SETTLED].includes( + current_data.status, + ); + const oldSettled = [STATUS.ACTIVE, STATUS.SETTLED].includes( + old_data.status, + ); const data = await this.dataService.getOneByOptions({ where: { - id: current_data.id, + id: event.data.id, }, relations: ['items'], }); - const profit_formula = await this.formulaService.getOneByOptions({ - where: { - type: FormulaType.PROFIT_SHARE, - }, - }); + if (settled || oldSettled) { + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); - const sales_price = await this.formulaService.getOneByOptions({ - where: { - type: FormulaType.SALES_PRICE, - }, - }); + if (settled) { + const profit_formula = await this.formulaService.getOneByOptions({ + where: { + type: FormulaType.PROFIT_SHARE, + }, + }); - const taxes = await this.taxService.getManyByOptions({ - where: { - status: STATUS.ACTIVE, - }, - }); + const sales_price = await this.formulaService.getOneByOptions({ + where: { + type: FormulaType.SALES_PRICE, + }, + }); - const queryRunner = this.dataService - .getRepository() - .manager.connection.createQueryRunner(); + const taxes = await this.taxService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); - // const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0); - const { dpp_value, tax_datas } = calculateSalesFormula( - sales_price.formula_string, - taxes, - data.payment_total_net_profit ?? 0, - ); + // const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0); + const { dpp_value, tax_datas } = calculateSalesFormula( + sales_price.formula_string, + taxes, + data.payment_total_net_profit ?? 0, + ); - Object.assign(data, { - payment_total_dpp: dpp_value, - profit_share_formula: profit_formula.formula_string, - sales_price_formula: sales_price.formula_string, - taxes: tax_datas, - }); + // console.log(data, 'dsa'); + const google_calendar = await CreateEventCalendarHelper(data); - await this.dataService.create(queryRunner, TransactionModel, data); + Object.assign(data, { + payment_total_dpp: dpp_value, + profit_share_formula: profit_formula.formula_string, + sales_price_formula: sales_price.formula_string, + taxes: tax_datas, + calendar_id: google_calendar?.id, + calendar_link: google_calendar?.htmlLink, + }); + } else if (oldSettled) { + // console.log(data, 'data oldSettled'); + const google_calendar = await CreateEventCalendarHelper(data); + + Object.assign(data, { + calendar_id: null, + calendar_link: null, + }); + } + + await this.dataService.create(queryRunner, TransactionModel, data); + } } } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.manager.ts index 8287849..de433cd 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.manager.ts @@ -11,7 +11,7 @@ import { TransactionChangeStatusEvent } from '../../entities/event/transaction-c @Injectable() export class ActiveTransactionManager extends BaseUpdateStatusManager { getResult(): string { - return `Success active data ${this.result.invoice_code}`; + return `Success ${this.dataStatus} data ${this.result.invoice_code}`; } async validateProcess(): Promise { diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts index 4fb4157..6f44101 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts @@ -20,7 +20,7 @@ export class BatchCancelTransactionManager extends BaseBatchUpdateStatusManager< if (![STATUS.EXPIRED, STATUS.PENDING].includes(data.status)) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + message: `Gagal! hanya tranksaksi dengan status ${STATUS.PENDING} dan ${STATUS.EXPIRED} yang dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts index d3e593a..0d9ea28 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts @@ -18,12 +18,29 @@ import { TransactionPaymentType } from '../../../constants'; @Injectable() export class BatchConfirmDataTransactionManager extends BaseBatchUpdateStatusManager { validateData(data: TransactionEntity): Promise { + if ( + [ + TransactionPaymentType.BANK_TRANSFER, + TransactionPaymentType.QRIS, + ].includes(this.data.payment_type) && + (!this.data.payment_date || + !this.data.payment_total || + !this.data.payment_type_method_number || + !this.data.payment_type_method_name) + ) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagal! tolong lengkapi data terlebih dahulu`, + error: 'Unprocessable Entity', + }); + } + if ( ![STATUS.PENDING, STATUS.REJECTED, STATUS.EXPIRED].includes(data.status) ) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data booking with status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} can be confirm`, + message: `Gagal! hanya pemesanan dengan status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts index 47606d4..464cb5c 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts @@ -24,7 +24,7 @@ export class BatchConfirmTransactionManager extends BaseBatchUpdateStatusManager if (data.status != STATUS.DRAFT) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + message: `Gagal! hanya data booking dengan status ${STATUS.DRAFT} yang dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } @@ -42,15 +42,16 @@ export class BatchConfirmTransactionManager extends BaseBatchUpdateStatusManager } catch (error) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! this transaction already created, please check your email to continue payment`, + message: `Gagal! transaksi telah terbuat, silahkan periksa email untuk melanjutkan pembayaran`, error: 'Unprocessable Entity', }); } } Object.assign(data, { - invoice_code: await generateInvoiceCodeHelper(this.dataService), + invoice_code: await generateInvoiceCodeHelper(this.dataService, 'INV'), status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, + invoice_date: new Date(), }); return; } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts index 9e7510e..46b088f 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts @@ -7,7 +7,12 @@ import { import { TransactionModel } from '../../../data/models/transaction.model'; import { TransactionDeletedEvent } from '../../entities/event/transaction-deleted.event'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; -import { Injectable } from '@nestjs/common'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class BatchDeleteTransactionManager extends BaseBatchDeleteManager { @@ -16,6 +21,21 @@ export class BatchDeleteTransactionManager extends BaseBatchDeleteManager { + const allowDelete = [ + STATUS.DRAFT, + STATUS.ACTIVE, + STATUS.CANCEL, + STATUS.PENDING, + STATUS.EXPIRED, + ].includes(data.status); + + if (!allowDelete) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagal! data dengan status ${data.status} tidak bisa di hapus!`, + error: 'Unprocessable Entity', + }); + } return; } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts index 2e939e3..5c3c013 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts @@ -16,14 +16,14 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class CancelTransactionManager extends BaseUpdateStatusManager { getResult(): string { - return `Success active data ${this.result.invoice_code}`; + return `Success ${this.dataStatus} data ${this.result.invoice_code}`; } async validateProcess(): Promise { - if (![STATUS.EXPIRED, STATUS.PENDING].includes(this.data.status)) { + if (![STATUS.EXPIRED, STATUS.PENDING].includes(this.oldData.status)) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + message: `Gagal! hanya tranksaksi dengan status ${STATUS.PENDING} dan ${STATUS.EXPIRED} yang dapat di${this.dataStatus}`, error: 'Unprocessable Entity', }); } @@ -31,9 +31,8 @@ export class CancelTransactionManager extends BaseUpdateStatusManager { - const freeTransaction = this.data.payment_total < 1; Object.assign(this.data, { - status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, + status: STATUS.CANCEL, }); return; } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts index f860f9e..3961cbb 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts @@ -21,6 +21,23 @@ export class ConfirmDataTransactionManager extends BaseUpdateStatusManager { + if ( + [ + TransactionPaymentType.BANK_TRANSFER, + TransactionPaymentType.QRIS, + ].includes(this.data.payment_type) && + (!this.data.payment_date || + !this.data.payment_total || + !this.data.payment_type_method_number || + !this.data.payment_type_method_name) + ) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagal! tolong lengkapi data terlebih dahulu`, + error: 'Unprocessable Entity', + }); + } + return; } @@ -32,7 +49,7 @@ export class ConfirmDataTransactionManager extends BaseUpdateStatusManager { @@ -27,7 +28,7 @@ export class ConfirmTransactionManager extends BaseUpdateStatusManager { @@ -15,6 +20,21 @@ export class DeleteTransactionManager extends BaseDeleteManager { + const allowDelete = [ + STATUS.DRAFT, + STATUS.ACTIVE, + STATUS.CANCEL, + STATUS.PENDING, + STATUS.EXPIRED, + ].includes(this.data.status); + + if (!allowDelete) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Gagal! data dengan status ${this.data.status} tidak bisa di hapus!`, + error: 'Unprocessable Entity', + }); + } return; } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts index 729c6ce..806b602 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts @@ -25,7 +25,12 @@ export class DetailTransactionManager extends BaseDetailManager { - let tenant; - - if (itemData.item_tenant_id) { - tenant = { - id: itemData.item_tenant_id, - name: itemData.item_tenant_name, - share_margin: itemData.item_tenant_share_margin, + if (data.payment_type_method_id || data.payment_type_method_name) { + payment_type_bank = { + id: data.payment_type_method_id, + issuer_name: data.payment_type_method_name, + account_number: data.payment_type_method_number, + qr_image: data.payment_type_method_qr, }; } - return { - item: { - id: itemData.item_id, - name: itemData.item_name, - item_type: itemData.item_type, - base_price: itemData.item_price, - hpp: itemData.item_hpp, - tenant: tenant, - item_category: { - id: itemData.item_category_id, - name: itemData.item_category_name, + const items = data?.['items']?.map((itemData) => { + let tenant; + let refund = itemData.refunds?.find( + (item) => ![STATUS.CANCEL].includes(item.refund.status), + ); + + if (itemData.item_tenant_id) { + tenant = { + id: itemData.item_tenant_id, + name: itemData.item_tenant_name, + share_margin: itemData.item_tenant_share_margin, + }; + } + + if (refundId) + refund = itemData.refunds?.find((item) => item.refund.id == refundId); + + return { + item: { + id: itemData.item_id, + name: itemData.item_name, + item_type: itemData.item_type, + base_price: itemData.item_price, + hpp: itemData.item_hpp, + tenant: tenant, + item_category: { + id: itemData.item_category_id, + name: itemData.item_category_name, + }, }, - }, - id: itemData.id, - refund: itemData.refund, - qty: itemData.qty, - qty_remaining: itemData.qty_remaining, - total_price_refund: itemData.refund?.refund_total ?? 0, - total_price: itemData.total_price, - }; - }); + id: itemData.id, + refund: refund, + qty: itemData.qty, + qty_remaining: itemData.qty_remaining, + total_price_refund: refund?.refund_total ?? 0, + total_price: itemData.total_price, + }; + }); - Object.assign(data, { - season_period: season_period, - items: items, - payment_type_bank: payment_type_bank, - }); + const refund = data.refunds?.find( + (refund) => ![STATUS.CANCEL].includes(refund.status), + ); - delete data.season_period_id; - delete data.season_period_name; - delete data.season_period_type_id; - delete data.season_period_type_name; + Object.assign(data, { + season_period: season_period, + items: items, + payment_type_bank: payment_type_bank, + refund: refund, + }); - delete data.payment_type_method_id; - delete data.payment_type_method_name; - delete data.payment_type_method_number; - delete data.payment_type_method_qr; + delete data.refunds; + + delete data.season_period_id; + delete data.season_period_name; + delete data.season_period_type_id; + delete data.season_period_type_name; + + delete data.payment_type_method_id; + delete data.payment_type_method_name; + delete data.payment_type_method_number; + delete data.payment_type_method_qr; + } catch (error) { + console.log('error mapping transactin', error); + } } export function mappingRevertTransaction(data, type) { + const { status } = data; + const isCancel = status == STATUS.CANCEL; if (type == TransactionType.COUNTER) { if (data.booking_id) { Object.assign(data, { editor_id: data.pos_admin?.id, editor_name: data.pos_admin?.name, edited_at: new Date(data.created_at), + payment_code: isCancel ? null : data.code, + status: isCancel ? STATUS.PENDING : data.status, }); } else { Object.assign(data, { + type: type, creator_id: data.pos_admin?.id, creator_name: data.pos_admin?.name, + invoice_code: data.code, }); } Object.assign(data, { id: data.booking_id ?? data._id, - invoice_code: data.code, creator_counter_no: Number(data.pos_number), - status: STATUS.SETTLED, settlement_date: new Date(data.created_at), - payment_date: new Date(data.created_at), + payment_date: isCancel ? null : new Date(data.created_at), invoice_date: new Date(data.created_at), - payment_type: + payment_type: TransactionPaymentType.COUNTER, + payment_type_counter: data.payment_type == 'cc' ? TransactionPaymentType.CC : data.payment_type, payment_card_information: data.card_information, - payment_code_reference: data.payment_code, + payment_code_reference: isCancel ? null : data.payment_code, discount_code_id: data.discount_code?.id, discount_code: data.discount_code?.code, discount_percentage: data.discount_code?.discount, }); } else { - // Object.assign(data, { - // payment_type: - // }) + Object.assign(data, { + type: type, + }); + } + + if (isCancel) { + Object.assign(data, { + payment_type_method_id: null, + payment_type_method_number: null, + payment_type_method_name: null, + payment_type_method_qr: null, + }); + } else { + Object.assign(data, { + payment_type_method_id: + data.payment_type_method?.id ?? data.payment_type_bank?.id, + payment_type_method_number: + data.payment_type_method?.account_number ?? + data.payment_type_bank?.account_number, + payment_type_method_name: + data.payment_type_method?.issuer_name ?? + data.payment_type_bank?.issuer_name, + payment_type_method_qr: + data.payment_type_method?.qr_image ?? data.payment_type_bank?.qr_image, + }); } Object.assign(data, { - type: type, payment_total_net_profit: data.payment_total, customer_category_id: data.customer_category?.id ?? null, customer_category_name: data.customer_category?.name ?? null, @@ -122,16 +165,6 @@ export function mappingRevertTransaction(data, type) { season_period_name: data.season_period?.holiday_name ?? null, season_period_type_id: data.season_period?.season_type?.id ?? null, season_period_type_name: data.season_period?.season_type?.name ?? null, - payment_type_method_id: - data.payment_type_method?.id ?? data.payment_type_bank?.id, - payment_type_method_number: - data.payment_type_method?.account_number ?? - data.payment_type_bank?.account_number, - payment_type_method_name: - data.payment_type_method?.issuer_name ?? - data.payment_type_bank?.issuer_name, - payment_type_method_qr: - data.payment_type_method?.qr_image ?? data.payment_type_bank?.qr_image, }); data.items?.map((item) => { @@ -160,7 +193,7 @@ export function mappingRevertTransaction(data, type) { item_tenant_share_margin: item.item.tenant?.share_margin ?? null, total_price: total_price, - total_hpp: Number(item.item.item_hpp) * Number(item.qty), + total_hpp: Number(item.item.hpp) * Number(item.qty), total_share_tenant: total_share_tenant, total_profit: total_price - Number(total_share_tenant), }); diff --git a/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts index 342e5c1..122920e 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts @@ -7,6 +7,7 @@ import { RelationParam, } from 'src/core/modules/domain/entities/base-filter.entity'; import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper'; +import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class IndexTransactionManager extends BaseIndexManager { @@ -20,12 +21,17 @@ export class IndexTransactionManager extends BaseIndexManager async afterProcess(): Promise { this.result?.data?.map((item) => { + const activeRefund = item['refunds'].find( + (refund) => ![STATUS.CANCEL].includes(refund.status), + ); + Object.assign(item, { - refund_code: item['refund']?.code ?? null, - refund_date: item['refund']?.refund_date ?? null, + refund_type: activeRefund?.type ?? null, + refund_code: activeRefund?.code ?? null, + refund_date: activeRefund?.refund_date ?? null, }); - delete item['refund']; + delete item['refunds']; }); return; } @@ -36,7 +42,7 @@ export class IndexTransactionManager extends BaseIndexManager joinRelations: [], // relation join and select (relasi yang ingin ditampilkan), - selectRelations: ['items', 'refund'], + selectRelations: ['items', 'refunds'], // relation yang hanya ingin dihitung (akan return number) countRelations: [], @@ -67,17 +73,23 @@ export class IndexTransactionManager extends BaseIndexManager `${this.tableName}.editor_id`, `${this.tableName}.editor_name`, + `${this.tableName}.payment_code`, + `${this.tableName}.payment_card_information`, `${this.tableName}.payment_type`, + `${this.tableName}.payment_type_counter`, `${this.tableName}.payment_date`, `${this.tableName}.payment_total_pay`, `${this.tableName}.payment_type_method_id`, `${this.tableName}.payment_type_method_name`, `${this.tableName}.payment_type_method_number`, - `refund.id`, - `refund.code`, - `refund.refund_date`, - `refund.request_date`, + `${this.tableName}.payment_midtrans_url`, + + `refunds.id`, + `refunds.code`, + `refunds.refund_date`, + `refunds.request_date`, + `refunds.type`, ]; } diff --git a/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts index d4d08d5..c124b17 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts @@ -19,6 +19,13 @@ export class UpdateTransactionManager extends BaseUpdateManager { mappingRevertTransaction(this.data, TransactionType.ADMIN); + const changeDate = this.data.booking_date != this.oldData.booking_date; + + if (changeDate) { + Object.assign(this.data, { + booking_date_before: this.oldData.booking_date, + }); + } return; } @@ -42,7 +49,6 @@ export class UpdateTransactionManager extends BaseUpdateManager { + this.invoiceManager.setData({ + id: dataId, + invoice_type: invoiceType, + }); + this.invoiceManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + this.paymentMethodService, + ); + await this.invoiceManager.execute(); + return this.invoiceManager.getResult(); + } + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); diff --git a/src/modules/transaction/transaction/infrastructure/dto/donwload-pdf.dto.ts b/src/modules/transaction/transaction/infrastructure/dto/donwload-pdf.dto.ts new file mode 100644 index 0000000..b0d522f --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/dto/donwload-pdf.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, ValidateIf } from 'class-validator'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; + +export class DownloadPdfDto { + @ApiProperty({ + type: 'string', + required: false, + description: `Select ${Object.values(InvoiceType)}`, + }) + @ValidateIf((body) => body.invoice_type) + @IsEnum(InvoiceType, { + message: `invoice type must be a valid enum ${JSON.stringify( + Object.values(InvoiceType), + )}`, + }) + invoice_type: InvoiceType; +} diff --git a/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts b/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts index cbb954b..2ecdc3d 100644 --- a/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts +++ b/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts @@ -103,10 +103,9 @@ export class TransactionDto extends BaseStatusDto { @ApiProperty({ type: String, - required: true, + required: false, example: TransactionPaymentType.MIDTRANS, }) - @IsString() payment_type: TransactionPaymentType; @ApiProperty({ diff --git a/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts b/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts index 8866cde..d9c7269 100644 --- a/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts +++ b/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts @@ -6,7 +6,9 @@ import { Patch, Post, Put, + Res, } from '@nestjs/common'; +import { Response } from 'express'; import { TransactionDataOrchestrator } from '../domain/usecases/transaction-data.orchestrator'; import { TransactionDto } from './dto/transaction.dto'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; @@ -15,6 +17,7 @@ import { TransactionEntity } from '../domain/entities/transaction.entity'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; import { Public } from 'src/core/guards'; +import { DownloadPdfDto } from './dto/donwload-pdf.dto'; @ApiTags(`${MODULE_NAME.TRANSACTION.split('-').join(' ')} - data`) @Controller(`v1/${MODULE_NAME.TRANSACTION}`) @@ -28,6 +31,19 @@ export class TransactionDataController { return await this.orchestrator.create(data); } + @Put('/:id/invoice/download') + async invoiceDownload( + @Param('id') dataId: string, + @Body() body: DownloadPdfDto, + @Res() res: Response, + ): Promise { + const data = await this.orchestrator.invoice(dataId, body.invoice_type); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', 'attachment; filename=invoice.pdf'); + res.send(data); + return res; + } + @Put('/batch-delete') async batchDeleted(@Body() body: BatchIdsDto): Promise { return await this.orchestrator.batchDelete(body.ids); diff --git a/src/modules/transaction/transaction/transaction.module.ts b/src/modules/transaction/transaction/transaction.module.ts index a626078..2153707 100644 --- a/src/modules/transaction/transaction/transaction.module.ts +++ b/src/modules/transaction/transaction/transaction.module.ts @@ -32,8 +32,12 @@ import { TaxModel } from '../tax/data/models/tax.model'; import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler'; import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler'; import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-transaction-callback.handler'; +import { PdfMakeManager } from 'src/modules/configuration/export/domain/managers/pdf-make.manager'; +import { PaymentMethodDataService } from '../payment-method/data/services/payment-method-data.service'; +import { PaymentMethodModel } from '../payment-method/data/models/payment-method.model'; @Module({ + exports: [TransactionReadService], imports: [ ConfigModule.forRoot(), TypeOrmModule.forFeature( @@ -43,6 +47,7 @@ import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-tra TransactionTaxModel, TaxModel, SalesPriceFormulaModel, + PaymentMethodModel, ], CONNECTION_NAME.DEFAULT, ), @@ -64,6 +69,7 @@ import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-tra BatchDeleteTransactionManager, BatchConfirmTransactionManager, CancelTransactionManager, + PdfMakeManager, BatchCancelTransactionManager, ConfirmDataTransactionManager, BatchConfirmDataTransactionManager, @@ -72,6 +78,7 @@ import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-tra TransactionReadService, TaxDataService, SalesPriceFormulaDataService, + PaymentMethodDataService, TransactionDataOrchestrator, TransactionReadOrchestrator, diff --git a/src/modules/transaction/vip-category/domain/usecases/managers/update-vip-category.manager.ts b/src/modules/transaction/vip-category/domain/usecases/managers/update-vip-category.manager.ts index e330981..418bc84 100644 --- a/src/modules/transaction/vip-category/domain/usecases/managers/update-vip-category.manager.ts +++ b/src/modules/transaction/vip-category/domain/usecases/managers/update-vip-category.manager.ts @@ -39,7 +39,6 @@ export class UpdateVipCategoryManager extends BaseUpdateManager statuses: [STATUS.ACTIVE], }); }, - message: 'Failed! There is active item', + message: 'Gagal! terdapat item yang aktif', }, ]; } diff --git a/src/modules/user-related/tenant/domain/usecases/managers/batch-inactive-tenant.manager.ts b/src/modules/user-related/tenant/domain/usecases/managers/batch-inactive-tenant.manager.ts index a12cbc8..befa0f0 100644 --- a/src/modules/user-related/tenant/domain/usecases/managers/batch-inactive-tenant.manager.ts +++ b/src/modules/user-related/tenant/domain/usecases/managers/batch-inactive-tenant.manager.ts @@ -22,7 +22,7 @@ export class BatchInactiveTenantManager extends BaseBatchUpdateStatusManager { statuses: [STATUS.ACTIVE], }); }, - message: 'Failed! There is active item', + message: 'Gagal! terdapat item yang aktif', }, ]; } diff --git a/src/modules/user-related/tenant/domain/usecases/managers/inactive-tenant.manager.ts b/src/modules/user-related/tenant/domain/usecases/managers/inactive-tenant.manager.ts index 1f656da..3df362e 100644 --- a/src/modules/user-related/tenant/domain/usecases/managers/inactive-tenant.manager.ts +++ b/src/modules/user-related/tenant/domain/usecases/managers/inactive-tenant.manager.ts @@ -21,7 +21,7 @@ export class InactiveTenantManager extends BaseUpdateStatusManager { statuses: [STATUS.ACTIVE], }); }, - message: 'Failed! There is active item', + message: 'Gagal! terdapat item yang aktif', }, ]; } diff --git a/src/modules/user-related/tenant/domain/usecases/managers/update-password-tenant.manager.ts b/src/modules/user-related/tenant/domain/usecases/managers/update-password-tenant.manager.ts index 723293f..7d63048 100644 --- a/src/modules/user-related/tenant/domain/usecases/managers/update-password-tenant.manager.ts +++ b/src/modules/user-related/tenant/domain/usecases/managers/update-password-tenant.manager.ts @@ -44,7 +44,6 @@ export class UpdatePasswordTenantManager extends BaseUpdateManager { return [ { topic: TenantUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/user-related/tenant/domain/usecases/managers/update-tenant.manager.ts b/src/modules/user-related/tenant/domain/usecases/managers/update-tenant.manager.ts index ea02385..3529b51 100644 --- a/src/modules/user-related/tenant/domain/usecases/managers/update-tenant.manager.ts +++ b/src/modules/user-related/tenant/domain/usecases/managers/update-tenant.manager.ts @@ -51,7 +51,6 @@ export class UpdateTenantManager extends BaseUpdateManager { return [ { topic: TenantUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/user-related/user-privilege/domain/entities/event/user-privilege-configuration-updated.event.ts b/src/modules/user-related/user-privilege/domain/entities/event/user-privilege-configuration-updated.event.ts new file mode 100644 index 0000000..962eea0 --- /dev/null +++ b/src/modules/user-related/user-privilege/domain/entities/event/user-privilege-configuration-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class UserPrivilegeConfigUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/user-related/user-privilege/domain/entities/user-privilege-configuration.entity.ts b/src/modules/user-related/user-privilege/domain/entities/user-privilege-configuration.entity.ts index e0b6158..cb350de 100644 --- a/src/modules/user-related/user-privilege/domain/entities/user-privilege-configuration.entity.ts +++ b/src/modules/user-related/user-privilege/domain/entities/user-privilege-configuration.entity.ts @@ -5,13 +5,13 @@ export interface UserPrivilegeConfigurationEntity extends BaseCoreEntity { module_label: string; menu: string; menu_label: string; - sub_menu: string; - sub_menu_label: string; - view: boolean; - create: boolean; - edit: boolean; - delete: boolean; - cancel: boolean; - confirm: boolean; - index: number; + sub_menu?: string; + sub_menu_label?: string; + view?: boolean; + create?: boolean; + edit?: boolean; + delete?: boolean; + cancel?: boolean; + confirm?: boolean; + index?: number; } diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts new file mode 100644 index 0000000..509b891 --- /dev/null +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts @@ -0,0 +1,108 @@ +import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { UserPrivilegeConfigUpdatedEvent } from '../../../entities/event/user-privilege-configuration-updated.event'; +import { UserPrivilegeConfigurationHelper } from '../helpers/generate-user-privilege-configuration.helper'; +import { UserPrivilegeDataService } from 'src/modules/user-related/user-privilege/data/service/user-privilege-data.service'; +import { PrivilegeAction } from 'src/core/strings/constants/privilege.constants'; +import { UserPrivilegeConfigurationModel } from 'src/modules/user-related/user-privilege/data/models/user-privilege-configuration.model'; +import { UserPrivilegeUpdatedEvent } from '../../../entities/event/user-privilege-updated.event'; +import { OPERATION } from 'src/core/strings/constants/base.constants'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@EventsHandler(UserPrivilegeConfigUpdatedEvent) +export class UserPrivilegeConfigUpdateHandler + implements IEventHandler +{ + constructor( + private dataService: UserPrivilegeDataService, + private eventBus: EventBus, + ) {} + + async handle(event: UserPrivilegeConfigUpdatedEvent) { + const data = event.data.data; + const configuration = await this.dataService.getOneByOptions({ + where: { + id: data.user_privilege_id, + }, + relations: ['user_privilege_configurations'], + }); + let configurationData = configuration?.[ + 'user_privilege_configurations' + ]?.sort((a, b) => a.index - b.index); + const configs = UserPrivilegeConfigurationHelper.createConfigurations(); + + configs + .filter( + (base) => + !configurationData.some((data) => { + return base.menu_label == data.menu_label; + }), + ) + .map((item) => { + Object.assign(item, { + user_privilege_id: data.user_privilege_id, + }); + + configurationData.push(item); + }); + + configurationData = configurationData + ?.filter((item) => + configs.some((data) => { + return item.menu_label == data.menu_label; + }), + ) + ?.map((item) => { + const exist = configs.find( + (config) => config.menu_label == item.menu_label, + ); + return { + ...item, + view: exist.actions.includes(PrivilegeAction.VIEW) + ? item.view ?? false + : null, + cancel: exist.actions.includes(PrivilegeAction.CANCEL) + ? item.cancel ?? false + : null, + confirm: exist.actions.includes(PrivilegeAction.CONFIRM) + ? item.confirm ?? false + : null, + create: exist.actions.includes(PrivilegeAction.CREATE) + ? item.create ?? false + : null, + delete: exist.actions.includes(PrivilegeAction.DELETE) + ? item.delete ?? false + : null, + edit: exist.actions.includes(PrivilegeAction.EDIT) + ? item.edit ?? false + : null, + }; + }); + + Object.assign(configuration, { + user_privilege_configurations: configurationData, + }); + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + await this.dataService.update( + queryRunner, + UserPrivilegeConfigurationModel, + { id: data.user_privilege_id }, + configuration, + ); + + this.eventBus.publishAll([ + new UserPrivilegeUpdatedEvent({ + id: configuration.id, + old: null, + data: configuration, + user: event.data.user, + description: '', + module: TABLE_NAME.USER_PRIVILEGE_CONFIGURATION, + op: OPERATION.UPDATE, + }), + ]); + } +} diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/index-user-privilege-configuration.manager.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/index-user-privilege-configuration.manager.ts index c5a1f0f..46c29af 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/index-user-privilege-configuration.manager.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/index-user-privilege-configuration.manager.ts @@ -7,6 +7,8 @@ import { RelationParam, } from 'src/core/modules/domain/entities/base-filter.entity'; import { ORDER_TYPE } from 'src/core/strings/constants/base.constants'; +import { UserPrivilegeConfigurationHelper } from '../helpers/generate-user-privilege-configuration.helper'; +import { PrivilegeAction } from 'src/core/strings/constants/privilege.constants'; @Injectable() export class IndexUserPrivilegeConfigurationManager extends BaseIndexManager { @@ -24,6 +26,62 @@ export class IndexUserPrivilegeConfigurationManager extends BaseIndexManager { + const configs = UserPrivilegeConfigurationHelper.createConfigurations() + .filter((item) => this.filterParam.modules.includes(item.module)) + ?.map((item) => { + return { + ...item, + }; + }); + + configs + .filter( + (base) => + !this.result.data.some((data) => { + return base.menu_label == data.menu_label; + }), + ) + .map((item) => { + Object.assign(item, { + user_privilege_id: this.filterParam.user_privilege_ids?.[0], + }); + + this.result.data.push(item); + }); + + this.result.data = this.result.data + ?.filter((item) => + configs.some((data) => { + return item.menu_label == data.menu_label; + }), + ) + ?.map((item) => { + const exist = configs.find( + (config) => config.menu_label == item.menu_label, + ); + return { + ...item, + view: exist.actions.includes(PrivilegeAction.VIEW) + ? item.view ?? false + : null, + cancel: exist.actions.includes(PrivilegeAction.CANCEL) + ? item.cancel ?? false + : null, + confirm: exist.actions.includes(PrivilegeAction.CONFIRM) + ? item.confirm ?? false + : null, + create: exist.actions.includes(PrivilegeAction.CREATE) + ? item.create ?? false + : null, + delete: exist.actions.includes(PrivilegeAction.DELETE) + ? item.delete ?? false + : null, + edit: exist.actions.includes(PrivilegeAction.EDIT) + ? item.edit ?? false + : null, + }; + }); + return; } diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts index d4a22b3..bb55d0a 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts @@ -6,6 +6,7 @@ import { } from 'src/core/strings/constants/interface.constants'; import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; import { UserPrivilegeConfigurationModel } from 'src/modules/user-related/user-privilege/data/models/user-privilege-configuration.model'; +import { UserPrivilegeConfigUpdatedEvent } from '../../../entities/event/user-privilege-configuration-updated.event'; @Injectable() export class UpdateUserPrivilegeConfigurationManager extends BaseCustomManager { @@ -24,6 +25,8 @@ export class UpdateUserPrivilegeConfigurationManager extends BaseCustomManager { return [ { topic: UserUpdatedEvent, - data: this.result, }, ]; } diff --git a/src/modules/user-related/user/domain/usecases/managers/update-user.manager.ts b/src/modules/user-related/user/domain/usecases/managers/update-user.manager.ts index 379a0c8..360bd70 100644 --- a/src/modules/user-related/user/domain/usecases/managers/update-user.manager.ts +++ b/src/modules/user-related/user/domain/usecases/managers/update-user.manager.ts @@ -52,7 +52,6 @@ export class UpdateUserManager extends BaseUpdateManager { return [ { topic: UserUpdatedEvent, - data: this.result, }, ]; } diff --git a/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts b/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts index a895e80..d03b24d 100644 --- a/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts +++ b/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts @@ -1,3 +1,5 @@ import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; -export interface FilterBannerEntity extends BaseFilterEntity {} +export interface FilterBannerEntity extends BaseFilterEntity { + titles: string[]; +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts index 8e33166..ed63d0e 100644 --- a/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts +++ b/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts @@ -52,8 +52,8 @@ export class IndexBannerManager extends BaseIndexManager { get specificFilter(): Param[] { return [ { - cols: `${this.tableName}.name`, - data: this.filterParam.names, + cols: `${this.tableName}.title`, + data: this.filterParam.titles, }, ]; } diff --git a/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts index 30b0c8b..e3fb25a 100644 --- a/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts +++ b/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts @@ -39,7 +39,6 @@ export class UpdateBannerManager extends BaseUpdateManager { return [ { topic: BannerUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts b/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts index b8fa4af..b5a3edf 100644 --- a/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts +++ b/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts @@ -1,6 +1,15 @@ import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; import { FilterBannerEntity } from '../../domain/entities/filter-banner.entity'; +import { Transform } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; export class FilterBannerDto extends BaseFilterDto - implements FilterBannerEntity {} + implements FilterBannerEntity +{ + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + titles: string[]; +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts index 17aef38..4691d0a 100644 --- a/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts +++ b/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts @@ -39,7 +39,6 @@ export class UpdateFaqManager extends BaseUpdateManager { return [ { topic: FaqUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts b/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts index 725d624..95f1bbd 100644 --- a/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts +++ b/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts @@ -14,7 +14,7 @@ export async function validateItemGate(dataService, data, id?) { if (existCode) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! Gate with code ${data.code} already exist`, + message: `Gagal! Gate dengan kode ${data.code} telah ada`, error: 'Unprocessable Entity', }); } @@ -31,7 +31,7 @@ export async function validateItemGate(dataService, data, id?) { if (existType) { throw new UnprocessableEntityException({ statusCode: HttpStatus.UNPROCESSABLE_ENTITY, - message: `Failed! Gate type ${data.type} with item ${data.item.name} already exist`, + message: `Gagal! Gate tipe ${data.type} dengan item ${data.item.name} telah ada`, error: 'Unprocessable Entity', }); } diff --git a/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts index 1018675..01d03e3 100644 --- a/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts +++ b/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts @@ -41,7 +41,6 @@ export class UpdateGateManager extends BaseUpdateManager { return [ { topic: GateUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts index 695d0d5..5b6a7ce 100644 --- a/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts +++ b/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts @@ -39,7 +39,6 @@ export class UpdateNewsManager extends BaseUpdateManager { return [ { topic: NewsUpdatedEvent, - data: this.data, }, ]; } diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts index 8c08145..5afc4fa 100644 --- a/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts +++ b/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts @@ -39,7 +39,6 @@ export class UpdateTermConditionManager extends BaseUpdateManager= 2.1.2 < 3" +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -4004,6 +4094,14 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4023,6 +4121,13 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.2" +is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4092,6 +4197,14 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-relative@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -5429,6 +5542,14 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -5612,6 +5733,11 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + pako@~1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -5749,6 +5875,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pdfmake@^0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.2.10.tgz#a8a0ee8a5acca8f5d728e0dfe4db8be5f1b9ec6b" + integrity sha512-doipFnmE1UHSk+Z3wfQuVweVQqx2pE/Ns2G5gCqZmWwqjDj+mZHnZYH/ryXWoIfD+iVdZUAutgI/VHkTCN+Xrw== + dependencies: + "@foliojs-fork/linebreak" "^1.1.1" + "@foliojs-fork/pdfkit" "^0.14.0" + iconv-lite "^0.6.3" + xmldoc "^1.1.2" + pg-cloudflare@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" @@ -5881,6 +6017,11 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +png-js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d" + integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g== + postgres-array@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" @@ -6100,6 +6241,16 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== +regexp.prototype.flags@^1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + relative-microtime@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/relative-microtime/-/relative-microtime-2.0.0.tgz#cceed2af095ecd72ea32011279c79e5fcc7de29b" @@ -6247,11 +6398,16 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" @@ -6345,6 +6501,16 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -6765,6 +6931,11 @@ tiny-emitter@^2.1.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== +tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + title-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982" @@ -7029,11 +7200,27 @@ unicode-byte-truncate@^1.0.0: is-integer "^1.0.6" unicode-substring "^0.1.0" +unicode-properties@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f" + integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg== + dependencies: + base64-js "^1.3.0" + unicode-trie "^2.0.0" + unicode-substring@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/unicode-substring/-/unicode-substring-0.1.0.tgz#6120ce3c390385dbcd0f60c32b9065c4181d4b36" integrity sha512-36Xaw9wXi7MB/3/EQZZHkZyyiRNa9i3k9YtPAz2KfqMVH2xutdXyMHn4Igarmnvr+wOrfWa/6njhY+jPpXN2EQ== +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -7327,6 +7514,13 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldoc@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.3.0.tgz#7823225b096c74036347c9ec5924d06b6a3cebab" + integrity sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng== + dependencies: + sax "^1.2.4" + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"