Skip to content

Commit

Permalink
feat(api): 🚚 added option to generate invoice
Browse files Browse the repository at this point in the history
  • Loading branch information
sahrohit committed Aug 10, 2023
1 parent aa66cd0 commit 4ce7e1c
Show file tree
Hide file tree
Showing 13 changed files with 533 additions and 14 deletions.
7 changes: 5 additions & 2 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ DATABASE_URL=
REDIS_URL=
SESSION_SECRET=
PORT=
SENDGRID_API_KEY=
CLIENT_URL=
KHALTI_SECRET_KEY=
KHALTI_SECRET_KEY=
RESEND_HOST=
RESENT_PORT=
RESEND_AUTH_USER=
RESEND_AUTH_PASS=
Binary file not shown.
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"connect-redis": "^6.1.3",
"cors": "^2.8.5",
"dotenv-safe": "^8.2.0",
"easyinvoice": "^3.0.23",
"express": "^4.18.2",
"express-session": "^1.17.3",
"graphql": "^16.7.1",
Expand Down
9 changes: 8 additions & 1 deletion apps/api/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ export const __prod__ = process.env.NODE_ENV === "production";
export const COOKIE_NAME = "qid";
export const FORGOT_PASSWORD_PREFIX = "forgot-password:";
export const VERIFY_EMAIL_PREFIX = "verify-email:";
export const COMPANY_NAME = "Hamropasal";
export const COMPANY = {
name: "Hamropasal",
logo: `${process.env.CLIENT_URL}/logo.png`,
address: "Tikathali, Mahalaxmi",
zip: "44708",
city: "Kathmandu",
country: "Nepal",
};
5 changes: 4 additions & 1 deletion apps/api/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ declare global {
REDIS_URL: string;
SESSION_SECRET: string;
PORT: string;
SENDGRID_API_KEY: string;
CLIENT_URL: string;
KHALTI_SECRET_KEY: string;
RESEND_HOST: string;
RESENT_PORT: string;
RESEND_AUTH_USER: string;
RESEND_AUTH_PASS: string;
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ import { FavouriteResolver } from "./resolvers/favourite";
import { ReviewResolver } from "./resolvers/review";
import Redis from "ioredis";
import { Product } from "./entities/Product";
import { verifyEmailTemplate } from "./static/verifyEmailTemplate";
import { sendEmail } from "./utils/sendEmail";
import { InvoiceResolver } from "./resolvers/invoice";

const Server = async () => {
AppDataSource.initialize()
Expand Down Expand Up @@ -120,6 +119,7 @@ const Server = async () => {
VariantResolver,
FavouriteResolver,
ReviewResolver,
InvoiceResolver,
],
validate: false,
}),
Expand Down
179 changes: 179 additions & 0 deletions apps/api/src/resolvers/invoice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { COMPANY } from "../constants";
import { OrderDetail } from "../entities/OrderDetail";
import { Arg, Ctx, Query, Resolver, UseMiddleware } from "type-graphql";
import easyinvoice from "easyinvoice";
import fs from "fs";
import { sendEmailWithAttachment } from "../utils/sendEmail";
import { type MyContext } from "../types";
import { User } from "../entities/User";
import { invoiceTemplate } from "../static/invoiceTemplate";
import { isAuth } from "../middlewares/isAuth";

@Resolver()
export class InvoiceResolver {
@Query(() => String, { nullable: true })
@UseMiddleware(isAuth)
async generate(
@Arg("orderId", () => String) orderId: string
): Promise<string> {
return (await easyinvoice.createInvoice(await getSampleData(orderId))).pdf;
}

@Query(() => Boolean)
@UseMiddleware(isAuth)
async emailInvoice(
@Arg("orderId", () => String) orderId: string,
@Ctx() { req }: MyContext
): Promise<boolean> {
const user = await User.findOne({ where: { id: req.session?.userId } });

if (!user) {
return false;
}

await easyinvoice.createInvoice(
await getSampleData(orderId),
async function (result) {
// TODO: Email doesn't attach the file yet.
// fs.writeFile(`${orderId}.pdf`, result.pdf, "base64", function (err) {
// if (err) {
// console.error(err);
// return;
// }
// });
await sendEmailWithAttachment(
user?.email,
`Invoice for ${orderId}`,
invoiceTemplate(orderId),
[
{
path: `${orderId}.pdf`,
},
]
);
// fs.unlink(`${orderId}.pdf`, (err) => {
// if (err) {
// console.error(err);
// return;
// }
// });
}
);
return true;
}
}

export const getSampleData = async (orderId: string) => {
const order = await OrderDetail.findOne({
relations: {
orderitems: {
inventory: {
product: {
images: true,
category: true,
inventories: {
variants: {
variant_value: {
variant: true,
},
},
},
},
variants: {
variant_value: {
variant: true,
},
},
},
},
address: true,
paymentdetails: true,
promo: true,
user: true,
},
where: { id: orderId },
});

return {
images: {
logo: COMPANY.logo,
},
sender: {
company: COMPANY.name,
address: COMPANY.address,
zip: COMPANY.zip,
city: COMPANY.city,
country: COMPANY.country,
},
client: {
company: order?.user?.first_name + " " + order?.user?.last_name,
address: order?.address.address,
zip: order?.address.zip,
city: order?.address.city,
country: order?.address.country,
// "custom1": "custom value 1",
},
information: {
date: order?.created_at.toString(),
number: order?.id,
// "due-date": "31-12-2021",
},
products: order?.orderitems.map((item) => {
return {
quantity: item.quantity.toString(),
description: item.inventory.product.name,
"tax-rate": 0,
price: item.inventory.price,
};
}),
// [
// {
// quantity: 2,
// description: "Product 1",
// "tax-rate": 6,
// price: 33.87,
// },
// {
// quantity: 4.1,
// description: "Product 2",
// "tax-rate": 6,
// price: 12.34,
// },
// {
// quantity: 4.5678,
// description: "Product 3",
// "tax-rate": 21,
// price: 6324.453456,
// },
// ]
"bottom-notice":
"This is a fake invoice. It is not valid for tax purposes.",
settings: {
currency: "NPR", // See documentation 'Locales and Currency' for more info. Leave empty for no currency.
// "locale": "nl-NL", // Defaults to en-US, used for number formatting (see docs)
// "margin-top": 25, // Default to 25
// "margin-right": 25, // Default to 25
// "margin-left": 25, // Default to 25
// "margin-bottom": 25, // Default to 25
// "format": "Letter", // Defaults to A4,
// "height": "1000px", // allowed units: mm, cm, in, px
// "width": "500px", // allowed units: mm, cm, in, px
// "orientation": "landscape", // portrait or landscape, defaults to portrait
},
// Used for translating the headers to your preferred language
// Defaults to English. Below example is translated to Dutch
translate: {
// "invoice": "FACTUUR",
// "number": "Nummer",
// "date": "Datum",
// "due-date": "Verloopdatum",
// "subtotal": "Subtotaal",
// "products": "Producten",
// "quantity": "Aantal",
// "price": "Prijs",
// "product-total": "Totaal",
// "total": "Totaal"
// "vat": "btw"
},
};
};
4 changes: 2 additions & 2 deletions apps/api/src/static/forgetPasswordTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { COMPANY_NAME } from "../constants";
import { COMPANY } from "../constants";

export const forgetPasswordTemplate = (link: string) => `
<!DOCTYPE html>
Expand Down Expand Up @@ -155,7 +155,7 @@ export const forgetPasswordTemplate = (link: string) => `
</tr>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 0px 30px 40px 30px; border-radius: 0px 0px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">Cheers,<br>${COMPANY_NAME} Team</p>
<p style="margin: 0;">Cheers,<br>${COMPANY.name} Team</p>
</td>
</tr>
</table>
Expand Down
Loading

1 comment on commit 4ce7e1c

@vercel
Copy link

@vercel vercel bot commented on 4ce7e1c Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.