Skip to content

Feat/purchase email #733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
batch_size: int,
encryption_key: IKey,
alarm_topic: ITopic,
dlq_count_alarm_threshold: int = 10,
):
super().__init__(scope, construct_id)

Expand Down Expand Up @@ -92,7 +93,11 @@ def __init__(
)

self._add_queue_alarms(
retention_period=retention_period, queue=self.queue, dlq=self.dlq, alarm_topic=alarm_topic
retention_period=retention_period,
queue=self.queue,
dlq=self.dlq,
alarm_topic=alarm_topic,
dlq_count_alarm_threshold=dlq_count_alarm_threshold,
)

QueryDefinition(
Expand All @@ -113,6 +118,7 @@ def _add_queue_alarms(
queue: IQueue,
dlq: IQueue,
alarm_topic: ITopic,
dlq_count_alarm_threshold: int = 10,
):
# Alarm if messages are older than half the queue retention period
message_age_alarm = Alarm(
Expand All @@ -135,7 +141,7 @@ def _add_queue_alarms(
'DLQMessagesAlarm',
metric=dlq.metric_approximate_number_of_messages_visible(),
evaluation_periods=1,
threshold=10,
threshold=dlq_count_alarm_threshold,
actions_enabled=True,
alarm_description=f'{dlq.node.path} high message volume',
comparison_operator=ComparisonOperator.GREATER_THAN_THRESHOLD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ export class Lambda implements LambdaInterface {
event.templateVariables.endDate
);
break;
case 'privilegePurchaseProviderNotification':
await this.emailService.sendPrivilegePurchaseProviderNotificationEmail(
event.templateVariables.transactionDate,
event.templateVariables.privileges,
event.templateVariables.totalCost,
event.templateVariables.costLineItems,
event.specificEmails
);
break;
default:
logger.info('Unsupported email template provided', { template: event.template });
throw new Error(`Unsupported email template: ${event.template}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,10 @@ export abstract class BaseEmailService {
report['root']['data']['childrenIds'].push(blockHeaderId);
}

protected insertBody(report: TReaderDocument, bodyText: string) {
protected insertBody(
report: TReaderDocument,
bodyText: string,
textAlign: 'center' | 'right' | 'left' | null = null) {
const blockId = `block-${crypto.randomUUID()}`;

report[blockId] = {
Expand All @@ -333,9 +336,208 @@ export abstract class BaseEmailService {
}
};

if (textAlign && report[blockId]['data']['style']) {
report[blockId]['data']['style']['textAlign'] = textAlign;
}

report['root']['data']['childrenIds'].push(blockId);
}

protected insertTuple(report: TReaderDocument, keyText: string, valueText: string) {
const containerBlockId = `block-${crypto.randomUUID()}`;
const keyBlockId = `block-${crypto.randomUUID()}`;
const valueBlockId = `block-${crypto.randomUUID()}`;


report[keyBlockId] = {
'type': 'Text',
'data': {
'style': {
'fontWeight': 'bold',
'padding': {
'top': 16,
'bottom': 0,
'right': 12,
'left': 24
}
},
'props': {
'text': keyText
}
}
};

report[valueBlockId] = {
'type': 'Text',
'data': {
'style': {
'color': '#525252',
'fontSize': 14,
'fontWeight': 'normal',
'padding': {
'top': 0,
'bottom': 0,
'right': 24,
'left': 24
}
},
'props': {
'text': valueText
}
}
};

report[containerBlockId] = {
'type': 'Container',
'data': {
'style': {
'padding': {
'top': 0,
'bottom': 0,
'right': 72,
'left': 76
}
},
'props': {
'childrenIds': [
keyBlockId,
valueBlockId
]
}
}
};

report['root']['data']['childrenIds'].push(containerBlockId);
}

protected insertTwoColumnTable(report: TReaderDocument, title: string, rows: { left: string, right: string }[]) {
const titleBlockId = `block-${crypto.randomUUID()}`;


report[titleBlockId] = {
'type': 'Text',
'data': {
'style': {
'fontWeight': 'bold',
'padding': {
'top': 24,
'bottom': 16,
'right': 24,
'left': 68
}
},
'props': {
'text': title
}
}
};

report['root']['data']['childrenIds'].push(titleBlockId);

rows.forEach((row) => {
this.insertTwoColumnRow(report, row.left, row.right, false, 6);
});
}

protected insertTwoColumnRow(
report: TReaderDocument,
leftContent: string,
rightContent: string,
isBold: boolean,
bottomPadding: number
) {
const containerId = `block-${crypto.randomUUID()}`;
const leftCellId = `block-${crypto.randomUUID()}`;
const rightCellId = `block-${crypto.randomUUID()}`;

report[leftCellId] = {
'type': 'Text',
'data': {
'style': {
'fontWeight': 'normal',
'textAlign': 'left',
'padding': {
'top': 0,
'bottom': 0,
'right': 24,
'left': 24
}
},
'props': {
'text': leftContent
}
}
};

report[rightCellId] = {
'type': 'Text',
'data': {
'style': {
'fontWeight': 'normal',
'textAlign': 'right',
'padding': {
'top': 0,
'bottom': 0,
'right': 24,
'left': 24
}
},
'props': {
'text': rightContent
}
}
};

report[containerId] = {
'type': 'ColumnsContainer',
'data': {
'style': {
'padding': {
'top': 0,
'bottom': bottomPadding || 6,
'right': 44,
'left': 44
}
},
'props': {
'fixedWidths': [
null,
null,
null
],
'columnsCount': 2,
'columnsGap': 10,
'columns': [
{
'childrenIds': [
leftCellId
]
},
{
'childrenIds': [
rightCellId
]
},
{
'childrenIds': []
}
]
}
}
};

if (
isBold
&& report[leftCellId]['data']['style']
&& report[rightCellId]['data']['style']
) {
report[leftCellId]['data']['style']['fontWeight'] = 'bold';
report[rightCellId]['data']['style']['fontWeight'] = 'bold';
}

report['root']['data']['childrenIds'].push(containerId);
}

protected insertMarkdownBody(report: TReaderDocument, bodyText: string) {
const blockId = `block-${crypto.randomUUID()}`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,75 @@ export class EmailNotificationService extends BaseEmailService {
]
});
}

/**
* Sends an email notification to a provider when they purchase privilege(s)
* @param specificEmails - The email adresses(s) to send the email to, in this case always the provider's email
* @param transactionDate - The date the transaction occured
* @param privileges - The relevant privilege data necessary to generate teh email
* @param totalCost - The total cost of the transaction
* @param costLineItems - The line items involved in the purchase transaction
*/
public async sendPrivilegePurchaseProviderNotificationEmail(
transactionDate: string,
privileges: {
jurisdiction: string,
licenseTypeAbbrev: string,
privilegeId: string
}[],
totalCost: number,
costLineItems: {
name: string,
quantity: string,
unitPrice: string
}[],
specificEmails: string[] = []
): Promise<void> {
this.logger.info('Sending provider privilege purchase notification email', { providerEmail: specificEmails[0] });

const recipients = specificEmails;

if (recipients.length === 0) {
throw new Error(`No recipients found`);
}

const emailContent = this.getNewEmailTemplate();
const headerText = `Privilege Purchase Confirmation`;
const subject = `Compact Connect Privilege Purchase Confirmation`;
const bodyText = `This email is to confirm you successfully purchased the following privileges on ${transactionDate}`;

this.insertHeader(emailContent, headerText);
this.insertBody(emailContent, bodyText, 'center');

privileges.forEach((privilege) => {
const titleText = `${privilege.licenseTypeAbbrev.toUpperCase()} - ${privilege.jurisdiction.toUpperCase()}`;
const privilegeIdText = `Privilege Id: ${privilege.privilegeId}`;

this.insertTuple(emailContent, titleText, privilegeIdText);
});

const rows = costLineItems.map((lineItem) => {
const quantityNum = parseInt(lineItem.quantity, 10);
const unitPriceNum = Number(lineItem.unitPrice);


const quantityText = quantityNum > 1 ? `x ${quantityNum}` : '';
const left = `${lineItem.name} ${quantityText}`;
const right = `$${(unitPriceNum * quantityNum).toFixed(2)}`;

return { left, right };
});

const totalCostDisplay = `$${totalCost.toFixed(2)}`;

this.insertTwoColumnTable(emailContent, 'Cost breakdown', rows);

this.insertTwoColumnRow(emailContent, 'Total', totalCostDisplay, true, 24);

this.insertFooter(emailContent);

const htmlContent = renderToStaticMarkup(emailContent, { rootBlockId: 'root' });

await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send provider privilege purchase notification email' });
}
}
Loading