From 8e9343f4a80c8b130289d3c66c5c0d6750d64309 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 22:33:58 +0100
Subject: [PATCH 01/14] feat: :art: Added more attributes
---
src/Database/Models/Quotes.model.ts | 24 +++++++++++++++++++++++-
src/Interfaces/Quotes.interface.ts | 9 +++++++--
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/src/Database/Models/Quotes.model.ts b/src/Database/Models/Quotes.model.ts
index 8c944dc..e578a5d 100644
--- a/src/Database/Models/Quotes.model.ts
+++ b/src/Database/Models/Quotes.model.ts
@@ -5,6 +5,7 @@ import { IQuotes } from "@interface/Quotes.interface";
import Logger from "../../Lib/Logger";
import GetText from "../../Translation/GetText";
import { A_CC_Payments } from "../../Types/PaymentMethod";
+import { currencyCodes } from "../../Lib/Currencies";
const QuotesSchema = new Schema
(
@@ -27,7 +28,6 @@ const QuotesSchema = new Schema
type: [
{
name: String,
- tax_rate: Number,
price: Number,
quantity: Number,
}
@@ -50,6 +50,28 @@ const QuotesSchema = new Schema
default: "",
},
+ currency: {
+ type: String,
+ required: true,
+ enum: currencyCodes,
+ default: "EUR",
+ },
+
+ tax_rate: {
+ type: Number,
+ default: 0,
+ },
+
+ accepted: {
+ type: Boolean,
+ default: false
+ },
+
+ declined: {
+ type: Boolean,
+ default: false
+ },
+
payment_method: {
type: String,
enum: [...A_CC_Payments],
diff --git a/src/Interfaces/Quotes.interface.ts b/src/Interfaces/Quotes.interface.ts
index eabccc9..ac09fb7 100644
--- a/src/Interfaces/Quotes.interface.ts
+++ b/src/Interfaces/Quotes.interface.ts
@@ -1,3 +1,5 @@
+import { TPaymentCurrency } from "../Lib/Currencies";
+import { ICustomer } from "./Customer.interface";
import { IInvoice } from "./Invoice.interface";
import { IPayments } from "./Payments.interface";
import { IPromotionsCodes } from "./PromotionsCodes.interface";
@@ -6,7 +8,7 @@ export interface IQuotes
{
uid: `QUO_${string}`;
id: number;
- customer_uid: string;
+ customer_uid: ICustomer["uid"];
items: IQuoteItem[];
promotion_codes: IPromotionsCodes["id"][] | [];
due_date: string;
@@ -14,13 +16,16 @@ export interface IQuotes
payment_method: keyof IPayments;
notified: boolean;
created_invoice: boolean;
+ tax_rate: number;
+ currency: TPaymentCurrency;
+ accepted: boolean;
+ declined: boolean;
invoice_uid?: IInvoice["uid"] | IInvoice["id"];
}
export interface IQuoteItem
{
name: string;
- tax_rate: number;
price: number;
quantity: number;
}
\ No newline at end of file
From 31af9ccae2445198d260ef624bc71bd1b9063d9f Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 22:34:21 +0100
Subject: [PATCH 02/14] fix: :bug: Fixed to new attributes
---
src/Lib/Quotes/CreateQuotePdf.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Lib/Quotes/CreateQuotePdf.ts b/src/Lib/Quotes/CreateQuotePdf.ts
index 51d36db..b7c23fe 100644
--- a/src/Lib/Quotes/CreateQuotePdf.ts
+++ b/src/Lib/Quotes/CreateQuotePdf.ts
@@ -71,7 +71,7 @@ export default function createQuotePdf(quote: IQuotes): Promise
return {
"quantity": item.quantity,
"description": item.name,
- "tax-rate": item.tax_rate,
+ "tax-rate": quote.tax_rate,
"price": item.price
}
}),
From 977fcb448c281a16d10aa4d09b0773bc88257ca1 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 22:34:44 +0100
Subject: [PATCH 03/14] feat: :art: Quote => Invoice method
---
src/Lib/Quotes/QuoteToInvoice.ts | 35 ++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 src/Lib/Quotes/QuoteToInvoice.ts
diff --git a/src/Lib/Quotes/QuoteToInvoice.ts b/src/Lib/Quotes/QuoteToInvoice.ts
new file mode 100644
index 0000000..452364c
--- /dev/null
+++ b/src/Lib/Quotes/QuoteToInvoice.ts
@@ -0,0 +1,35 @@
+import { IQuotes } from "@interface/Quotes.interface";
+import InvoiceModel from "../../Database/Models/Invoices.model";
+import { idInvoice } from "../Generator";
+import dateFormat from "date-and-time";
+import mainEvent from "../../Events/Main.event";
+
+export default async (quote: IQuotes) =>
+{
+ // Converts quote to invoice
+ const invoice = await (new InvoiceModel({
+ uid: idInvoice(),
+ customer_uid: quote.customer_uid,
+ items: quote.items.map(item => ({
+ notes: item.name,
+ amount: item.price,
+ quantity: item.quantity,
+ })),
+ dates: {
+ invoice_date: dateFormat.format(new Date(), "YYYY-MM-DD"),
+ due_date: quote.due_date,
+ },
+ amount: quote.items.reduce((acc, item) => acc + item.price * item.quantity, 0),
+ currency: quote.currency,
+ tax_rate: quote.tax_rate,
+ notified: false,
+ transactions: [],
+ paid: false,
+ notes: quote.memo,
+ payment_method: quote.payment_method,
+ }).save());
+
+ mainEvent.emit("invoice_created", invoice);
+
+ return invoice;
+}
\ No newline at end of file
From 8511532940dd9de77065f8a3d9a914113db007b1 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 22:35:03 +0100
Subject: [PATCH 04/14] feat: :art: Quote table for email
---
.../Templates/Methods/QuotesItems.print.ts | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
create mode 100644 src/Email/Templates/Methods/QuotesItems.print.ts
diff --git a/src/Email/Templates/Methods/QuotesItems.print.ts b/src/Email/Templates/Methods/QuotesItems.print.ts
new file mode 100644
index 0000000..329429e
--- /dev/null
+++ b/src/Email/Templates/Methods/QuotesItems.print.ts
@@ -0,0 +1,27 @@
+import { IQuotes } from "@interface/Quotes.interface";
+import { GetCurrencySymbol } from "../../../Lib/Currencies";
+import GetTableStyle from "../CSS/GetTableStyle";
+
+export default async function printQuotesItemsTable(quote: IQuotes)
+{
+ return `
+
+
+
+ Product |
+ Quantity |
+ Price |
+
+
+
+ ${(await Promise.all(quote.items.map(async item => `
+
+ ${item.name} |
+ ${item.quantity} |
+ ${item.price} ${GetCurrencySymbol(quote.currency)} |
+
+ `))).join('')}
+
+
+ `
+}
\ No newline at end of file
From a0670b68a5abe3210ed205b8a02be518225ead3c Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 22:35:15 +0100
Subject: [PATCH 05/14] feat: :art: Quote email template
---
.../Templates/Quotes/Quote.create.template.ts | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 src/Email/Templates/Quotes/Quote.create.template.ts
diff --git a/src/Email/Templates/Quotes/Quote.create.template.ts b/src/Email/Templates/Quotes/Quote.create.template.ts
new file mode 100644
index 0000000..e924c79
--- /dev/null
+++ b/src/Email/Templates/Quotes/Quote.create.template.ts
@@ -0,0 +1,39 @@
+import { stripIndents } from "common-tags";
+import { Company_Name, CPG_Customer_Panel_Domain } from "../../../Config";
+import { ICustomer } from "@interface/Customer.interface";
+import getFullName from "../../../Lib/Customers/getFullName";
+import UseStyles from "../General/UseStyles";
+import { IQuotes } from "@interface/Quotes.interface";
+import printQuotesItemsTable from "../Methods/QuotesItems.print";
+
+export default async (quote: IQuotes, customer: ICustomer) => await UseStyles(stripIndents`
+
+
Hello ${getFullName(customer)}.
+
+ This is a notice that ${await Company_Name()} has sent a quote to you.
+
+
+ Memo: ${quote.memo}
+
+
+ Due Date: ${quote.due_date}
+
+
+ Payment Method: ${quote.payment_method}
+
+
+ ${await printQuotesItemsTable(quote)}
+
+
+
+ Total:
+
+ ${quote.items.reduce((total, item) => total + (item.price * item.quantity), 0) + ((quote.tax_rate/100) * quote.items.reduce((total, item) => total + (item.price * item.quantity), 0))}
+
+ ${CPG_Customer_Panel_Domain ? `
+
+ View quote to accept or decline.
+
+ ` : ''}
+
+`);
\ No newline at end of file
From ca504ffaabdfc1c4147741391b42f3a5f72b17d7 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 22:35:35 +0100
Subject: [PATCH 06/14] feat: :sparkles: Modified routes for `Quotes`
---
src/Server/Routes/v2/Quotes/Quotes.config.ts | 65 +++++++++++++++++--
.../Routes/v2/Quotes/Quotes.controller.ts | 19 +-----
2 files changed, 64 insertions(+), 20 deletions(-)
diff --git a/src/Server/Routes/v2/Quotes/Quotes.config.ts b/src/Server/Routes/v2/Quotes/Quotes.config.ts
index 209bf15..8defe8f 100644
--- a/src/Server/Routes/v2/Quotes/Quotes.config.ts
+++ b/src/Server/Routes/v2/Quotes/Quotes.config.ts
@@ -3,9 +3,12 @@ import CustomerModel from "../../../../Database/Models/Customers/Customer.model"
import QuotesModel from "../../../../Database/Models/Quotes.model";
import AW from "../../../../Lib/AW";
import createQuotePdf from "../../../../Lib/Quotes/CreateQuotePdf";
-import { APIError } from "../../../../Lib/Response";
+import QuoteToInvoice from "../../../../Lib/Quotes/QuoteToInvoice";
+import { APIError, APISuccess } from "../../../../Lib/Response";
import EnsureAdmin from "../../../../Middlewares/EnsureAdmin";
+import EnsureAuth from "../../../../Middlewares/EnsureAuth";
import QuotesController from "./Quotes.controller";
+import { sendInvoiceEmail } from "../../../../Lib/Invoices/SendEmail";
export = class QuotesRouter
{
@@ -27,11 +30,13 @@ export = class QuotesRouter
QuotesController.getByUid
]);
- this.router.get("/:uid/view", async (req, res) =>
+ this.router.get("/:uid/view", EnsureAuth(), async (req, res) =>
{
- //
const uid = req.params.uid;
- const [quote, e_quote] = await AW(await QuotesModel.findOne({ uid: uid }));
+ const [quote, e_quote] = await AW(await QuotesModel.findOne({ $or: [
+ { uid: uid },
+ { id: uid }
+ ] }));
if(e_quote || !quote)
return APIError(`Failed to fetch quote with uid ${uid}`)(res);
@@ -53,6 +58,58 @@ export = class QuotesRouter
res.end(result, "base64");
});
+ this.router.post("/:uid/accept", EnsureAuth(), async (req, res) =>
+ {
+ const uid = req.params.uid;
+ const [quote, e_quote] = await AW(await QuotesModel.findOne({ $or: [
+ { uid: uid },
+ { id: uid }
+ ] }));
+
+ if(e_quote || !quote)
+ return APIError(`Failed to fetch quote with uid ${uid}`)(res);
+
+ const customer = await CustomerModel.findOne({ $or: [
+ { id: quote.customer_uid },
+ { uid: quote.customer_uid as any }
+ ] });
+
+ if(!customer)
+ return APIError(`Failed to fetch customer with uid ${quote.customer_uid}`)(res);
+
+ quote.accepted = true;
+
+ await quote.save();
+
+ // Convert quote to invoice
+ const invoice = await QuoteToInvoice(quote);
+ if(!invoice)
+ return APIError("Failed to convert quote to invoice")(res);
+
+ // Send email to customer, no need to await since if it fails it will run cron either way
+ sendInvoiceEmail(invoice, customer);
+
+ return APISuccess(invoice)(res);
+ });
+
+ this.router.post("/:uid/decline", EnsureAuth(), async (req, res) =>
+ {
+ const uid = req.params.uid;
+ const [quote, e_quote] = await AW(await QuotesModel.findOne({ $or: [
+ { uid: uid },
+ { id: uid }
+ ] }));
+
+ if(e_quote || !quote)
+ return APIError(`Failed to fetch quote with uid ${uid}`)(res);
+
+ quote.declined = true;
+
+ await quote.save();
+
+ return APISuccess(`Declined quote offer.`)(res);
+ });
+
this.router.post("/", [
EnsureAdmin(),
QuotesController.insert
diff --git a/src/Server/Routes/v2/Quotes/Quotes.controller.ts b/src/Server/Routes/v2/Quotes/Quotes.controller.ts
index 5c1e1e0..bc39f81 100644
--- a/src/Server/Routes/v2/Quotes/Quotes.controller.ts
+++ b/src/Server/Routes/v2/Quotes/Quotes.controller.ts
@@ -1,14 +1,14 @@
import { Request, Response } from "express";
-import { Company_Name, Full_Domain } from "../../../../Config";
+import { Company_Name } from "../../../../Config";
import CustomerModel from "../../../../Database/Models/Customers/Customer.model";
import QuotesModel from "../../../../Database/Models/Quotes.model";
import { SendEmail } from "../../../../Email/Send";
import mainEvent from "../../../../Events/Main.event";
import { IQuotes } from "@interface/Quotes.interface";
-import getFullName from "../../../../Lib/Customers/getFullName";
import { idQuotes } from "../../../../Lib/Generator";
import { APISuccess } from "../../../../Lib/Response";
import BaseModelAPI from "../../../../Models/BaseModelAPI";
+import QuoteCreateTemplate from "../../../../Email/Templates/Quotes/Quote.create.template";
const API = new BaseModelAPI(idQuotes, QuotesModel);
@@ -32,20 +32,7 @@ function insert(req: Request, res: Response)
// Send email to customer.
await SendEmail(Customer.personal.email, `Quote from ${await Company_Name() === "" ? "CPG" : await Company_Name()}`, {
isHTML: true,
- body: `
- Quote
-
- Hello ${getFullName(Customer)}!
-
-
- You have a new quote.
-
-
-
- Click here to view the quote.
-
-
- `
+ body: await QuoteCreateTemplate(result, Customer)
});
}
}
From 2c9d5a781e01fb4e5169cc457e7f0ff03b28f9f6 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 23:07:15 +0100
Subject: [PATCH 07/14] fix: :bug: Fixed path to customer panel
---
src/Email/Templates/Invoices/LateInvoice.Template.ts | 2 +-
src/Email/Templates/Orders/NewOrderCreated.ts | 2 +-
src/Email/Templates/Payments/PaymentFailed.template.ts | 2 +-
src/Email/Templates/Quotes/Quote.create.template.ts | 2 +-
src/Email/Templates/Transaction/NewTransaction.template.ts | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Email/Templates/Invoices/LateInvoice.Template.ts b/src/Email/Templates/Invoices/LateInvoice.Template.ts
index 2566eca..e37764f 100644
--- a/src/Email/Templates/Invoices/LateInvoice.Template.ts
+++ b/src/Email/Templates/Invoices/LateInvoice.Template.ts
@@ -78,7 +78,7 @@ export default async (invoice: IInvoice & IInvoiceMethods, customer: ICustomer)
${CPG_Customer_Panel_Domain ? `
- View Invoice
+ View Invoice
` : ''}
diff --git a/src/Email/Templates/Orders/NewOrderCreated.ts b/src/Email/Templates/Orders/NewOrderCreated.ts
index 48c743c..91b26d2 100644
--- a/src/Email/Templates/Orders/NewOrderCreated.ts
+++ b/src/Email/Templates/Orders/NewOrderCreated.ts
@@ -52,7 +52,7 @@ export default async (order: IOrder, customer: ICustomer) => await UseStyles(str
${CPG_Customer_Panel_Domain ? `
- View Order
+ View Order
` : ''}
diff --git a/src/Email/Templates/Payments/PaymentFailed.template.ts b/src/Email/Templates/Payments/PaymentFailed.template.ts
index 6959d9e..5f16581 100644
--- a/src/Email/Templates/Payments/PaymentFailed.template.ts
+++ b/src/Email/Templates/Payments/PaymentFailed.template.ts
@@ -41,7 +41,7 @@ export = async (invoice: IInvoice, customer: ICustomer) => UseStyles(stripIndent
${CPG_Customer_Panel_Domain ? `
- View invoice
+ View invoice
` : ''}
diff --git a/src/Email/Templates/Quotes/Quote.create.template.ts b/src/Email/Templates/Quotes/Quote.create.template.ts
index e924c79..d443a43 100644
--- a/src/Email/Templates/Quotes/Quote.create.template.ts
+++ b/src/Email/Templates/Quotes/Quote.create.template.ts
@@ -32,7 +32,7 @@ export default async (quote: IQuotes, customer: ICustomer) => await UseStyles(st
${CPG_Customer_Panel_Domain ? `
- View quote to accept or decline.
+ View quote to accept or decline.
` : ''}
diff --git a/src/Email/Templates/Transaction/NewTransaction.template.ts b/src/Email/Templates/Transaction/NewTransaction.template.ts
index 663fec8..86a4d97 100644
--- a/src/Email/Templates/Transaction/NewTransaction.template.ts
+++ b/src/Email/Templates/Transaction/NewTransaction.template.ts
@@ -29,7 +29,7 @@ export = async (t: ITransactions, c: ICustomer, charged = false) => UseStyles(st
${CPG_Customer_Panel_Domain ? `
- View Transaction
+ View Transaction
` : ''}
From 7864f9bd6b3a7536e620f6d867ed5b97cbf22ebd Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 23:07:36 +0100
Subject: [PATCH 08/14] fix: :bug: Fixed await
---
src/Email/Templates/Invoices/Invoice.template.ts | 2 +-
src/Lib/Invoices/CreatePDFInvoice.ts | 2 +-
src/Lib/Quotes/CreateQuotePdf.ts | 16 ++++++++--------
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/Email/Templates/Invoices/Invoice.template.ts b/src/Email/Templates/Invoices/Invoice.template.ts
index 74181b2..36262e2 100644
--- a/src/Email/Templates/Invoices/Invoice.template.ts
+++ b/src/Email/Templates/Invoices/Invoice.template.ts
@@ -78,7 +78,7 @@ export default async (invoice: IInvoice & IInvoiceMethods, customer: ICustomer)
${CPG_Customer_Panel_Domain ? `
- View Invoice
+ View Invoice
` : ''}
diff --git a/src/Lib/Invoices/CreatePDFInvoice.ts b/src/Lib/Invoices/CreatePDFInvoice.ts
index 21381a6..b8f7dc4 100644
--- a/src/Lib/Invoices/CreatePDFInvoice.ts
+++ b/src/Lib/Invoices/CreatePDFInvoice.ts
@@ -147,7 +147,7 @@ export default function createPDFInvoice(invoice: IInvoice): Promise
)
data["client"]["custom1"] = `
Innehar ${(await Company_Tax_Registered()) ? "" : "inte"} F-Skattsedel`;
- if(Company_Logo_Url && PDF_Template_Url === "")
+ if(await Company_Logo_Url() !== "" && PDF_Template_Url === "")
// @ts-ignore
data["images"]["logo"] = await Company_Logo_Url();
diff --git a/src/Lib/Quotes/CreateQuotePdf.ts b/src/Lib/Quotes/CreateQuotePdf.ts
index b7c23fe..126b32c 100644
--- a/src/Lib/Quotes/CreateQuotePdf.ts
+++ b/src/Lib/Quotes/CreateQuotePdf.ts
@@ -29,7 +29,7 @@ export default function createQuotePdf(quote: IQuotes): Promise
},
"translate": {
- "invoice": `Quote`,
+ "invoice": `Quote #${quote.id}`,
"number": GetText().invoice.txt_Number,
"date": GetText().invoice.txt_Date,
"due-date": GetText().invoice.txt_DueDate,
@@ -49,11 +49,11 @@ export default function createQuotePdf(quote: IQuotes): Promise
"margin-bottom": 25,
},
"sender": {
- "company": Company_Name,
- "address": Company_Address,
- "zip": Company_Zip,
- "city": Company_City,
- "country": Company_Country,
+ "company": (await Company_Name()),
+ "address": await Company_Address(),
+ "zip": await Company_Zip(),
+ "city": await Company_City(),
+ "country": await Company_Country(),
},
"client": {
"company": Customer.billing.company ?? `${Customer.personal.first_name} ${Customer.personal.last_name}`,
@@ -84,9 +84,9 @@ export default function createQuotePdf(quote: IQuotes): Promise
data["client"]["custom1"] = `
Innehar ${await Company_Tax_Registered() ? "" : "inte"} F-Skattsedel`;
- if(Company_Logo_Url && PDF_Template_Url === "")
+ if(await Company_Logo_Url() !== "" && PDF_Template_Url === "")
// @ts-ignore
- data["images"]["logo"] = Company_Logo_Url;
+ data["images"]["logo"] = await Company_Logo_Url();
if(PDF_Template_Url !== "")
// @ts-ignore
From d57cf2e3a0385e8ecc2a93fa2a5427b61d0d9d04 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 23:07:56 +0100
Subject: [PATCH 09/14] feat: :sparkles: Added `/my/quotes`
---
.../Routes/v2/Customers/Customers.config.ts | 57 +++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/src/Server/Routes/v2/Customers/Customers.config.ts b/src/Server/Routes/v2/Customers/Customers.config.ts
index 2a3eaf6..72a62c7 100644
--- a/src/Server/Routes/v2/Customers/Customers.config.ts
+++ b/src/Server/Routes/v2/Customers/Customers.config.ts
@@ -27,6 +27,7 @@ import { idImages } from "../../../../Lib/Generator";
import { CacheImages } from "../../../../Cache/Image.cache";
import ImageModel from "../../../../Database/Models/Images.model";
import Jimp from 'jimp';
+import QuotesModel from "../../../../Database/Models/Quotes.model";
export = class CustomerRouter
{
@@ -319,6 +320,62 @@ export = class CustomerRouter
return APISuccess("Order cancelled.")(res);
});
+ this.router.get("/my/quotes", EnsureAuth(), async (req, res) =>
+ {
+ const customer = await CustomerModel.findOne({
+ // @ts-ignore
+ id: req.customer.id
+ });
+
+ if(!customer)
+ return APIError(`Unable to find customer`)(res);
+
+ const data = await MongoFind(QuotesModel, req.query,{
+ $or: [
+ { customer_uid: customer.uid },
+ { customer_uid: customer.id }
+ ]
+ });
+
+ res.setHeader("X-Total-Pages", data.totalPages);
+ res.setHeader("X-Total", data.totalCount);
+
+ return APISuccess(data.data)(res);
+ });
+
+ this.router.get("/my/quotes/:id", EnsureAuth(), async (req, res) =>
+ {
+ const quoteId = req.params.id;
+
+ if(!quoteId)
+ return APIError(`Invalid invoice id`)(res);
+
+ const customer = await CustomerModel.findOne({
+ // @ts-ignore
+ id: req.customer.id
+ });
+
+ if(!customer)
+ return APIError(`Unable to find customer`)(res);
+
+ const {data: [order]} = await MongoFind(QuotesModel, req.query,{
+ $or: [
+ {
+ customer_uid: customer.uid,
+ },
+ {
+ customer_uid: customer.id,
+ },
+ ],
+ id: quoteId,
+ });
+
+ if(!order)
+ return APIError(`Unable to find quote`)(res);
+
+ return APISuccess(order)(res);
+ });
+
this.router.get("/my/transactions", EnsureAuth(), async (req, res) =>
{
const customer = await CustomerModel.findOne({
From a2383f28ad1a06273c13359c6ede5bbab373512a Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Thu, 10 Mar 2022 23:08:08 +0100
Subject: [PATCH 10/14] fix: :bug: Checking if already accepted
---
src/Server/Routes/v2/Quotes/Quotes.config.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/Server/Routes/v2/Quotes/Quotes.config.ts b/src/Server/Routes/v2/Quotes/Quotes.config.ts
index 8defe8f..fc3e2b9 100644
--- a/src/Server/Routes/v2/Quotes/Quotes.config.ts
+++ b/src/Server/Routes/v2/Quotes/Quotes.config.ts
@@ -77,6 +77,9 @@ export = class QuotesRouter
if(!customer)
return APIError(`Failed to fetch customer with uid ${quote.customer_uid}`)(res);
+ if(quote.accepted)
+ return APIError(`Quote already accepted`)(res);
+
quote.accepted = true;
await quote.save();
From 7302956609ce3475438b5780aee3ffaa9987bb7f Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Sat, 12 Mar 2022 15:28:48 +0100
Subject: [PATCH 11/14] chore: :label: v2.8
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 0f37039..9bdfcd5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cpg-api",
- "version": "v2.7",
+ "version": "v2.8",
"description": "Central Payment Gateway",
"main": "./build/Main.js",
"dependencies": {
From 89e26a707b18c398c61f8895d3a9f7060507fd36 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Sat, 12 Mar 2022 15:29:10 +0100
Subject: [PATCH 12/14] fix: :bug: Possible fix to #94
---
src/Lib/Orders/newInvoice.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Lib/Orders/newInvoice.ts b/src/Lib/Orders/newInvoice.ts
index f91ef08..15cb1da 100644
--- a/src/Lib/Orders/newInvoice.ts
+++ b/src/Lib/Orders/newInvoice.ts
@@ -90,7 +90,9 @@ export async function createInvoiceFromOrder(order: IOrder)
customer_uid: Customer_Id,
dates: {
due_date: order.dates.next_recycle,
- invoice_date: dateFormat.format(new Date(), "YYYY-MM-DD"),
+ // Possible fix to issue #94
+ invoice_date: order.dates.last_recycle,
+ // invoice_date: dateFormat.format(new Date(), "YYYY-MM-DD"),
},
amount: items.reduce((acc, item) =>
{
From 62a743fada35ffcdcd5efc4111c38e289728ef09 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Sat, 12 Mar 2022 15:43:40 +0100
Subject: [PATCH 13/14] fix: :art: Added `id` in transactions
---
src/Database/Models/Transactions.model.ts | 8 ++++----
src/Interfaces/Transactions.interface.ts | 5 ++---
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/Database/Models/Transactions.model.ts b/src/Database/Models/Transactions.model.ts
index a30d2b1..febf518 100644
--- a/src/Database/Models/Transactions.model.ts
+++ b/src/Database/Models/Transactions.model.ts
@@ -1,7 +1,7 @@
-import mongoose, { model, Schema } from "mongoose"
+import mongoose, { Document, model, Schema } from "mongoose"
import increment from "mongoose-auto-increment";
import { Default_Language, MongoDB_URI } from "../../Config";
-import { IDTransactions } from "@interface/Transactions.interface";
+import { ITransactions } from "@interface/Transactions.interface";
import Logger from "../../Lib/Logger";
import GetText from "../../Translation/GetText";
import { A_CC_Payments } from "../../Types/PaymentMethod";
@@ -58,7 +58,7 @@ const TransactionsSchema = new Schema
);
// Log when a transaction is created
-TransactionsSchema.post('save', function(doc: IDTransactions)
+TransactionsSchema.post('save', function(doc: ITransactions & Document)
{
Logger.db(GetText(Default_Language).database.txt_Model_Created(doc.modelName, doc.uid));
// Logger.db(`Created transaction ${doc.uid}`);
@@ -74,6 +74,6 @@ TransactionsSchema.plugin(increment.plugin, {
incrementBy: 1
});
-const TransactionsModel = model("transactions", TransactionsSchema);
+const TransactionsModel = model("transactions", TransactionsSchema);
export default TransactionsModel;
\ No newline at end of file
diff --git a/src/Interfaces/Transactions.interface.ts b/src/Interfaces/Transactions.interface.ts
index 03eaa04..d45776d 100644
--- a/src/Interfaces/Transactions.interface.ts
+++ b/src/Interfaces/Transactions.interface.ts
@@ -13,6 +13,7 @@ import { IInvoice } from "./Invoice.interface";
*/
export interface ITransactions
{
+ id: number;
uid: `TRAN_${string}`;
customer_uid: ICustomer["uid"];
invoice_uid: IInvoice["uid"];
@@ -21,6 +22,4 @@ export interface ITransactions
amount: IInvoice["amount"];
currency: TPaymentCurrency;
fees: number;
-}
-
-export interface IDTransactions extends ITransactions, Document {}
\ No newline at end of file
+}
\ No newline at end of file
From 72295466315d52b740ca6eedd4fa7818624bf362 Mon Sep 17 00:00:00 2001
From: Tolfx
Date: Sat, 12 Mar 2022 15:44:01 +0100
Subject: [PATCH 14/14] feat: :art: Added `Quote accepted` template email
---
.../Quotes/Quote.accepted.template.ts | 23 +++++++++++++++++++
src/Server/Routes/v2/Quotes/Quotes.config.ts | 10 ++++++++
2 files changed, 33 insertions(+)
create mode 100644 src/Email/Templates/Quotes/Quote.accepted.template.ts
diff --git a/src/Email/Templates/Quotes/Quote.accepted.template.ts b/src/Email/Templates/Quotes/Quote.accepted.template.ts
new file mode 100644
index 0000000..c2f4a36
--- /dev/null
+++ b/src/Email/Templates/Quotes/Quote.accepted.template.ts
@@ -0,0 +1,23 @@
+import { stripIndents } from "common-tags";
+import { CPG_Customer_Panel_Domain } from "../../../Config";
+import { ICustomer } from "@interface/Customer.interface";
+import getFullName from "../../../Lib/Customers/getFullName";
+import UseStyles from "../General/UseStyles";
+import { IQuotes } from "@interface/Quotes.interface";
+
+export default async (quote: IQuotes, customer: ICustomer) => await UseStyles(stripIndents`
+
+
Hello ${getFullName(customer)}.
+
+ This is a notice that quote #${quote.id} has been accepted.
+
+
+ We will generate a invoice for you shortly.
+
+ ${CPG_Customer_Panel_Domain ? `
+
+ View quote.
+
+ ` : ''}
+
+`);
\ No newline at end of file
diff --git a/src/Server/Routes/v2/Quotes/Quotes.config.ts b/src/Server/Routes/v2/Quotes/Quotes.config.ts
index fc3e2b9..2ea4f45 100644
--- a/src/Server/Routes/v2/Quotes/Quotes.config.ts
+++ b/src/Server/Routes/v2/Quotes/Quotes.config.ts
@@ -9,6 +9,8 @@ import EnsureAdmin from "../../../../Middlewares/EnsureAdmin";
import EnsureAuth from "../../../../Middlewares/EnsureAuth";
import QuotesController from "./Quotes.controller";
import { sendInvoiceEmail } from "../../../../Lib/Invoices/SendEmail";
+import { sendEmail } from "../../../../Email/Send";
+import QuoteAcceptedTemplate from "../../../../Email/Templates/Quotes/Quote.accepted.template";
export = class QuotesRouter
{
@@ -84,6 +86,14 @@ export = class QuotesRouter
await quote.save();
+ await sendEmail({
+ receiver: customer.personal.email,
+ subject: `Quote accepted | #${quote.id}`,
+ body: {
+ body: QuoteAcceptedTemplate(quote, customer),
+ }
+ });
+
// Convert quote to invoice
const invoice = await QuoteToInvoice(quote);
if(!invoice)