import type Log from 'bunyan'; import type { PrismaClient, Want, User, Listing, Offer, } from '@prisma/client'; import assert from 'assert/strict'; import groupBy from 'lodash/groupBy'; import sortBy from 'lodash/sortBy'; import { Context } from 'koa'; import { logAxiosError, wantDetails } from './utils'; import { Pg } from './pg'; export enum Status { Pending = 'Pending', Accepted = 'Accepted', Declined = 'Declined', Confirmed = 'Confirmed', Complete = 'Complete', } export default function createOffers(log: Log, pg: Pg) { async function generate(listingIds: DiscogsListing[], bulkShipping: number, baseShipping: number) { log.info('generateOffers', { listingIds, bulkShipping, baseShipping }); const listings = await pg.listingByIds(listingIds); // log.info({ listings }, 'creating offers for listings') const shipping = 10 + Math.random(); // yolt // const shipping = bulkShipping / listings.length; // how to deal with users who don't want all items in a bundle... const offers = listings.map(l => ({ userId: l.want.user.id, listingId: l.id, wantId: l.want.id, // sellerId: l.seller, status: Status.Pending, item: l.data.price.value, shipping, total: l.data.price.value + shipping, // without mcrates... comparison: l.data.price.value + baseShipping, })) // we must sort the offers by price, // sometimes a seller has multiples of a want // to deal with this we just ignore duplicates on insert // and ensure the cheapest one goes in first const sorted = sortBy(offers, ['item']); const inserts = await pg.offersCreate(sorted); return inserts; } async function accept(ctx: Context) { const { id } = ctx.request.body; ctx.assert(id, 401, 'no id supplied'); // check the user actually has this offer const offer = await pg.userOfferFind(ctx.state.user, id); return pg.offerStatusSet(offer.id, Status.Accepted); } async function statusSet(user: User, id: UUID, status: Status) { log.info({ status, id }, 'setting offer status'); const offer = await pg.offerFindId(id); const update = await pg.offerStatusSet(offer.id, status); if (status === Status.Complete) { log.info({ id, status }, 'completed offer, removing want...'); await pg.wantRm(offer.wantId); } return update; } async function bundles() { const bundles = await pg.bundleCandidates(); interface ListingData extends Listing { data: any, want: { user: User }, }; return bundles.map((b: ListingData[]) => { const total = b.reduce((acc, l) => acc + l.data.price.value, 0); const items = b.map(l => ({ id: l.data.id, description: l.data.release.description, originalPrice: l.data.original_price.value, originalCurrency: l.data.original_price.curr_abbr, price: l.data.price.value, currency: l.data.price.currency, condition: l.data.condition, uri: l.data.uri, user: l.want.user, })); const shipping = b[0]?.data.seller.shipping; const bundle = { username: b[0]?.data.seller.username, seller: b[0]?.data.seller.id, shipsFrom: b[0]?.data.ships_from, total, currency: b[0]?.data.price.currency, shipping: '...', items, mentionsAustralia: shipping.toLowerCase().includes('australia'), mentionsWorldwide: shipping.toLowerCase().includes('world'), } return bundle; }); } async function acpOffers() { const offers = await pg.offersAdmin(); const grouped = groupBy(offers, o => o.listing.data.seller.id); const sellerOffers = sortBy(Object.values(grouped).map(offers => { const shipping = offers[0]?.listing.data.seller.shipping; const bundle = { username: offers[0]?.listing.data.seller.username, seller: offers[0]?.listing.data.seller.id, shipsFrom: offers[0]?.listing.data.ships_from, shipping, offers, mentionsAustralia: shipping.toLowerCase().includes('australia'), mentionsWorldwide: shipping.toLowerCase().includes('world'), } return bundle; }), bundle => -bundle.offers.length); return sellerOffers; } return { generate, accept, bundles, statusSet, acpOffers, }; }