diff --git a/src/app.ts b/src/app.ts index e333e43..9026e7a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,16 +4,17 @@ import { Invoice } from './classes/invoice'; import { httpServer } from './server' import { MailService } from './services/mail-service' import { Env } from './utils/env'; +import { startIntervalsOverdue } from './services/invoice-service'; import { Logger } from './utils/logger'; if (process.env.NODE_ENV == null || process.env.NODE_ENV === 'develepmont') { - dotenv.config(); + dotenv.config(); } try { Env.validateMandatoryKeys(); } catch (err) { - console.error('.env not properly configured: ', err); + Logger.error('.env not properly configured: ', err); process.exit(-1); } @@ -22,5 +23,6 @@ try { const PORT: any = process.env.PORT || 6060; httpServer.listen(PORT, () => Logger.info(`The server is running on port ${PORT}`)); - + //TODO move to schedule + startIntervalsOverdue(); })(); diff --git a/src/classes/invoice.ts b/src/classes/invoice.ts index 4fff8fd..5618224 100644 --- a/src/classes/invoice.ts +++ b/src/classes/invoice.ts @@ -14,6 +14,15 @@ export interface Invoice { period_end: Date } +//move to db table? +export enum InvoiceStatus { + sent, + paid, + overdue, + generated, + notGenerated + } + export const invoiceSchema = Joi.object({ invoice_id: Joi.number().integer().min(0).required(), contract_id: Joi.number().integer().min(0).required(), diff --git a/src/controllers/invoice-controller.ts b/src/controllers/invoice-controller.ts index 73134b4..8f92bd8 100644 --- a/src/controllers/invoice-controller.ts +++ b/src/controllers/invoice-controller.ts @@ -143,4 +143,19 @@ export const getInvoiceByUserId: RequestHandler = async (req: Request, res: Resp message: 'There was an error when fetching invoices' }); } -} +}; + +//TODO add authentication +export const getOverdueInvoices: RequestHandler = async (req: Request, res: Response) => { + try { + const invoice = await invoiceService.getOverdueInvoices(); + res.status(200).json(invoice); + } catch (error) { + + console.log(error); + res.status(500).json({ + message: 'There was an error when fetching overdue invoices' + }); + } +}; + diff --git a/src/routes/invoice-routes.ts b/src/routes/invoice-routes.ts index 22f3ed5..14b3461 100644 --- a/src/routes/invoice-routes.ts +++ b/src/routes/invoice-routes.ts @@ -5,6 +5,7 @@ import { UserRole } from '../models/userrole'; const router = express.Router(); +router.get('/overdue', auth.authenticate([UserRole.ACCOUNTANT, UserRole.ADMIN]), invoiceController.getOverdueInvoices); router.get('/', auth.authenticate([UserRole.ADMIN, UserRole.ACCOUNTANT]), invoiceController.getAllInvoices); router.get('/:id/pdf', invoiceController.getInvoicePdfById); router.get('/:id', auth.authenticate([UserRole.ADMIN, UserRole.ACCOUNTANT]), invoiceController.getInvoiceById); diff --git a/src/routes/invoice.ts b/src/routes/invoice.ts deleted file mode 100644 index e20ca5d..0000000 --- a/src/routes/invoice.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** source/routes/invoices.ts */ -import express from 'express'; -import controller from '../controller/invoice'; -const router = express.Router(); - -router.get('/invoices', controller.getInvoices); -router.get('/invoices/:id', controller.getInvoice); -router.put('/invoices', controller.updateInvoice); -router.delete('/invoices/:id', controller.deleteInvoice); -router.post('/invoices', controller.addInvoice); - -export = router; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 3a73dc6..380f3ff 100644 --- a/src/server.ts +++ b/src/server.ts @@ -29,14 +29,6 @@ if (process.env.NODE_ENV == null || process.env.NODE_ENV === 'development') { dotenv.config(); } -try { - Env.validateMandatoryKeys(); -} catch (err) { - console.error('.env not properly configured: ', err); - process.exit(-1); -} - - const router: Express = express(); /** Middleware for CORS */ diff --git a/src/services/invoice-service.ts b/src/services/invoice-service.ts index 87d0790..bc79797 100644 --- a/src/services/invoice-service.ts +++ b/src/services/invoice-service.ts @@ -1,8 +1,10 @@ -import {execute} from "../utils/mysql.connector"; -import {Invoice} from "../classes/invoice"; -import {invoiceQueries} from "../queries/invoice-queries"; -import {Contract} from "../classes/contracts"; -import {InvoicePdf} from "../classes/invoice-pdf"; +import { execute } from "../utils/mysql.connector"; +import { Invoice, InvoiceStatus } from "../classes/invoice"; +import { invoiceQueries } from "../queries/invoice-queries"; +import { setInterval } from "timers"; +import { MailService } from "./mail-service"; +import { Contract } from "../classes/contracts"; +import { InvoicePdf } from "../classes/invoice-pdf"; export const getAllInvoices = async () => { return await execute(invoiceQueries.getAllInvoices, [], "rows"); @@ -13,8 +15,8 @@ export const getInvoiceById = async (id: Invoice['invoice_id']) => { return invoices[0]; }; -export const getInvoiceByPeriod = async (id: Invoice['contract_id'],periodStart: Invoice['period_start'], periodEnd: Invoice['period_end']) => { - const invoices = await execute(invoiceQueries.getInvoiceByPeriod, [periodStart,periodEnd], "rows"); +export const getInvoiceByPeriod = async (id: Invoice['contract_id'], periodStart: Invoice['period_start'], periodEnd: Invoice['period_end']) => { + const invoices = await execute(invoiceQueries.getInvoiceByPeriod, [periodStart, periodEnd], "rows"); return invoices[0]; }; @@ -79,3 +81,32 @@ export const getInvoicePdfData = async (id: Invoice['invoice_id']) => { export const getInvoiceByUserId = async (id: Invoice['invoice_id']) => { return await execute(invoiceQueries.getInvoiceByUserId, [id], "rows"); }; + +//this gives all the overdue invoices even if status id isn't InvoiceStatus.overdue +export const getOverdueInvoices = async () => { + const query = `SELECT * FROM invoices WHERE status_id = $1;`; + const invoicesSql = await execute<{ rows: Invoice[] }>(query, [InvoiceStatus.overdue]) + .catch(e => console.log("overdue invoices error: ", e)); + + if (!invoicesSql || !invoicesSql.rows) + return undefined + console.log(invoicesSql.rows.length) + return invoicesSql.rows; +}; + +//TODO use scheduler (invoice branch) +export async function setOverdue() { + const querySelect = `select * from "invoices" WHERE "status_id" = $1 AND "due_date" <= $2`; + const queryUpdate = `UPDATE "invoices" SET "status_id" = $1 WHERE "status_id" = $2 AND "due_date" <= $3` + const invoicesSql = await execute<{ rows: Invoice[] }>(querySelect, [InvoiceStatus.sent, new Date().toISOString()]); + if(!invoicesSql) + return undefined + execute(queryUpdate, [InvoiceStatus.overdue,InvoiceStatus.sent, new Date().toISOString()]).catch(e => console.log(e)); + + const ms = new MailService(); + invoicesSql.rows.forEach(o => ms.overdueInvoice(o)); +} + +export async function startIntervalsOverdue() { + setInterval(setOverdue, 10000); +} diff --git a/src/services/mail-service.ts b/src/services/mail-service.ts index d1114ea..2c4e6e6 100644 --- a/src/services/mail-service.ts +++ b/src/services/mail-service.ts @@ -9,7 +9,7 @@ export class MailService { private transport: Transporter; private hostEmail: string; private from : string; - + private endMail = `Best regards,\n${mailConfig.company}`; //supposed to be gotten from the data base private customer: Customer = { @@ -18,7 +18,7 @@ export class MailService { lastName: "rohan48", email: "AD0830686@PE2022.com" }; - + constructor() { this.checkEnv() this.hostEmail = process.env.MAILSERVER_U + "@" + mailConfig.domain; @@ -28,58 +28,57 @@ export class MailService { //console.log(this.transport); this.verify() }; - - - + + + private checkEnv() { if (!process.env.MAILSERVER_P) - throw new Error("Please define MAILSERVER_U in your env file"); - + throw new Error("Please define MAILSERVER_U in your env file"); + if (!process.env.MAILSERVER_U) throw new Error("Please define MAILSERVER_P in your env file"); - } - - private createTrans() { - //using https://ethereal.email includes authentication, testing - //mailserver doesn't have authentication (no TLS) - return createTransport({ - from: this.from, - host: mailConfig.host, - port: mailConfig.port, - auth: { - user: this.hostEmail, - pass: process.env.MAILSERVER_P - }, - //logger: true, - //transactionLog: true, - secure: false, - requireTLS: false - }) - } - - private async verify(): Promise { - if (!(await this.transport.verify())) + } + + private createTrans() { + //using https://ethereal.email includes authentication, testing + //mailserver doesn't have authentication (no TLS) + return createTransport({ + from: this.from, + host: mailConfig.host, + port: mailConfig.port, + auth: { + user: this.hostEmail, + pass: process.env.MAILSERVER_P + }, + //logger: true, + //transactionLog: true, + secure: false, + requireTLS: false + }) + } + + private async verify(): Promise { + if (!(await this.transport.verify())) throw new Error("transport in mailService isn't valid"); - } - - public sendInvoice(invoice: Invoice): SentMessageInfo { - //info from db - //TODO Get customer data from db - const title = `Dear ${this.customer.firstName} ${this.customer.lastName}`; - const body = [ - `your invoice ${invoice.InvoiceID} of ${invoice.Price} is due at ${invoice.DueDate.toDateString()} Please pay this as soon as possible.`, + } + + public sendInvoice(invoice: Invoice): SentMessageInfo { + //TODO Get customer data from db + const title = `Dear ${this.customer.firstName} ${this.customer.lastName}`; + const body = [ + `your invoice ${invoice.invoice_id} of ${invoice.price} is due at ${invoice.due_date.toDateString()} Please pay this as soon as possible.`, "if you wish to see more details and/or pay please visit this link" ]; - + return this.transport.sendMail({ from: this.from, to: this.customer.email, - subject: `Invoice: ${invoice.InvoiceID}`, + subject: `Invoice: ${invoice.invoice_id}`, text: this.textFormat(title, body), html: this.htmlFormat(title, body), }).catch((e) => { Logger.error(e); }) } - + public sendWorkOrder(/*employee: Employee | User*/): SentMessageInfo{ const employee = this.customer; //info from db @@ -97,7 +96,7 @@ export class MailService { html: this.htmlFormat(title, body), }).catch((e) => { Logger.error(e); }); } - + public sendAppointment(/*appointment: Appointment*/): SentMessageInfo{ const Customer = this.customer; //info from db @@ -116,6 +115,30 @@ export class MailService { }).catch((e) => { Logger.error(e); }); } + overdueInvoice(o: Invoice) : SentMessageInfo{ + const Customer = this.customer; + //info from db + const title = `Dear ${Customer.firstName} ${Customer.lastName}`; + const body = [ + `Your invoice ${o.invoice_id} is overdue`, + `Please pay the invoice at this link.`, + `
`, + `Invoice: ${o.invoice_id}`, + `date: ${o.creation_date}`, + `price: ${o.price}`, + ]; + + console.log(o); + + return this.transport.sendMail({ + from: this.from, + to: this.customer.email, + subject: `Invoice: ${o.invoice_id}`, + text: this.textFormat(title, body), + html: this.htmlFormat(title, body), + }).catch((e) => { Logger.error(e); }); + } + textFormat(title: string, body: string[]): string { let text = title; for (const b of body) { @@ -124,7 +147,7 @@ export class MailService { text += "\n\n" + this.endMail; return text; } - + htmlFormat(title: string, body: string[]): string { let text = "

" + title + "

"; for (const b of body) { @@ -134,5 +157,7 @@ export class MailService { text += "

" + this.endMail.replace("\n", "
") + "

"; return text; } + + };