const ExcelJS = require('exceljs'); const easypost = new (require('@easypost/api'))(process.env.EASYPOST_API_KEY); module.exports = async function(users, coupons, items, filename) { const workbook = new ExcelJS.Workbook(); workbook.creator = 'Ashe Erickson' workbook.lastModifiedBy = 'Ashe Erickson' workbook.created = new Date() workbook.modified = new Date() const usersSheet = workbook.addWorksheet('Users', {views:[{state: 'frozen', xSplit: 0, ySplit:1}]}) const ordersSheet = workbook.addWorksheet('Orders', {views:[{state: 'frozen', xSplit: 0, ySplit:1}]}) const couponsSheet = workbook.addWorksheet('Coupons', {views:[{state: 'frozen', xSplit: 0, ySplit:1}]}) const couponCache = {}; // Write coupons couponsSheet.columns = [ {header: 'Coupon Code', width: 20}, {header: 'Expires', width: 14}, {header: 'Flat discount', key: 'flat', width: 12}, {header: '% discount', key: 'percent', width: 10}, {header: '# Socks free', width: 12}, {header: 'Per sock discount', key: 'perSock', width: 18}, {header: 'Free Shipping', width: 15}, {header: '# Allowed Uses', width: 18}, {header: '# Times Used', width: 18}, ] for (const coupon of coupons) { const row = couponsSheet.addRow([ coupon.code, new Date(coupon.expires * 1000), formatMoney(coupon.flatDiscount), coupon.percentDiscount ? coupon.percentDiscount / 100 : '', coupon.socksFree || '', formatMoney(coupon.perSockDiscount), coupon.freeShipping ? 'Yes' : 'No', coupon.numAllowedUses, coupon.uses.length ]) row.eachCell(cell => {cell.alignment = {horizontal: 'left' }}) couponCache[coupon._id.toString()] = { code: coupon.code, row: row.number } } // Write users + orders usersSheet.columns = [ {header: 'Email', width: 40}, {header: 'Email Confirmed', width: 10}, {header: 'Registered', width: 20}, {header: 'Last Login', width: 20}, {header: 'Purchases', width: 20}, {header: 'Admin', width: 20}, ] ordersSheet.columns = [ {header: 'User', width: 40}, {header: 'Purchase Date', width: 14}, {header: 'Shipment Date', width: 14}, {header: 'Items', width: 60}, {header: 'Item Subtotal', key: 'itemCost', width: 20}, {header: 'Shipping Estimate', key: 'shippingEst', width: 20}, {header: 'Shipping Actual', key: 'shippingCost', width: 20}, {header: 'Total Paid', key: 'totalCost', width: 20}, {header: 'Coupon', key: 'coupon', width: 20}, {header: 'Tracking Code', width: 20}, {header: 'Address', width: 20}, ] const usersWithPurchases = users.filter(user => user.purchases?.length); usersWithPurchases.sort((a,b) => (a.email || 'zzzz').localeCompare(b.email || 'zzzz')) let userNum = 0; for(const user of usersWithPurchases) { if (user.purchases?.length < 1) continue; userNum++ const creationDate = new Date(parseInt(user._id.toString().slice(0,8), 16)*1000) const userID = user.email || user._id.toString(); const firstOrderRow = ordersSheet.rowCount + 1; const userRow = usersSheet.rowCount + 1; console.log(` (${userNum}/${usersWithPurchases.length}) Retrieving shipment + address info for orders by user ${userID}`) for(const purchase of user.purchases) { const shipmentPromise = easypost.Shipment.retrieve(purchase.shipment); const addressPromise = easypost.Address.retrieve(purchase.address); try { purchase.shipment = await shipmentPromise; purchase.address = await addressPromise; } catch { console.warn(` Unable to retrieve info for order ${purchase._id}`) } if (purchase.shipment?.tracker?.id) { easypost.Tracker.retrieve(purchase.shipment.tracker.id).then(tracker => { purchase.shipment.tracker = tracker }).catch(() => {}) } const coupon = couponCache[purchase.coupon] const trackingCode = purchase.shipment?.tracking_code || purchase.trackingCode const trackingLink = purchase.shipment?.tracker?.public_url || `https://tools.usps.com/go/TrackConfirmAction_input?qtc_tLabels1=${trackingCode}` const row = ordersSheet.addRow([ user.email ? {formula: `=HYPERLINK("#'Users'!A${userRow}", "${userID}")`} : userID, purchase.purchaseTime ? new Date(purchase.purchaseTime) : '', purchase.shippedOn ? new Date(purchase.shippedOn) : '', purchase.items.map(id => items.find(item => item._id.toString() === id.toString()).urlslug).join(', '), formatMoney(purchase.sockPrice), formatMoney(purchase.shippingEstimate), formatMoney(purchase.shipment?.selected_rate?.rate), formatMoney(purchase.totalPrice), coupon ? {formula: `=HYPERLINK("#'Coupons'!A${coupon.row}","${coupon.code}")`} : '', trackingCode ? {formula: `=HYPERLINK("${trackingLink}", "${trackingCode}")`} : "", formatAddress(purchase.address) ]) row.eachCell(cell => {cell.alignment = {horizontal: 'left' }}) } if (!user.email) continue; const row = usersSheet.addRow([ userID, user.emailConfirmed ? 'Yes' : 'No', creationDate, user.lastLogin ? new Date(user.lastLogin) : '', {formula: `=HYPERLINK("#'Orders'!A${firstOrderRow}", "${user.purchases.length} purchase${user.purchases.length > 1 ? "s" : ""}")`}, user.isAdmin ? 'Yes' : 'No' ]) row.eachCell(cell => {cell.alignment = {horizontal: 'left' }}) } ordersSheet.getColumn('itemCost').eachCell(cell => {cell.numFmt = '$0.00'}) ordersSheet.getColumn('shippingEst').eachCell(cell => {cell.numFmt = '$0.00'}) ordersSheet.getColumn('shippingCost').eachCell(cell => {cell.numFmt = '$0.00'}) ordersSheet.getColumn('totalCost').eachCell(cell => {cell.numFmt = '$0.00'}) couponsSheet.getColumn('flat').eachCell(cell => {cell.numFmt = '$0.00'}) couponsSheet.getColumn('percent').eachCell(cell => {cell.numFmt = '0%'}) couponsSheet.getColumn('perSock').eachCell(cell => {cell.numFmt = '$0.00'}) await workbook.xlsx.writeFile(filename); } function formatMoney(value) { if(value === undefined) return '' if(value === '') return '' if(value === 0) return '' if(typeof value === 'string') return parseFloat(value); return value } function formatAddress(address) { if(typeof address !== 'object') return address return `${address.name || ""} | ${address.company ? address.company + "| " : ""}${address.street1} | ${address.street2 ? address.street2 + "| " : ""}${address.city} | ${address.state} | ${address.country} | ${address.zip}` }