From 3453109715451cbf65019a35f81558a1d7ddd5c9 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 10:59:43 +0200 Subject: [PATCH 01/27] feat: :sparkles: Added email prompt --- src/Admin/AdminHandler.ts | 89 +------------------------ src/Admin/Commands/Email.prompt.ts | 102 +++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 87 deletions(-) create mode 100644 src/Admin/Commands/Email.prompt.ts diff --git a/src/Admin/AdminHandler.ts b/src/Admin/AdminHandler.ts index b6db48e..312fd64 100644 --- a/src/Admin/AdminHandler.ts +++ b/src/Admin/AdminHandler.ts @@ -1,27 +1,13 @@ -import { stripIndent } from "common-tags"; import prompt from "prompt"; -import { CacheAdmin } from "../Cache/Admin.cache"; -import { cron_chargeStripePayment, cron_notifyInvoices, cron_notifyLateInvoicePaid } from "../Cron/Methods/Invoices.cron.methods"; -import AdminModel from "../Database/Models/Administrators.model"; import ConfigModel from "../Database/Models/Configs.model"; import Logger from "../Lib/Logger"; import { getPlugins, installPlugin } from "../Plugins/PluginHandler"; -import createAdmin from "./CreateAdmin"; import chalk from "chalk"; import clear from "clear"; import figlet from "figlet"; import Prompt, { cacheCommands } from "./Commands/Prompt"; import inquirer from 'inquirer'; -export interface ICommandsAdmin -{ - [key: string]: { - description: string; - method: any; - [key: string]: any; - } -} - export default class AdminHandler { @@ -67,79 +53,8 @@ export default class AdminHandler } }).catch((error) => { - console.log(error) - }); - } - - - private async show_emails() - { - return new Promise(async (resolve,) => - { - // Get our config from database - const config = (await ConfigModel.find())[0]; - Logger.info(`Emails:`, config.smtp_emails); - resolve(true); - }); - } - - private async add_email() - { - return new Promise((resolve) => - { - prompt.get([ - { - name: "email", - description: "Email for administrator", - required: true - }, - ], async (err, result) => - { - const email = result.email as string; - Logger.info(`Adding email..`); - // Check if email is valid - // eslint-disable-next-line no-useless-escape - if(!email.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) - { - Logger.error(`Invalid email`); - return resolve(false); - } - // Get our config from database - const config = (await ConfigModel.find())[0]; - - config.smtp_emails.push(email); - - // Save our config - await config.save(); - return resolve(true) - }); - }); - } - - private async delete_email() - { - return new Promise((resolve) => - { - prompt.get([ - { - name: "email", - description: "Email for administrator", - required: true - }, - ], async (err, result) => - { - const email = result.email as string; - Logger.info(`Deleting email..`); - // Get our config from database - const config = (await ConfigModel.find())[0]; - - // Remove email - config.smtp_emails = config.smtp_emails.filter(e => e !== email); - - // Save our config - await config.save(); - return resolve(true) - }); + Logger.error(error); + this.action(); }); } diff --git a/src/Admin/Commands/Email.prompt.ts b/src/Admin/Commands/Email.prompt.ts new file mode 100644 index 0000000..08d9c46 --- /dev/null +++ b/src/Admin/Commands/Email.prompt.ts @@ -0,0 +1,102 @@ +/* eslint-disable no-case-declarations */ +import Logger from "../../Lib/Logger"; +import prompt from "prompt"; +import ConfigModel from "../../Database/Models/Configs.model"; + +export default +{ + name: 'Emails', + description: 'Get all email jobs', + args: [ + { + name: 'action', + type: "list", + message: "Select the email job you want to run", + choices: [ + { + name: 'Show emails', + value: 'show_emails', + }, + { + name: 'Add email', + value: 'add_email', + }, + { + name: 'Delete email', + value: 'delete_email', + } + ], + } + ], + method: async ({action}: {action: string}) => + { + switch(action) + { + case 'show_emails': + return new Promise(async (resolve,) => + { + // Get our config from database + const config = (await ConfigModel.find())[0]; + Logger.info(`Emails:`, config.smtp_emails); + resolve(true); + }); + + case 'add_email': + return new Promise((resolve) => + { + prompt.get([ + { + name: "email", + description: "Email for administrator", + required: true + }, + ], async (err, result) => + { + const email = result.email as string; + Logger.info(`Adding email..`); + // Check if email is valid + // eslint-disable-next-line no-useless-escape + if(!email.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) + { + Logger.error(`Invalid email`); + return resolve(false); + } + // Get our config from database + const config = (await ConfigModel.find())[0]; + + config.smtp_emails.push(email); + + // Save our config + await config.save(); + return resolve(true) + }); + }); + case 'delete_email': + return new Promise((resolve) => + { + prompt.get([ + { + name: "email", + description: "Email for administrator", + required: true + }, + ], async (err, result) => + { + const email = result.email as string; + Logger.info(`Deleting email..`); + // Get our config from database + const config = (await ConfigModel.find())[0]; + + // Remove email + config.smtp_emails = config.smtp_emails.filter(e => e !== email); + + // Save our config + await config.save(); + return resolve(true) + }); + }); + + } + return true; + } +} \ No newline at end of file From e0d48b808769328247bb2ce8663b6f97d69ae2db Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 11:00:10 +0200 Subject: [PATCH 02/27] feat: :sparkles: See late invoices --- src/Admin/Commands/Invoices.prompt.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index d2b37a1..8bc3912 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -7,6 +7,7 @@ import { Company_Currency } from "../../Config"; import { getDate } from "../../Lib/Time"; import { idTransactions } from "../../Lib/Generator"; import sendEmailOnTransactionCreation from "../../Lib/Transaction/SendEmailOnCreation"; +import { getDates30DaysAgo } from "../../Cron/Methods/Invoices.cron.methods"; export default { @@ -26,6 +27,10 @@ export default name: 'Get invoice', value: 'get_invoice', }, + { + name: 'Get late invoices', + value: 'get_late_invoices', + }, { name: 'Mark invoice as paid', value: 'mark_invoice_paid', @@ -66,7 +71,21 @@ export default id: id, })); break; - + case 'get_late_invoices': + { + const invoices = await InvoiceModel.find({ + "dates.due_date": { + $in: [...(getDates30DaysAgo())] + }, + paid: false, + status: { + $not: /fraud|cancelled|draft|refunded/g + } + }); + Logger.info(`Total late invoices:`, invoices.length); + Logger.info(`Late invoices ids:`, invoices.map(e => e.id)); + break; + } case 'mark_invoice_paid': { const action = [ From b371030365b469ecd65830fc8235c3693fa35df6 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 12:04:14 +0200 Subject: [PATCH 03/27] refactor: :art: Cleaned admin handler --- src/Admin/AdminHandler.ts | 101 -------------------------------------- 1 file changed, 101 deletions(-) diff --git a/src/Admin/AdminHandler.ts b/src/Admin/AdminHandler.ts index 312fd64..fe8dc69 100644 --- a/src/Admin/AdminHandler.ts +++ b/src/Admin/AdminHandler.ts @@ -1,7 +1,4 @@ -import prompt from "prompt"; -import ConfigModel from "../Database/Models/Configs.model"; import Logger from "../Lib/Logger"; -import { getPlugins, installPlugin } from "../Plugins/PluginHandler"; import chalk from "chalk"; import clear from "clear"; import figlet from "figlet"; @@ -58,102 +55,4 @@ export default class AdminHandler }); } - - private async add_webhook() - { - return new Promise((resolve) => - { - prompt.get([ - { - name: "url", - description: "URL for webhook", - required: true - }, - ], async (err, result) => - { - const url = result.url as string; - Logger.info(`Adding webhook..`); - // Get our config from database - const config = (await ConfigModel.find())[0]; - - // Add webhook - config.webhooks_urls.push(url); - - // Save our config - await config.save(); - return resolve(true) - }); - }); - } - - private async delete_webhook() - { - return new Promise((resolve) => - { - prompt.get([ - { - name: "url", - description: "URL for webhook", - required: true - }, - ], async (err, result) => - { - const url = result.url as string; - Logger.info(`Deleting webhook..`); - // Get our config from database - const config = (await ConfigModel.find())[0]; - - // Remove webhook - config.webhooks_urls = config.webhooks_urls.filter((e: any) => e !== url); - - // Save our config - await config.save(); - return resolve(true) - }); - }); - } - - private async show_webhooks() - { - return new Promise(async (resolve) => - { - // Get our config from database - const config = (await ConfigModel.find())[0]; - Logger.info(`Webhooks:`, config.webhooks_urls); - resolve(true); - }); - } - - private async show_plugins() - { - return new Promise(async (resolve) => - { - // Get our config from database - const plugins = getPlugins() - Logger.info(`Plugins:`, ...plugins); - resolve(true); - }); - } - - private async update_plugin() - { - return new Promise(async (resolve) => - { - prompt.get([ - { - name: "plugin", - description: "Plugin", - required: false, - type: "string", - }, - ], async (err, result) => - { - Logger.info(`Updating plugins..`); - const plugin = result.plugin as string; - if(plugin) - await installPlugin(`${plugin}@latest`); - return resolve(true) - }); - }); - } } \ No newline at end of file From 9682231fd390de5b99a80215448962a8334e2f91 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 12:04:37 +0200 Subject: [PATCH 04/27] style: :art: Formatting --- src/Admin/Commands/Admin.prompt.ts | 3 --- src/Admin/Commands/Invoices.prompt.ts | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Admin/Commands/Admin.prompt.ts b/src/Admin/Commands/Admin.prompt.ts index 6db952c..8a2ebe8 100644 --- a/src/Admin/Commands/Admin.prompt.ts +++ b/src/Admin/Commands/Admin.prompt.ts @@ -1,9 +1,6 @@ /* eslint-disable no-case-declarations */ import Logger from "../../Lib/Logger"; import prompt from "prompt"; -import { CacheConfig } from "../../Cache/Configs.cache"; -import ConfigModel from "../../Database/Models/Configs.model"; -import updateSMTP from "../updateSMTP"; import AdminModel from "../../Database/Models/Administrators.model"; import createAdmin from "../CreateAdmin"; diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index 8bc3912..53a5a47 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -64,8 +64,8 @@ export default const { invoiceId, isOCR } = await inquirer.prompt(action); let id = invoiceId; if(isOCR) - // get id from ocr, by removing the last 8 digits in the start - id = invoiceId.substring(0, invoiceId.length - 8); + // get id from ocr, by removing first 8 characters + id = invoiceId.substring(8); Logger.info(`Invoice:`, await InvoiceModel.findOne({ id: id, From 3a1da0b9feccb916ad75874215845d8d40a7b14e Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 12:04:49 +0200 Subject: [PATCH 05/27] feat: :sparkles: Webhook prompts --- src/Admin/Commands/Webhook.prompt.ts | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/Admin/Commands/Webhook.prompt.ts diff --git a/src/Admin/Commands/Webhook.prompt.ts b/src/Admin/Commands/Webhook.prompt.ts new file mode 100644 index 0000000..0c56f99 --- /dev/null +++ b/src/Admin/Commands/Webhook.prompt.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-case-declarations */ +import Logger from "../../Lib/Logger"; +import prompt from "prompt"; +import ConfigModel from "../../Database/Models/Configs.model"; + +export default +{ + name: 'Webhooks', + description: 'Get all webhook jobs', + args: [ + { + name: 'action', + type: "list", + message: "Select the webhook job you want to run", + choices: [ + { + name: 'Show webhooks', + value: 'show_webhooks', + }, + { + name: 'Add webhook', + value: 'add_webhook', + }, + { + name: 'Delete webhook', + value: 'delete_webhook', + } + ], + } + ], + method: async ({action}: {action: string}) => + { + switch(action) + { + case 'show_webhooks': + return new Promise(async (resolve) => + { + // Get our config from database + const config = (await ConfigModel.find())[0]; + Logger.info(`Webhooks:`, config.webhooks_urls); + resolve(true); + }); + + case 'add_email': + return new Promise((resolve) => + { + prompt.get([ + { + name: "url", + description: "URL for webhook", + required: true + }, + ], async (err, result) => + { + const url = result.url as string; + Logger.info(`Adding webhook..`); + // Get our config from database + const config = (await ConfigModel.find())[0]; + + // Add webhook + config.webhooks_urls.push(url); + + // Save our config + await config.save(); + return resolve(true) + }); + }); + case 'delete_email': + return new Promise((resolve) => + { + prompt.get([ + { + name: "url", + description: "URL for webhook", + required: true + }, + ], async (err, result) => + { + const url = result.url as string; + Logger.info(`Deleting webhook..`); + // Get our config from database + const config = (await ConfigModel.find())[0]; + + // Remove webhook + config.webhooks_urls = config.webhooks_urls.filter((e: any) => e !== url); + + // Save our config + await config.save(); + return resolve(true) + }); + }); + + } + return true; + } +} \ No newline at end of file From e6713880d382082958e83a7e40181a66424b8a3d Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 15:05:22 +0200 Subject: [PATCH 06/27] feat: :package: Added new plugin --- package.json | 1 + src/Admin/AdminHandler.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 6864c3a..ccba848 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "graphql-compose-mongoose": "^9.7.1", "graphql-resolvers": "^0.4.2", "inquirer": "^8.2.2", + "inquirer-search-list": "^1.2.6", "jimp": "^0.16.1", "jsonwebtoken": "^8.5.1", "mongoose": "^6.1.4", diff --git a/src/Admin/AdminHandler.ts b/src/Admin/AdminHandler.ts index fe8dc69..d37721c 100644 --- a/src/Admin/AdminHandler.ts +++ b/src/Admin/AdminHandler.ts @@ -18,6 +18,7 @@ export default class AdminHandler }) ) ) + inquirer.registerPrompt('search-list', require('inquirer-search-list')); this.action(); } From 22313c2249d271c712061a0e4844301b15054568 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 15:05:45 +0200 Subject: [PATCH 07/27] feat: :sparkles: Possible to create invoice in CLI --- src/Admin/Commands/Invoices.prompt.ts | 123 +++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index 53a5a47..ab154f4 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -5,9 +5,14 @@ import inquirer from 'inquirer'; import TransactionsModel from "../../Database/Models/Transactions.model"; import { Company_Currency } from "../../Config"; import { getDate } from "../../Lib/Time"; -import { idTransactions } from "../../Lib/Generator"; +import { idInvoice, idTransactions } from "../../Lib/Generator"; import sendEmailOnTransactionCreation from "../../Lib/Transaction/SendEmailOnCreation"; import { getDates30DaysAgo } from "../../Cron/Methods/Invoices.cron.methods"; +import { A_CC_Payments } from "../../Types/PaymentMethod"; +import { currencyCodes } from "../../Lib/Currencies"; +import CustomerModel from "../../Database/Models/Customers/Customer.model"; +import mainEvent from "../../Events/Main.event"; +import { sendInvoiceEmail } from "../../Lib/Invoices/SendEmail"; export default { @@ -34,6 +39,10 @@ export default { name: 'Mark invoice as paid', value: 'mark_invoice_paid', + }, + { + name: 'Create invoice', + value: 'create_invoice', } ], } @@ -145,6 +154,118 @@ export default await invoice.save(); Logger.info(`Invoice with id ${invoiceId} marked as paid`); + break; + } + case 'create_invoice': + { + const action = [ + { + name: 'customerId', + type: 'search-list', + message: 'Customer', + choices: (await CustomerModel.find()).map(e => + { + return { + name: `${e.personal.first_name} ${e.personal.last_name} (${e.id})`, + value: e.id, + } + }) + }, + { + name: 'invoice_date', + type: 'input', + message: 'Enter the invoiced date', + }, + { + name: 'due_date', + type: 'input', + message: 'Enter the due date', + }, + { + name: 'amount', + type: 'number', + message: 'Enter the amount', + }, + { + name: 'tax_rate', + type: 'number', + message: 'Enter the tax rate', + }, + { + name: 'payment_method', + type: 'search-list', + message: 'Enter the payment method', + choices: A_CC_Payments + }, + { + name: 'currency', + type: 'search-list', + message: 'Enter the currency', + choices: currencyCodes + }, + { + name: 'notes', + type: 'input', + message: 'Enter the notes', + }, + { + name: "items", + type: "input", + message: "Enter the items (separated by ';') (name,quantity,price;...)", + response: 'array', + validate: (value: string) => + { + if (value.split(';').length < 1) + return 'Please enter at least one item'; + return true; + } + }, + { + name: "send_email", + type: "confirm", + message: "Send notification?", + default: true, + } + ] + const { customerId, invoice_date, due_date, amount, tax_rate, payment_method, currency, notes, items, send_email } = await inquirer.prompt(action); + const customer = await CustomerModel.findOne({ id: customerId }) + if(!customer) + return Logger.error(`Customer with id ${customerId} not found`); + // parse items + const nItems = items.split(';').map((e: any) => + { + const [notes, quantity, price] = e.split(','); + return { + notes, + quantity: Number(quantity), + price: Number(price), + } + }); + + const invoice = await (new InvoiceModel({ + customer_uid: customerId, + dates: { + invoice_date: invoice_date, + due_date: due_date, + }, + amount, + tax_rate, + payment_method, + currency, + notes, + nItems, + status: "active", + paid: false, + notified: false, + uid: idInvoice(), + transactions: [], + }).save()); + Logger.info(`Invoice created with id ${invoice.id}`); + mainEvent.emit("invoice_created", invoice); + if(send_email) + await sendInvoiceEmail(invoice, customer); + + break; } } return true; From 1541ba8658f7962e0cdebfdf82d5d7c0b184cbd7 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 8 Apr 2022 15:20:49 +0200 Subject: [PATCH 08/27] fix: :pencil2: Fixed typing errors --- src/Admin/Commands/Invoices.prompt.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index ab154f4..a06cf44 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -247,6 +247,9 @@ export default dates: { invoice_date: invoice_date, due_date: due_date, + date_refunded: null, + date_cancelled: null, + date_paid: null }, amount, tax_rate, From 061cd8b47db078d7a218c708268d292ef61b7bb1 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Tue, 12 Apr 2022 12:57:18 +0200 Subject: [PATCH 09/27] feat: :label: v3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ccba848..9742856 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cpg-api", - "version": "v3.1", + "version": "v3.2", "description": "Central Payment Gateway", "main": "./build/Main.js", "dependencies": { From 6e79e0df4145b7177aaa30536d98b36c6cf43ec0 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Tue, 12 Apr 2022 12:57:35 +0200 Subject: [PATCH 10/27] fix: :bug: Fixed `items` --- src/Admin/Commands/Invoices.prompt.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index a06cf44..0f905d5 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -232,15 +232,19 @@ export default if(!customer) return Logger.error(`Customer with id ${customerId} not found`); // parse items - const nItems = items.split(';').map((e: any) => + const nItems = (items as string).split(';').map((e: string) => { + if(e === "") + return null; const [notes, quantity, price] = e.split(','); + if(!notes || !quantity || !price) + null; return { notes, quantity: Number(quantity), - price: Number(price), + amount: Number(price), } - }); + }).filter(e => e); const invoice = await (new InvoiceModel({ customer_uid: customerId, @@ -256,13 +260,14 @@ export default payment_method, currency, notes, - nItems, + items: nItems, status: "active", paid: false, notified: false, uid: idInvoice(), transactions: [], }).save()); + Logger.info(`Invoice created with id ${invoice.id}`); mainEvent.emit("invoice_created", invoice); if(send_email) From d1ee9285d6461d83027159d6e7ae9b346f42cf19 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Tue, 12 Apr 2022 13:32:23 +0200 Subject: [PATCH 11/27] feat: :sparkles: Table on `invoices` --- src/Admin/Commands/Invoices.prompt.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index 0f905d5..91418f8 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -53,7 +53,21 @@ export default { case 'get_invoices': // Getting all invoices - Logger.info(`All invoices:`, await InvoiceModel.find()); + const invoices = (await InvoiceModel.find()).map(invoice => + { + return { + id: invoice.id, + customer: invoice.customer_uid, + amount: invoice.amount, + currency: invoice.currency, + due_date: invoice.dates?.due_date, + invoice_date: invoice.dates?.invoice_date, + notified: invoice.notified, + paid: invoice.paid, + status: invoice.status, + } + }); + console.table(invoices); break; case 'get_invoice': From 0db5561b31ae1055c61fd6db1ad69a39a8dd8f72 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Wed, 13 Apr 2022 16:12:15 +0200 Subject: [PATCH 12/27] fix: :pencil2: Fixed typing errors --- src/Admin/Commands/Invoices.prompt.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index 91418f8..e25eb12 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -13,6 +13,7 @@ import { currencyCodes } from "../../Lib/Currencies"; import CustomerModel from "../../Database/Models/Customers/Customer.model"; import mainEvent from "../../Events/Main.event"; import { sendInvoiceEmail } from "../../Lib/Invoices/SendEmail"; +import { IInvoice } from "@interface/Invoice.interface"; export default { @@ -274,7 +275,7 @@ export default payment_method, currency, notes, - items: nItems, + items: nItems as IInvoice['items'], status: "active", paid: false, notified: false, From 20f92d1ce3b4071b0fad4ae1cada95bf97942200 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Wed, 13 Apr 2022 21:54:55 +0200 Subject: [PATCH 13/27] perf: :zap: Improved place order route --- src/Server/Routes/v2/Orders/Orders.config.ts | 193 +++++++++---------- 1 file changed, 88 insertions(+), 105 deletions(-) diff --git a/src/Server/Routes/v2/Orders/Orders.config.ts b/src/Server/Routes/v2/Orders/Orders.config.ts index 9c99a7c..a8cfb26 100644 --- a/src/Server/Routes/v2/Orders/Orders.config.ts +++ b/src/Server/Routes/v2/Orders/Orders.config.ts @@ -30,19 +30,28 @@ import { sanitizeMongoose } from "../../../../Lib/Sanitize"; import { getEnabledPaymentMethods } from "../../../../Cache/Configs.cache"; import { setTypeValueOfObj } from "../../../../Lib/Sanitize"; -async function createOrder(customer: ICustomer, products: Array<{ - product_id: IProduct["id"], - quantity: number, - configurable_options?: Array<{ - id: IConfigurableOptions["id"], - option_index?: number, - }>; -}>, _products: IProduct[], payment_method: string, billing_type: string, currency: TPaymentCurrency, billing_cycle?: TRecurringMethod) +async function createOrder(payload: { + customer: ICustomer, + products: Array<{ + product_id: IProduct["id"], + quantity: number, + configurable_options?: Array<{ + id: IConfigurableOptions["id"], + option_index?: number, + }>; + }>, + _products: IProduct[], + payment_method: keyof IPayments, + billing_type: TPaymentTypes, + billing_cycle?: TRecurringMethod, + currency: TPaymentCurrency, + fees: number, +}) { const order = await (new OrderModel({ - customer_uid: customer.id, + customer_uid: payload.customer.id, // @ts-ignore - products: products.map(product => + products: payload.products.map(product => { return { product_id: product.product_id, @@ -50,26 +59,27 @@ async function createOrder(customer: ICustomer, products: Array<{ quantity: product.quantity, } }), - payment_method: payment_method as keyof IPayments, + payment_method: payload.payment_method as keyof IPayments, order_status: "active", - billing_type: billing_type as TPaymentTypes, - billing_cycle: billing_cycle, + billing_type: payload.billing_type as TPaymentTypes, + billing_cycle: payload.billing_cycle, + fees: payload.fees, dates: { createdAt: new Date(), next_recycle: dateFormat.format(nextRecycleDate( - new Date(), billing_cycle ?? "monthly") + new Date(), payload.billing_cycle ?? "monthly") , "YYYY-MM-DD"), last_recycle: dateFormat.format(new Date(), "YYYY-MM-DD") }, - currency: currency, + currency: payload.currency, uid: idOrder(), }).save()); mainEvent.emit("order_created", order); - await SendEmail(customer.personal.email, `New order from ${await Company_Name() !== "" ? await Company_Name() : "CPG"} #${order.id}`, { + await SendEmail(payload.customer.personal.email, `New order from ${await Company_Name() !== "" ? await Company_Name() : "CPG"} #${order.id}`, { isHTML: true, - body: await NewOrderCreated(order, customer), + body: await NewOrderCreated(order, payload.customer), }); } export = OrderRoute; @@ -101,7 +111,7 @@ class OrderRoute }>; }>; const payment_method = req.body.payment_method as TPayments; - + const fees = parseInt(req.body.fees ?? "0") as number ?? 0; // Check if payment_method is valid const validPaymentMethods = getEnabledPaymentMethods(); @@ -165,6 +175,7 @@ class OrderRoute , "YYYY-MM-DD"), last_recycle: dateFormat.format(new Date(), "YYYY-MM-DD") }, + fees: fees, uid: idOrder(), // @ts-ignore invoices: [], @@ -172,38 +183,49 @@ class OrderRoute promotion_code: promotion_code?.id, } - const one_timers = []; - const recurring_monthly = []; - const recurring_quarterly = []; - const recurring_semi_annually = []; - const recurring_biennially = []; - const recurring_triennially = []; - const recurring_yearly = []; + const recurringMethods = { + one_timers: [], + monthly: [], + yearly: [], + quarterly: [], + semi_annually: [], + biennially: [], + triennially: [], + }; // Possible to get a Dos attack // ! prevent this for (const p of _products) { if(p.payment_type === "one_time") - one_timers.push(p); - - if(p.payment_type === "recurring" && p.recurring_method === "monthly") - recurring_monthly.push(p); - - if(p.payment_type === "recurring" && p.recurring_method === "quarterly") - recurring_quarterly.push(p); - - if(p.payment_type === "recurring" && p.recurring_method === "semi_annually") - recurring_semi_annually.push(p); + recurringMethods["one_timers"].push(p); - if(p.payment_type === "recurring" && p.recurring_method === "biennially") - recurring_biennially.push(p); - - if(p.payment_type === "recurring" && p.recurring_method === "triennially") - recurring_triennially.push(p); - - if(p.payment_type === "recurring" && p.recurring_method === "yearly") - recurring_yearly.push(p); + if(p.payment_type === "recurring") + { + switch(p.recurring_method) + { + case "monthly": + recurringMethods["monthly"].push(p); + break; + case "quarterly": + recurringMethods["quarterly"].push(p); + break; + case "semi_annually": + recurringMethods["semi_annually"].push(p); + break; + case "biennially": + recurringMethods["biennially"].push(p); + break; + case "triennially": + recurringMethods["triennially"].push(p); + break; + case "yearly": + recurringMethods["yearly"].push(p); + break; + default: + break; + } + } let configurable_option: any = undefined if(products.find(e => e.product_id === p.id)?.configurable_options) @@ -217,69 +239,30 @@ class OrderRoute }); } - // Create new orders - if(recurring_monthly.length > 0) - await createOrder(customer, recurring_monthly.map(p => - { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), recurring_monthly, payment_method, "recurring", _order_.currency, "monthly"); - - if(recurring_quarterly.length > 0) - await createOrder(customer, recurring_quarterly.map(p => - { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), recurring_quarterly, payment_method, "recurring", _order_.currency, "quarterly"); - - if(recurring_semi_annually.length > 0) - await createOrder(customer, recurring_semi_annually.map(p => - { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), recurring_semi_annually, payment_method, "recurring", _order_.currency, "semi_annually"); - - if(recurring_biennially.length > 0) - await createOrder(customer, recurring_biennially.map(p => - { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), recurring_biennially, payment_method, "recurring", _order_.currency, "biennially"); - - if(recurring_triennially.length > 0) - await createOrder(customer, recurring_triennially.map(p => - { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), recurring_triennially, payment_method, "recurring", _order_.currency, "triennially"); - - if(one_timers.length > 0) - await createOrder(customer, one_timers.map(p => - { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), one_timers, payment_method, "one_time", _order_.currency); - - if(recurring_yearly.length > 0) - await createOrder(customer, recurring_yearly.map(p => + // @ts-ignore + Object.keys(recurringMethods).forEach(async (key: keyof (typeof recurringMethods)) => + { + const isOneTimer = key === "one_timers"; + if(recurringMethods[key].length > 0) { - return products.find(p2 => p2.product_id == p.id) ?? { - product_id: p.id, - quantity: 1 - } - }), recurring_yearly, payment_method, "recurring", _order_.currency, "yearly"); + await createOrder({ + customer: customer, + products: recurringMethods[key].map(p => + { + return products.find(p2 => p2.product_id == p.id) ?? { + product_id: p.id, + quantity: 1 + } + }), + payment_method: payment_method, + fees: fees, + currency: _order_.currency, + _products: recurringMethods[key], + billing_type: isOneTimer ? "one_time" : "recurring", + billing_cycle: !isOneTimer ? "monthly" : undefined, + }) + } + }); const invoice = await createInvoiceFromOrder(_order_); From afbf456da2e5f91fe4ea125ab1786ec82a5ed22f Mon Sep 17 00:00:00 2001 From: Tolfx Date: Wed, 13 Apr 2022 21:56:43 +0200 Subject: [PATCH 14/27] feat: :sparkles: `fees` input --- src/Admin/Commands/Invoices.prompt.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Admin/Commands/Invoices.prompt.ts b/src/Admin/Commands/Invoices.prompt.ts index e25eb12..9c69fa3 100644 --- a/src/Admin/Commands/Invoices.prompt.ts +++ b/src/Admin/Commands/Invoices.prompt.ts @@ -152,7 +152,7 @@ export default const t = await (new TransactionsModel({ amount: invoice.amount+invoice.amount*invoice.tax_rate/100, payment_method: invoice.payment_method, - fees: 0, + fees: invoice.fees, invoice_uid: invoice.id, customer_uid: invoice.customer_uid, currency: invoice.currency ?? await Company_Currency(), @@ -235,6 +235,11 @@ export default return true; } }, + { + name: 'fees', + type: 'number', + message: 'Enter the fees', + }, { name: "send_email", type: "confirm", @@ -242,7 +247,7 @@ export default default: true, } ] - const { customerId, invoice_date, due_date, amount, tax_rate, payment_method, currency, notes, items, send_email } = await inquirer.prompt(action); + const { customerId, invoice_date, due_date, amount, tax_rate, payment_method, currency, notes, items, send_email, fees } = await inquirer.prompt(action); const customer = await CustomerModel.findOne({ id: customerId }) if(!customer) return Logger.error(`Customer with id ${customerId} not found`); @@ -275,6 +280,7 @@ export default payment_method, currency, notes, + fees: parseInt(fees ?? '0'), items: nItems as IInvoice['items'], status: "active", paid: false, From a1fad00c5872383067b191ec19403e894e478fbc Mon Sep 17 00:00:00 2001 From: Tolfx Date: Wed, 13 Apr 2022 21:57:09 +0200 Subject: [PATCH 15/27] feat: :sparkles: Added `fees` into Order/Invoice --- src/Database/Models/Invoices.model.ts | 5 +++++ src/Database/Models/Orders.model.ts | 2 +- src/Interfaces/Invoice.interface.ts | 1 + src/Interfaces/Orders.interface.ts | 4 +--- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Database/Models/Invoices.model.ts b/src/Database/Models/Invoices.model.ts index 9690c2c..4939715 100644 --- a/src/Database/Models/Invoices.model.ts +++ b/src/Database/Models/Invoices.model.ts @@ -57,6 +57,11 @@ const InvoiceSchema = new Schema default: 0, }, + fees: { + type: Number, + default: 0, + }, + items: { type: [ { diff --git a/src/Database/Models/Orders.model.ts b/src/Database/Models/Orders.model.ts index dff38c1..18c9b14 100644 --- a/src/Database/Models/Orders.model.ts +++ b/src/Database/Models/Orders.model.ts @@ -74,7 +74,7 @@ const OrderSchema = new Schema required: false, }, - price_override: { + fees: { type: Number, default: 0, }, diff --git a/src/Interfaces/Invoice.interface.ts b/src/Interfaces/Invoice.interface.ts index 7e9dc2f..cd4c357 100644 --- a/src/Interfaces/Invoice.interface.ts +++ b/src/Interfaces/Invoice.interface.ts @@ -50,6 +50,7 @@ export interface IInvoice customer_uid: ICustomer["uid"]; dates: IInvoice_Dates; amount: number; + fees?: number; items: Array; transactions: Array; payment_method: keyof IPayments; diff --git a/src/Interfaces/Orders.interface.ts b/src/Interfaces/Orders.interface.ts index 763e3fc..48ee2b7 100644 --- a/src/Interfaces/Orders.interface.ts +++ b/src/Interfaces/Orders.interface.ts @@ -18,7 +18,6 @@ import { IPromotionsCodes } from "./PromotionsCodes.interface"; * @property {string} billing_type "free" | "one_time" | "recurring" * @property {string} billing_cycle "monthly" | "quarterly" | "semi_annually" | "biennially" | "triennially" * @property {number} quantity - * @property {number} price_override Overwrite price from product * @property {object} dates */ @@ -43,14 +42,13 @@ export interface IOrder * if 'billing_type' is "recurring" `billing_cycle` wont be undefined */ billing_cycle?: TRecurringMethod; - price_override?: number; + fees?: number; dates: IOrderDates; invoices: Array; currency: TPaymentCurrency; promotion_code?: IPromotionsCodes["id"]; } - export type TOrderStatus = "active" | "pending" | "fraud" | "cancelled"; export interface IOrderDates From b8ca21fe19ac035e7ad3960145db54b15c90e2ce Mon Sep 17 00:00:00 2001 From: Tolfx Date: Wed, 13 Apr 2022 21:57:33 +0200 Subject: [PATCH 16/27] feat: :sparkles: Printing fees into invoice/order --- src/Email/Templates/Methods/OrderProducts.print.ts | 10 ++++++++++ src/Lib/Orders/newInvoice.ts | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Email/Templates/Methods/OrderProducts.print.ts b/src/Email/Templates/Methods/OrderProducts.print.ts index 09e2fa4..48be69e 100644 --- a/src/Email/Templates/Methods/OrderProducts.print.ts +++ b/src/Email/Templates/Methods/OrderProducts.print.ts @@ -64,6 +64,16 @@ export default async function printOrderProductTable(order: IOrder, customer: IC } } + if(order.fees) + { + result += stripIndents` + + Fees + 1 + ${order.fees.toFixed(2)} ${GetCurrencySymbol(order.currency)} + ` + } + return result; }))).join("")} diff --git a/src/Lib/Orders/newInvoice.ts b/src/Lib/Orders/newInvoice.ts index 56c318d..b349c6f 100644 --- a/src/Lib/Orders/newInvoice.ts +++ b/src/Lib/Orders/newInvoice.ts @@ -106,6 +106,15 @@ export async function createInvoiceFromOrder(order: IOrder) } } + if(order.fees) + { + items.push({ + amount: order.fees, + notes: "+ Fees", + quantity: 1, + }); + } + // Create invoice const newInvoice = await (new InvoiceModel({ uid: idInvoice(), From c0b442736fefe2038121eac3d2c075a963d6ed82 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Wed, 13 Apr 2022 21:57:43 +0200 Subject: [PATCH 17/27] feat: :sparkles: Dynamic fees --- src/Payments/Paypal.ts | 2 +- src/Payments/Stripe.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Payments/Paypal.ts b/src/Payments/Paypal.ts index b67d77d..0628e26 100644 --- a/src/Payments/Paypal.ts +++ b/src/Payments/Paypal.ts @@ -133,7 +133,7 @@ export async function retrievePaypalTransaction(payerId: string, paymentId: stri const newTrans = await (new TransactionsModel({ amount: invoice.amount+invoice.amount*invoice.tax_rate/100, payment_method: invoice.payment_method, - fees: 0, + fees: invoice.fees, invoice_uid: invoice.id, customer_uid: invoice.customer_uid, currency: invoice.currency ?? await Company_Currency(), diff --git a/src/Payments/Stripe.ts b/src/Payments/Stripe.ts index 57539ae..10fd5c3 100644 --- a/src/Payments/Stripe.ts +++ b/src/Payments/Stripe.ts @@ -195,7 +195,7 @@ export const ChargeCustomer = async (invoice_id: IInvoice["id"]) => const newTrans = await (new TransactionsModel({ amount: invoice.amount+invoice.amount*invoice.tax_rate/100, payment_method: invoice.payment_method, - fees: 0, + fees: invoice.fees, invoice_uid: invoice.id, customer_uid: invoice.customer_uid, currency: invoice.currency ?? await Company_Currency(), @@ -236,7 +236,7 @@ export const markInvoicePaid = async (intent: stripe.Response Date: Thu, 14 Apr 2022 15:52:33 +0200 Subject: [PATCH 18/27] feat: :package: pdfkit-table package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9742856..ed88349 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "nodemailer": "^6.7.0", "npm": "^7.20.5", "paypal-rest-sdk": "^1.8.1", + "pdfkit-table": "^0.1.86", "pg": "^8.7.1", "pg-hstore": "^2.3.4", "prompt": "^1.2.0", From 4630f71e638fbf150b1c1c2e44b1c55e6ad36a62 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Thu, 14 Apr 2022 15:52:58 +0200 Subject: [PATCH 19/27] fix: :pencil2: Fixed typing error --- src/Config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Config.ts b/src/Config.ts index 41c17a2..217dddf 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -104,6 +104,7 @@ export const Company_Currency = async (): Promise => From 91c6d1dfd5aac2b33c9dd92dee4361d79194e352 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Thu, 14 Apr 2022 15:53:13 +0200 Subject: [PATCH 20/27] fix: :ambulance: Fixed typing errors.. --- src/Admin/Commands/Company.prompt.ts | 3 ++- src/Interfaces/Admin/Configs.interface.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Admin/Commands/Company.prompt.ts b/src/Admin/Commands/Company.prompt.ts index 140c0db..38c9714 100644 --- a/src/Admin/Commands/Company.prompt.ts +++ b/src/Admin/Commands/Company.prompt.ts @@ -3,6 +3,7 @@ import Logger from "../../Lib/Logger"; import prompt from "prompt"; import { CacheConfig } from "../../Cache/Configs.cache"; import ConfigModel from "../../Database/Models/Configs.model"; +import { TPaymentCurrency } from "../../Lib/Currencies"; export default { @@ -133,7 +134,7 @@ export default email: result.email as string, logo_url: result.logo_url as string, tax_registered: result.tax_registered === "true", - currency: result.currency as string, + currency: result.currency as TPaymentCurrency, website: result.website as string }; diff --git a/src/Interfaces/Admin/Configs.interface.ts b/src/Interfaces/Admin/Configs.interface.ts index 92537eb..630ff5a 100644 --- a/src/Interfaces/Admin/Configs.interface.ts +++ b/src/Interfaces/Admin/Configs.interface.ts @@ -1,4 +1,5 @@ import { Document } from "mongoose"; +import { TPaymentCurrency } from "../../Lib/Currencies"; import { TPayments } from "../../Types/PaymentMethod"; export interface IConfigs @@ -20,7 +21,7 @@ export interface ICompanyConfig phone: string; email: string; vat: string; - currency: string; + currency: TPaymentCurrency; logo_url: string; tax_registered: boolean; website: string; From 66c217b10af45658c700d2fcf0caad135b717e4f Mon Sep 17 00:00:00 2001 From: Tolfx Date: Thu, 14 Apr 2022 15:53:26 +0200 Subject: [PATCH 21/27] feat: :sparkles: PDF table on income/expense --- src/Server/Routes/v3/Taxes/Taxes.config.ts | 104 ++++++++++++++++++++- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/src/Server/Routes/v3/Taxes/Taxes.config.ts b/src/Server/Routes/v3/Taxes/Taxes.config.ts index d6e0f48..5a6b522 100644 --- a/src/Server/Routes/v3/Taxes/Taxes.config.ts +++ b/src/Server/Routes/v3/Taxes/Taxes.config.ts @@ -1,7 +1,10 @@ import { Application, Router } from "express"; import TransactionsModel from "../../../../Database/Models/Transactions.model"; -import { APISuccess } from "../../../../Lib/Response"; import EnsureAdmin from "../../../../Middlewares/EnsureAdmin"; +// @ts-ignore +import pdfdocs from "pdfkit-table"; +import { Company_Currency } from "../../../../Config"; +import { convertCurrency } from "../../../../Lib/Currencies"; export = TaxesRouter; class TaxesRouter @@ -27,9 +30,102 @@ class TaxesRouter }, }); - return APISuccess({ - transactions, - })(res); + const companyCurrency = await Company_Currency(); + + const doc = new pdfdocs({ + size: "A4", + margin: 50, + }); + + const income = { + title: "Income", + headers: [ + "Id", + "Date", + "Invoice ID", + "Customer ID", + "Total", + "Fees", + "Payment method" + ], + rows: transactions.filter(e => e.statement === "income").map((t) => + { + return [ + t.id, + t.date, + t.invoice_uid, + t.customer_uid, + `${t.amount.toFixed(2)} ${t.currency}`, + t.fees, + t.payment_method, + ]; + }), + }; + + doc.table(income); + + const expense = { + title: "Expense", + headers: [ + "Date", + "Invoice ID", + "Customer", + "Total", + "Fees", + ], + rows: transactions.filter(e => e.statement === "expense").map((t) => + { + return [ + t.date, + t.invoice_uid, + t.customer_uid, + t.amount, + t.fees, + ]; + }), + }; + + doc.table(expense); + + const nTotal = { + expense: (await Promise.all(transactions.filter(e => e.statement === "expense").map(async (e) => + { + // Convert to company currency + return (await convertCurrency(e.amount, e.currency, companyCurrency)); + }))).reduce((a, b) => a + b, 0).toFixed(2), + income: (await Promise.all(transactions.filter(e => e.statement === "income").map(async (e) => + { + // Convert to company currency + return (await convertCurrency(e.amount, e.currency, companyCurrency)); + }))).reduce((a, b) => a + b, 0).toFixed(2) + } + + const total = { + title: "Total", + headers: [ + "From", + "To", + "Total income", + "Total expense", + "Total" + ], + rows: [ + [ + start_date, + end_date, + nTotal.income + ` ${companyCurrency}`, + nTotal.expense + ` ${companyCurrency}`, + (parseFloat(nTotal.income) - parseFloat(nTotal.expense)).toFixed(2) + ` ${companyCurrency}` + ], + ], + } + + doc.table(total); + + doc.pipe(res); + + // done + doc.end(); }); } From 7b40b5e834d4e4e676c25a4ae976e1964268867c Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 15 Apr 2022 18:15:18 +0200 Subject: [PATCH 22/27] feat: :sparkles: Formatting formats for emails --- src/Email/Templates/Orders/NewOrderCreated.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Email/Templates/Orders/NewOrderCreated.ts b/src/Email/Templates/Orders/NewOrderCreated.ts index 0c6c023..40c6e55 100644 --- a/src/Email/Templates/Orders/NewOrderCreated.ts +++ b/src/Email/Templates/Orders/NewOrderCreated.ts @@ -15,6 +15,9 @@ export default async (order: IOrder, customer: ICustomer) => await UseStyles(str

Your order has been created.

+

+ A invoice will be generated for you. +

Order number: ${order.id}

From c128e147d65a30a88cf37fba9e2a3009e8483cd8 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 15 Apr 2022 18:15:43 +0200 Subject: [PATCH 23/27] feat: :package: Added new plugin --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ed88349..145da48 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "graphql-compose-mongoose": "^9.7.1", "graphql-resolvers": "^0.4.2", "inquirer": "^8.2.2", + "inquirer-search-checkbox": "^1.0.0", "inquirer-search-list": "^1.2.6", "jimp": "^0.16.1", "jsonwebtoken": "^8.5.1", From e69ea28f7601c19684e4b38fb17125d22d68d06b Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 15 Apr 2022 18:15:58 +0200 Subject: [PATCH 24/27] feat: :sparkles: Plugins required --- src/Admin/AdminHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Admin/AdminHandler.ts b/src/Admin/AdminHandler.ts index d37721c..8f87703 100644 --- a/src/Admin/AdminHandler.ts +++ b/src/Admin/AdminHandler.ts @@ -19,6 +19,7 @@ export default class AdminHandler ) ) inquirer.registerPrompt('search-list', require('inquirer-search-list')); + inquirer.registerPrompt('search-checkbox', require('inquirer-search-checkbox')); this.action(); } From dac1680cde6efefad7de384ac7564607f9621550 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 15 Apr 2022 18:16:10 +0200 Subject: [PATCH 25/27] feat: :sparkles: New orders prompt --- src/Admin/Commands/Orders.prompt.ts | 225 ++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/Admin/Commands/Orders.prompt.ts diff --git a/src/Admin/Commands/Orders.prompt.ts b/src/Admin/Commands/Orders.prompt.ts new file mode 100644 index 0000000..fedc60d --- /dev/null +++ b/src/Admin/Commands/Orders.prompt.ts @@ -0,0 +1,225 @@ +import inquirer from "inquirer"; +import CustomerModel from "../../Database/Models/Customers/Customer.model"; +import OrderModel from "../../Database/Models/Orders.model"; +import ProductModel from "../../Database/Models/Products.model"; +import PromotionCodeModel from "../../Database/Models/PromotionsCode.model"; +import { currencyCodes } from "../../Lib/Currencies"; +import { idOrder } from "../../Lib/Generator"; +import Logger from "../../Lib/Logger"; +import { A_CC_Payments, A_RecurringMethod } from "../../Types/PaymentMethod"; +import dateFormat from "date-and-time"; +import nextRycleDate from "../../Lib/Dates/DateCycle"; +import mainEvent from "../../Events/Main.event"; +import { sendEmail } from "../../Email/Send"; +import NewOrderCreated from "../../Email/Templates/Orders/NewOrderCreated"; +import { Company_Name } from "../../Config"; + +export default +{ + name: 'Orders', + description: 'Get all invoice jobs', + args: [ + { + name: 'action', + type: "list", + message: "Select the cron you want to run", + choices: [ + { + name: 'Get orders', + value: 'get_orders', + }, + { + name: 'Get order', + value: 'get_order', + }, + { + name: 'Create order', + value: 'create_order', + } + ], + } + ], + method: async ({action}: {action: string}) => + { + switch(action) + { + case 'get_orders': + { + const orders = (await OrderModel.find()); + Logger.info(orders); + break; + } + + case 'get_order': + { + const action = [ + { + name: 'orderId', + type: 'input', + message: 'Enter the order id', + }, + ] + const { orderId } = await inquirer.prompt(action); + + Logger.info(`Order:`, await OrderModel.findOne({ + id: orderId, + })); + break; + } + case 'create_order': + { + const action1 = [ + { + name: 'customer_uid', + type: 'search-list', + message: 'Customer', + choices: (await CustomerModel.find()).map(e => + { + return { + name: `${e.personal.first_name} ${e.personal.last_name} (${e.id})`, + value: e.id, + } + }) + }, + { + name: 'payment_method', + type: 'search-list', + message: 'Enter the payment method', + choices: A_CC_Payments + }, + { + name: 'currency', + type: 'search-list', + message: 'Enter the currency', + choices: currencyCodes + }, + { + name: 'products', + type: 'search-checkbox', + message: 'Enter the products', + choices: (await ProductModel.find()).map(e => + { + return { + name: `${e.name} (${e.id})`, + value: e.id, + } + }), + }, + ] + + // eslint-disable-next-line prefer-const + let { currency, customer_uid, payment_method, products } = await inquirer.prompt(action1); + + // @ts-ignore + const action2 = [...products.map(e => + { + return { + name: `quantity_${e}`, + type: 'number', + message: `Enter the quantity for #${e}`, + } + }), + { + name: 'billing_type', + type: 'list', + message: 'Enter the billing type', + choices: [ + { + name: 'One time', + value: 'one_time', + }, + { + name: 'Recurring', + value: 'recurring', + } + ], + }, + { + name: 'billing_cycle', + type: 'list', + message: 'Enter the billing cycle', + choices: A_RecurringMethod + }, + { + name: "fees", + type: 'number', + message: 'Enter the fees', + + }, + { + name: "promotion_code", + type: 'search-list', + message: 'Enter the promotion code', + choices: [...(await PromotionCodeModel.find({ + products_ids: { + $in: products, + } + })).map(e => + { + return { + name: `${e.name} (${e.id})`, + value: e.id, + } + }), { + name: 'None', + value: null, + }] + } + ] + + const action2Result = await inquirer.prompt(action2); + + // @ts-ignore + const newProduct = products.map(e => + { + return { + product_id: e, + quantity: action2Result[`quantity_${e}`], + } + }); + + const b_recurring = action2Result.billing_type === "recurring"; + const newOrder = await (new OrderModel({ + uid: idOrder(), + invoices: [], + currency, + customer_uid, + dates: { + createdAt: new Date(), + last_recycle: b_recurring ? dateFormat.format(new Date(), "YYYY-MM-DD") : undefined, + next_recycle: b_recurring ? dateFormat.format(nextRycleDate(new Date(), action2Result.billing_cycle), "YYYY-MM-DD") : undefined, + }, + order_status: 'pending', + payment_method, + products: newProduct, + billing_type: action2Result.billing_type, + billing_cycle: action2Result.billing_cycle, + fees: action2Result.fees, + promotion_code: action2Result.promotion_code, + }).save()); + + mainEvent.emit("order_created", newOrder); + + const customer = await CustomerModel.findOne({ + id: customer_uid, + }); + + if(!customer) + throw new Error(`Fail to find customer with id: ${customer_uid}`); + + await sendEmail({ + receiver: customer.personal.email, + subject: `New order from ${await Company_Name() !== "" ? await Company_Name() : "CPG"} #${newOrder.id}`, + body: { + body: await NewOrderCreated(newOrder, customer) + } + }); + + Logger.info(newOrder); + + break; + } + + } + } +}; \ No newline at end of file From c453bc166b0c4f53f1e1d0e47942330c3ee687bf Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 15 Apr 2022 18:16:26 +0200 Subject: [PATCH 26/27] style: :art: Formatted orders controller --- src/Server/Routes/v2/Orders/Orders.controller.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Server/Routes/v2/Orders/Orders.controller.ts b/src/Server/Routes/v2/Orders/Orders.controller.ts index a9aab0f..d1a5ae7 100644 --- a/src/Server/Routes/v2/Orders/Orders.controller.ts +++ b/src/Server/Routes/v2/Orders/Orders.controller.ts @@ -1,16 +1,16 @@ -import {Request, Response} from "express"; +import { Request, Response } from "express"; import dateFormat from "date-and-time"; import OrderModel from "../../../../Database/Models/Orders.model"; -import {IOrder} from "@interface/Orders.interface"; +import { IOrder } from "@interface/Orders.interface"; import nextRycleDate from "../../../../Lib/Dates/DateCycle"; -import {idOrder} from "../../../../Lib/Generator"; -import {APISuccess} from "../../../../Lib/Response"; +import { idOrder } from "../../../../Lib/Generator"; +import { APISuccess } from "../../../../Lib/Response"; import BaseModelAPI from "../../../../Models/BaseModelAPI"; -import {createInvoiceFromOrder} from "../../../../Lib/Orders/newInvoice"; -import {SendEmail} from "../../../../Email/Send"; +import { createInvoiceFromOrder } from "../../../../Lib/Orders/newInvoice"; +import { SendEmail } from "../../../../Email/Send"; import CustomerModel from "../../../../Database/Models/Customers/Customer.model"; import NewOrderCreated from "../../../../Email/Templates/Orders/NewOrderCreated"; -import {Company_Name} from "../../../../Config"; +import { Company_Name } from "../../../../Config"; import mainEvent from "../../../../Events/Main.event"; const API = new BaseModelAPI(idOrder, OrderModel); From 54671b021dae425ab82f4e2ff8c3c01ab45dc6e7 Mon Sep 17 00:00:00 2001 From: Tolfx Date: Fri, 15 Apr 2022 18:18:06 +0200 Subject: [PATCH 27/27] fix: :bug: Fixed cron time, now only 12 every day --- src/Cron/Invoices.cron.ts | 2 +- src/Cron/Orders.cron.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cron/Invoices.cron.ts b/src/Cron/Invoices.cron.ts index 7140ccd..430afa9 100644 --- a/src/Cron/Invoices.cron.ts +++ b/src/Cron/Invoices.cron.ts @@ -11,7 +11,7 @@ import { export = function Cron_Invoices() { // Every hour - new CronJob("0 */12 * * *", () => + new CronJob("0 12 * * *", () => { Logger.info(GetText(Default_Language).cron.txt_Invoice_Checking); diff --git a/src/Cron/Orders.cron.ts b/src/Cron/Orders.cron.ts index 1878e96..fc3c03d 100644 --- a/src/Cron/Orders.cron.ts +++ b/src/Cron/Orders.cron.ts @@ -11,7 +11,7 @@ import GetText from "../Translation/GetText"; export = function Cron_Orders() { // Every hour - new CronJob("0 */12 * * *", () => + new CronJob("0 12 * * *", () => { Logger.info(GetText(Default_Language).cron.txt_Orders_Checking); // Logger.info(`Checking orders..`);