You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
6.5 KiB
JavaScript

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}`
}