feat: whatsapp notification
parent
82e7879969
commit
44e74de315
|
@ -43,4 +43,7 @@ GOOGLE_CALENDAR_ID="326464ac296874c7121825f5ef2e2799baa90b51da240f0045aae22beec1
|
|||
|
||||
SUPERSET_URL=https://dashboard.weplayground.eigen.co.id
|
||||
SUPERSET_ADMIN_USERNAME=admin
|
||||
SUPERSET_ADMIN_PASSWORD=admin
|
||||
SUPERSET_ADMIN_PASSWORD=admin
|
||||
|
||||
WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID=604883366037548
|
||||
WHATSAPP_BUSINESS_ACCESS_TOKEN=EAAINOvRRiEEBO9yQsYDnYtjHZB7q1nZCwbBpRcxIGMDWajKZBtmWxNRKvPYkS95KQZBsZBOvSFyjiEg5CcCZBZBtaSZApxyV8fiA3cEyVwf7iVZBQP2YCTPRQZArMFeeXbO0uq5TGygmjsIz3M4YxcUHxPzKO4pKxIyxnzcoUZCqCSo1NqQSLVf3a0JyZAwgDXGL55dV
|
|
@ -40,4 +40,7 @@ GOOGLE_CALENDAR_ID="326464ac296874c7121825f5ef2e2799baa90b51da240f0045aae22beec1
|
|||
|
||||
SUPERSET_URL=https://dashboard.weplayground.eigen.co.id
|
||||
SUPERSET_ADMIN_USERNAME=admin
|
||||
SUPERSET_ADMIN_PASSWORD=admin
|
||||
SUPERSET_ADMIN_PASSWORD=admin
|
||||
|
||||
WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID=604883366037548
|
||||
WHATSAPP_BUSINESS_ACCESS_TOKEN=EAAINOvRRiEEBO9yQsYDnYtjHZB7q1nZCwbBpRcxIGMDWajKZBtmWxNRKvPYkS95KQZBsZBOvSFyjiEg5CcCZBZBtaSZApxyV8fiA3cEyVwf7iVZBQP2YCTPRQZArMFeeXbO0uq5TGygmjsIz3M4YxcUHxPzKO4pKxIyxnzcoUZCqCSo1NqQSLVf3a0JyZAwgDXGL55dV
|
|
@ -165,6 +165,15 @@ export class QueueService extends BaseDataService<QueueModel> {
|
|||
super(repo);
|
||||
}
|
||||
|
||||
async queueTicket(queueId: string) {
|
||||
return this.repo.findOne({
|
||||
relations: ['item', 'item.ticket'],
|
||||
where: {
|
||||
id: queueId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async queues(ids: string[]) {
|
||||
const start = moment().startOf('day').valueOf();
|
||||
const end = moment().endOf('day').valueOf();
|
||||
|
@ -225,6 +234,11 @@ export class QueueService extends BaseDataService<QueueModel> {
|
|||
});
|
||||
}
|
||||
|
||||
async updateLastNotification(queue_id: string, time: number) {
|
||||
const query = `UPDATE queues SET last_notification = ${time} WHERE id = '${queue_id}'`;
|
||||
this.dataSource.query(query);
|
||||
}
|
||||
|
||||
async updateItemQty(item_id: string, qty: number): Promise<void> {
|
||||
const query = `UPDATE queue_items SET qty = qty - ${qty} WHERE id = '${item_id}'`;
|
||||
this.dataSource.query(query);
|
||||
|
|
|
@ -12,6 +12,11 @@ import {
|
|||
ORDER_TYPE,
|
||||
QUEUE_STATUS,
|
||||
} from 'src/core/strings/constants/base.constants';
|
||||
import { QueueTimeFormula } from './usecases/formula/queue-time.formula';
|
||||
import * as moment from 'moment';
|
||||
import { timeIsBefore, toTime } from './helpers/time.helper';
|
||||
import { WhatsappService } from 'src/services/whatsapp/whatsapp.service';
|
||||
import { WhatsappQueue } from 'src/services/whatsapp/entity/whatsapp-queue.entity';
|
||||
|
||||
@Injectable()
|
||||
export class QueueAdminOrchestrator {
|
||||
|
@ -21,6 +26,7 @@ export class QueueAdminOrchestrator {
|
|||
private indexManager: IndexQueueManager,
|
||||
private callManager: CallQueueManager,
|
||||
private doneManager: DoneQueueManager,
|
||||
private readonly queueTimeFormula: QueueTimeFormula,
|
||||
) {}
|
||||
|
||||
async index(params): Promise<PaginationResponse<Queue>> {
|
||||
|
@ -43,4 +49,44 @@ export class QueueAdminOrchestrator {
|
|||
await this.doneManager.execute();
|
||||
return this.doneManager.getResult();
|
||||
}
|
||||
|
||||
async job(): Promise<void> {
|
||||
const notification = new WhatsappService();
|
||||
const itemMasters = await this.dataService.allQueue();
|
||||
const currentTime = moment().valueOf();
|
||||
|
||||
for (const queueItem of itemMasters) {
|
||||
const queueTimes = await this.queueTimeFormula.items(queueItem.id);
|
||||
if (!queueItem.use_notification) continue;
|
||||
|
||||
for (const queueId in queueTimes) {
|
||||
const callTime = queueTimes[queueId];
|
||||
|
||||
if (timeIsBefore(currentTime, callTime, queueItem.call_preparation)) {
|
||||
const queueTicket = await this.service.queueTicket(queueId);
|
||||
const payload: WhatsappQueue = {
|
||||
id: queueId,
|
||||
phone: queueTicket.item.ticket.phone,
|
||||
code: queueTicket.code,
|
||||
name: queueTicket.item.ticket.customer,
|
||||
item_name: queueItem.name,
|
||||
time: callTime,
|
||||
};
|
||||
|
||||
console.log({
|
||||
currentTime: toTime(currentTime),
|
||||
callTime: toTime(callTime),
|
||||
last_notification: toTime(queueTicket.last_notification),
|
||||
queueId,
|
||||
});
|
||||
|
||||
const call_preparation = queueItem.call_preparation * 60 * 1000;
|
||||
if (queueTicket.last_notification < currentTime - call_preparation) {
|
||||
await notification.queueProcess(payload);
|
||||
this.service.updateLastNotification(queueId, currentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import { padCode } from 'src/modules/transaction/vip-code/domain/usecases/manage
|
|||
import { QueueBucketReadService } from '../../data/services/queue-bucket';
|
||||
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
|
||||
import { QueueTimeFormula } from './formula/queue-time.formula';
|
||||
import { WhatsappService } from 'src/services/whatsapp/whatsapp.service';
|
||||
import { WhatsappQueue } from 'src/services/whatsapp/entity/whatsapp-queue.entity';
|
||||
import * as moment from 'moment';
|
||||
|
||||
@Injectable()
|
||||
|
@ -22,6 +24,8 @@ export class RegisterQueueManager extends BaseCreateManager<QueueModel> {
|
|||
super();
|
||||
}
|
||||
|
||||
private currentItemMaster;
|
||||
|
||||
async averageTime(): Promise<number> {
|
||||
const item = await this.getItemMaster();
|
||||
return item.play_estimation;
|
||||
|
@ -40,6 +44,7 @@ export class RegisterQueueManager extends BaseCreateManager<QueueModel> {
|
|||
async beforeProcess(): Promise<void> {
|
||||
const vip = this.data.vip ?? false;
|
||||
const item = await this.getItemMaster();
|
||||
this.currentItemMaster = item;
|
||||
const [, end] = await this.queueTime(item.item_queue_id);
|
||||
|
||||
const queueNumber = await this.bucketService.getQueue(
|
||||
|
@ -71,6 +76,21 @@ export class RegisterQueueManager extends BaseCreateManager<QueueModel> {
|
|||
}
|
||||
|
||||
async afterProcess(): Promise<void> {
|
||||
const notificationService = new WhatsappService();
|
||||
const item = this.currentItemMaster ?? (await this.getItemMaster());
|
||||
const queueTicket = await this.dataService.queueTicket(this.result.id);
|
||||
|
||||
const payload: WhatsappQueue = {
|
||||
id: this.result.id,
|
||||
phone: queueTicket.item.ticket.phone,
|
||||
code: this.result.code,
|
||||
name: queueTicket.item.ticket.customer,
|
||||
item_name: item.name,
|
||||
time: this.result.time,
|
||||
};
|
||||
notificationService.queueRegister(payload);
|
||||
const currentTime = moment().valueOf();
|
||||
this.dataService.updateLastNotification(this.result.id, currentTime);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { Controller, Logger, Post } from '@nestjs/common';
|
||||
|
||||
import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { QueueAdminOrchestrator } from '../../domain/queue-admin.orchestrator';
|
||||
import { Public } from 'src/core/guards';
|
||||
// import { Cron } from '@nestjs/schedule';
|
||||
|
||||
@ApiTags(`Queue Admin`)
|
||||
@Controller(`v1/${MODULE_NAME.QUEUE}-job`)
|
||||
@ApiBearerAuth('JWT')
|
||||
@Public(true)
|
||||
export class QueueJobController {
|
||||
constructor(private orchestrator: QueueAdminOrchestrator) {}
|
||||
|
||||
// @Cron('*/1 * * * *')
|
||||
@Post('queues/notification')
|
||||
async call() {
|
||||
Logger.log('call preparation');
|
||||
return this.orchestrator.job();
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import { SplitQueueManager } from './domain/usecases/split-queue.manager';
|
|||
import { QueueTransactionCancelHandler } from './infrastructure/handlers/cancel-transaction.handler';
|
||||
import { ItemQueueModel } from '../item-related/item-queue/data/models/item-queue.model';
|
||||
import { QueueTimeFormula } from './domain/usecases/formula/queue-time.formula';
|
||||
import { QueueJobController } from './infrastructure/controllers/queue-job.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -56,7 +57,7 @@ import { QueueTimeFormula } from './domain/usecases/formula/queue-time.formula';
|
|||
),
|
||||
CqrsModule,
|
||||
],
|
||||
controllers: [QueueController, QueueAdminController],
|
||||
controllers: [QueueController, QueueAdminController, QueueJobController],
|
||||
providers: [
|
||||
QueueOrchestrator,
|
||||
QueueAdminOrchestrator,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export interface WhatsappQueue {
|
||||
id: string;
|
||||
phone: string;
|
||||
code: string;
|
||||
name: string;
|
||||
item_name: string;
|
||||
time: number;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
export const WHATSAPP_BUSINESS_API_URL =
|
||||
process.env.WHATSAPP_BUSINESS_API_URL ?? 'https://graph.facebook.com/';
|
||||
|
||||
export const WHATSAPP_BUSINESS_VERSION =
|
||||
process.env.WHATSAPP_BUSINESS_VERSION ?? 'v21.0';
|
||||
|
||||
export const WHATSAPP_BUSINESS_QUEUE_URL =
|
||||
process.env.WHATSAPP_BUSINESS_QUEUE_URL ?? 'auth/login';
|
||||
|
||||
export const WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID =
|
||||
process.env.WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID ?? '';
|
||||
|
||||
export const WHATSAPP_BUSINESS_ACCESS_TOKEN =
|
||||
process.env.WHATSAPP_BUSINESS_ACCESS_TOKEN ?? '';
|
|
@ -0,0 +1,62 @@
|
|||
export function getTextMessageInput(recipient, text) {
|
||||
return JSON.stringify({
|
||||
messaging_product: 'whatsapp',
|
||||
preview_url: false,
|
||||
recipient_type: 'individual',
|
||||
to: recipient,
|
||||
type: 'text',
|
||||
text: {
|
||||
body: text,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function getTemplatedMessageInput(recipient, movie, seats) {
|
||||
return JSON.stringify({
|
||||
messaging_product: 'whatsapp',
|
||||
to: recipient,
|
||||
type: 'template',
|
||||
template: {
|
||||
name: 'sample_movie_ticket_confirmation',
|
||||
language: {
|
||||
code: 'en_US',
|
||||
},
|
||||
components: [
|
||||
{
|
||||
type: 'header',
|
||||
parameters: [
|
||||
{
|
||||
type: 'image',
|
||||
image: {
|
||||
link: movie.thumbnail,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'body',
|
||||
parameters: [
|
||||
{
|
||||
type: 'text',
|
||||
text: movie.title,
|
||||
},
|
||||
{
|
||||
type: 'date_time',
|
||||
date_time: {
|
||||
fallback_value: movie.time,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: movie.venue,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: seats,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
import {
|
||||
phoneNumberOnly,
|
||||
toTime,
|
||||
} from 'src/modules/queue/domain/helpers/time.helper';
|
||||
import { WhatsappQueue } from './entity/whatsapp-queue.entity';
|
||||
import {
|
||||
WHATSAPP_BUSINESS_ACCESS_TOKEN,
|
||||
WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID,
|
||||
WHATSAPP_BUSINESS_API_URL,
|
||||
WHATSAPP_BUSINESS_QUEUE_URL,
|
||||
WHATSAPP_BUSINESS_VERSION,
|
||||
} from './whatsapp.constant';
|
||||
import axios from 'axios';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
export class WhatsappService {
|
||||
async sendMessage(data) {
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: `${WHATSAPP_BUSINESS_API_URL}/${WHATSAPP_BUSINESS_VERSION}/${WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID}/messages`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${WHATSAPP_BUSINESS_ACCESS_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: data,
|
||||
};
|
||||
|
||||
const response = await axios(config);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async queueRegister(data: WhatsappQueue) {
|
||||
const queueUrl = `${WHATSAPP_BUSINESS_QUEUE_URL}?id=${data.id}`;
|
||||
const payload = {
|
||||
messaging_product: 'whatsapp',
|
||||
to: phoneNumberOnly(data.phone), // recipient's phone number
|
||||
type: 'template',
|
||||
template: {
|
||||
name: 'queue_created',
|
||||
language: {
|
||||
code: 'id', // language code
|
||||
},
|
||||
components: [
|
||||
{
|
||||
type: 'header',
|
||||
parameters: [
|
||||
{
|
||||
parameter_name: 'queue_code',
|
||||
type: 'text',
|
||||
text: data.code, // replace with queue_code variable
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'body',
|
||||
parameters: [
|
||||
{
|
||||
parameter_name: 'name',
|
||||
type: 'text',
|
||||
text: data.name, // replace with name variable
|
||||
},
|
||||
{
|
||||
parameter_name: 'item_name',
|
||||
type: 'text',
|
||||
text: data.item_name, // replace with item_name variable
|
||||
},
|
||||
{
|
||||
parameter_name: 'queue_code',
|
||||
type: 'text',
|
||||
text: data.code, // replace with queue_code variable
|
||||
},
|
||||
{
|
||||
parameter_name: 'queue_time',
|
||||
type: 'text',
|
||||
text: toTime(data.time), // replace with queue_time variable
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
sub_type: 'url',
|
||||
index: '0',
|
||||
parameters: [
|
||||
{
|
||||
type: 'text',
|
||||
text: queueUrl, // replace with dynamic URL
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await this.sendMessage(payload);
|
||||
Logger.log(`Notification register for ${data.code} send to ${data.phone}`);
|
||||
}
|
||||
|
||||
async queueProcess(data: WhatsappQueue) {
|
||||
const queueUrl = `${WHATSAPP_BUSINESS_QUEUE_URL}?id=${data.id}`;
|
||||
const payload = {
|
||||
messaging_product: 'whatsapp',
|
||||
to: data.phone, // recipient's phone number
|
||||
type: 'template',
|
||||
template: {
|
||||
name: 'queue_process',
|
||||
language: {
|
||||
code: 'id', // language code
|
||||
},
|
||||
components: [
|
||||
{
|
||||
type: 'header',
|
||||
parameters: [
|
||||
{
|
||||
parameter_name: 'queue_code',
|
||||
type: 'text',
|
||||
text: data.item_name, // replace with queue_code variable
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'body',
|
||||
parameters: [
|
||||
{
|
||||
parameter_name: 'name',
|
||||
type: 'text',
|
||||
text: data.name, // replace with name variable
|
||||
},
|
||||
{
|
||||
parameter_name: 'queue_code',
|
||||
type: 'text',
|
||||
text: data.code, // replace with queue_code variable
|
||||
},
|
||||
{
|
||||
parameter_name: 'queue_time',
|
||||
type: 'text',
|
||||
text: toTime(data.time), // replace with queue_time variable
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
sub_type: 'url',
|
||||
index: '0',
|
||||
parameters: [
|
||||
{
|
||||
type: 'text',
|
||||
text: queueUrl, // replace with dynamic URL
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await this.sendMessage(payload);
|
||||
Logger.log(`Notification process for ${data.code} send to ${data.phone}`);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue