diff --git a/packages/apps/frenet/src/frenet-events.ts b/packages/apps/frenet/src/frenet-events.ts index ce35238c0..e65323316 100644 --- a/packages/apps/frenet/src/frenet-events.ts +++ b/packages/apps/frenet/src/frenet-events.ts @@ -9,7 +9,9 @@ import handleApiEvent from './functions-lib/events-to-frenet'; const { httpsFunctionOptions: { region } } = config.get(); export const frenet = { - cronTrackingCodes: functions.region(region).pubsub + cronTrackingCodes: functions.region(region) + .runWith({ timeoutSeconds: 540 }) + .pubsub .schedule(process.env.CRONTAB_FRENET_TRACKING_CODES || '19 * * * *') .onRun(() => { return handleTrackingCodes(); diff --git a/packages/apps/frenet/src/functions-lib/database.ts b/packages/apps/frenet/src/functions-lib/database.ts index 54cf46ae3..dc81fb1f8 100644 --- a/packages/apps/frenet/src/functions-lib/database.ts +++ b/packages/apps/frenet/src/functions-lib/database.ts @@ -19,7 +19,10 @@ const save = ( serviceCode, createdAt: new Date(), }).then(() => resolve(true)) - .catch(logger.error); + .catch((err) => { + logger.error(err); + resolve(false); + }); } }); @@ -47,98 +50,73 @@ const get = async ( }); }); -const clear = async () => new Promise((resolve) => { +const maxAgeDays = 60; + +const getDeadline = () => { const deadline = new Date(); - deadline.setMonth(deadline.getMonth() - 2); + deadline.setDate(deadline.getDate() - maxAgeDays); + return deadline; +}; - getFirestore().collection(firestoreColl) - .where('createdAt', '<=', deadline) - .get() - .then((docsRef) => { - docsRef.docs.forEach((doc) => { - doc.ref.delete(); - }); - resolve(true); - }); -}); +const clear = async () => { + const docsRef = await getFirestore().collection(firestoreColl) + .where('createdAt', '<=', getDeadline()) + .get(); + const bulkWriter = getFirestore().bulkWriter(); + docsRef.docs.forEach((doc) => { + bulkWriter.delete(doc.ref); + }); + await bulkWriter.close(); +}; const remove = async ( trackingCode: string, serviceCode: string, -) => new Promise((resolve) => { - getFirestore().doc(`${firestoreColl}/${trackingCode}`) - .get() - .then(async (documentSnapshot) => { - if (documentSnapshot && documentSnapshot.exists) { - const data = documentSnapshot.data(); - if (data && data.serviceCode === serviceCode) { - await documentSnapshot.ref.delete(); - resolve(true); - } - } - resolve(false); - }); -}); +) => { + const documentSnapshot = await getFirestore().doc(`${firestoreColl}/${trackingCode}`).get(); + if (documentSnapshot.exists && documentSnapshot.data()?.serviceCode === serviceCode) { + await documentSnapshot.ref.delete(); + return true; + } + return false; +}; const update = async ( - trackingStatus: string, + trackingStatus: string | number, trackingCode: string, -) => new Promise((resolve) => { - const documentRef = getFirestore().doc(`${firestoreColl}/${trackingCode}`); - if (documentRef) { - documentRef.set({ - trackingStatus, - updateAt: new Date().toISOString(), - }, { merge: true }) - .then(() => resolve(true)); +) => { + await getFirestore().doc(`${firestoreColl}/${trackingCode}`).set({ + trackingStatus: String(trackingStatus), + updateAt: new Date().toISOString(), + }, { merge: true }); + return true; +}; + +const listByStatus = async ( + operator: 'in' | 'not-in', +) => { + const docsRef = await getFirestore().collection(firestoreColl) + .where('trackingStatus', operator, ['9', 9]) + .get(); + if (!docsRef.size) { + const err = new Error('Tracking code not found for any store :)'); + err.name = 'NoTrackingCodesForUpdate'; + throw err; } -}); + const list: { [x: string]: any }[] = []; + docsRef.forEach((documentSnapshot) => { + const data = documentSnapshot.data(); + list.push({ ...data, createdAt: data.createdAt?.toDate?.() }); + }); + return list; +}; -const getAll = () => new Promise((resolve, reject) => { - getFirestore().collection(firestoreColl) - .where('trackingStatus', '!=', '9') - .get() - .then((docsRef) => { - if (docsRef.size > 0) { - const list: { [x: string]: any }[] = []; - docsRef.forEach((documentSnapshot) => { - if (documentSnapshot && documentSnapshot.exists) { - const data = documentSnapshot.data(); - list.push({ ...data, createAt: data.toDate() }); - } - }); - resolve(list); - } else { - const err = new Error('Tracking code not found for any store :)'); - err.name = 'NoTrackingCodesForUpdate'; - reject(err); - } - }); -}); +const getAll = () => listByStatus('not-in'); -const getAllDelivered = () => new Promise((resolve, reject) => { - getFirestore().collection(firestoreColl) - .where('trackingStatus', '==', '9') - .get() - .then((docsRef) => { - if (docsRef.size > 0) { - const list: { [x: string]: any }[] = []; - docsRef.forEach((documentSnapshot) => { - if (documentSnapshot && documentSnapshot.exists) { - const data = documentSnapshot.data(); - list.push({ ...data, createAt: data.toDate() }); - } - }); - resolve(list); - } else { - const err = new Error('Tracking code not found for any store :)'); - err.name = 'NoTrackingCodesForUpdate'; - reject(err); - } - }); -}); +const getAllDelivered = () => listByStatus('in'); const db = { + getDeadline, save, get, clear, @@ -155,6 +133,6 @@ export type TrackingDoc = { trackingStatus: string, trackingCode: string, serviceCode: string, - createdAt: string, + createdAt: Date, updateAt?: string } diff --git a/packages/apps/frenet/src/functions-lib/fetch-tracking-code.ts b/packages/apps/frenet/src/functions-lib/fetch-tracking-code.ts index ed1655327..389474eab 100644 --- a/packages/apps/frenet/src/functions-lib/fetch-tracking-code.ts +++ b/packages/apps/frenet/src/functions-lib/fetch-tracking-code.ts @@ -1,22 +1,23 @@ import axios from 'axios'; -export default ( +export default async ( trackingCode: string, serviceCode: string, token: string, -) => new Promise((resolve) => { - axios({ +) => { + const { data } = await axios({ method: 'post', url: 'http://api.frenet.com.br/tracking/trackinginfo', headers: { 'Content-Type': 'application/json', - Accept: 'application/json', + 'Accept': 'application/json', token, }, data: { ShippingServiceCode: serviceCode, TrackingNumber: trackingCode, }, - }) - .then(({ data }) => resolve(data)); -}); + timeout: 10000, + }); + return data; +}; diff --git a/packages/apps/frenet/src/functions-lib/remove-delivered.ts b/packages/apps/frenet/src/functions-lib/remove-delivered.ts index bb8cb28b8..8765f7299 100644 --- a/packages/apps/frenet/src/functions-lib/remove-delivered.ts +++ b/packages/apps/frenet/src/functions-lib/remove-delivered.ts @@ -8,18 +8,22 @@ const removeDeliveredToFirestore = async () => { try { const codes: TrackingDoc[] = await db.getAllDelivered() as TrackingDoc[]; if (codes.length > 0) { - codes.forEach(async (code) => { + await Promise.all(codes.map(async (code) => { try { await db.remove(code.trackingCode, code.serviceCode); logger.info('> (App Frenet) Code removed, delivered status', code); } catch (err) { logger.error(err); } - }); + })); + } + resolve(null); + } catch (err: any) { + if (err.name === 'NoTrackingCodesForUpdate') { resolve(null); + } else { + reject(err); } - } catch (err) { - reject(err); } }); diff --git a/packages/apps/frenet/src/functions-lib/tracking-codes.ts b/packages/apps/frenet/src/functions-lib/tracking-codes.ts index b50d34037..3ee37b4ca 100644 --- a/packages/apps/frenet/src/functions-lib/tracking-codes.ts +++ b/packages/apps/frenet/src/functions-lib/tracking-codes.ts @@ -21,7 +21,7 @@ const getConfig = async () => { }; const diffInDays = ( - createdAt: string, + createdAt: Date, ) => { const today = new Date(); const created = new Date(createdAt); @@ -47,13 +47,21 @@ const handleTrackingCodes = async () => { resolve(null); } + if (codes) { + // Don't notify customers of old orders + const deadline = db.getDeadline().getTime(); + const total = codes.length; + codes = codes.filter(({ createdAt }) => createdAt && createdAt.getTime() > deadline); + if (codes.length < total) { + logger.info(`> (App Frenet) Skipping ${total - codes.length} tracking codes older than deadline`); + } + } + if (!appConfig || !appConfig.frenet_access_token || !codes) { // no config for store bye resolve(null); } else { - // Wait for every code before finishing, the function may be stopped - // right after resolving - await Promise.allSettled(codes.map(async (code) => { + const handleCode = async (code: TrackingDoc) => { const serviceCode = String(code.serviceCode).startsWith('FR') ? String(code.serviceCode.replace(/^FR/, '')).trim() : code.serviceCode; @@ -119,11 +127,14 @@ const handleTrackingCodes = async () => { if (trackingEvent && parseInt(code.trackingStatus, 10) !== trackingEvent.status) { try { + // Don't email customers about events from long ago + const isOldEvent = diffInDays(trackingEvent.date) > 7; await updateOrderfulfilllment( code.orderId, code.trackingCode, trackingEvent.status, trackingEvent.date, + isOldEvent, ); await db.update(trackingEvent.status, code.trackingCode); @@ -146,12 +157,21 @@ const handleTrackingCodes = async () => { } } } - })); + }; + // Limit concurrent requests to Frenet and Store API + for (let i = 0; i < codes.length; i += 10) { + // eslint-disable-next-line no-await-in-loop + await Promise.allSettled(codes.slice(i, i + 10).map(handleCode)); + } resolve(null); } }); if (appConfig && appConfig.frenet_access_token) { + await db.clear().catch((err) => { + logger.error('> (App Frenet) Error removing old tracking codes => ', err); + }); + await checkTrackingCodes.catch((err) => { logger.error('> (App Frenet) Error => ', err); }); @@ -159,12 +179,6 @@ const handleTrackingCodes = async () => { await removeDeliveredToFirestore().catch((err) => { logger.error('> (App Frenet) Error removing Delivered to Firestore => ', err); }); - - if (new Date().getHours() === 23) { - await db.clear().catch((err) => { - logger.error('> (App Frenet) Error removing old tracking codes => ', err); - }); - } } }; diff --git a/packages/apps/frenet/src/functions-lib/update-fulfillments.ts b/packages/apps/frenet/src/functions-lib/update-fulfillments.ts index 11a5316d0..b2974280a 100644 --- a/packages/apps/frenet/src/functions-lib/update-fulfillments.ts +++ b/packages/apps/frenet/src/functions-lib/update-fulfillments.ts @@ -22,6 +22,7 @@ const updateFulfillments = async ( trackingCode: string, eventType: number, eventDate: Date, + skipNotification?: boolean, ) => { let notes; const status = parseStatus(eventType); @@ -33,6 +34,9 @@ const updateFulfillments = async ( 'app-frenet', ], } as any; // TODO: incompatible type=> amount; + if (skipNotification) { + bodyFulfillments.customer_notified = true; + } if (eventType === 2) { notes = 'Entrega está atrasada.';