From 04450ec83ca9859f012eabfa1efbb17745697a78 Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Wed, 28 Feb 2024 13:16:53 +0100 Subject: [PATCH 1/6] refactor(customer-order.function.ts): remove unused imports and endpoints feat(customer-order.function.ts): add new endpoint for customer order range feat(customer-order.function.ts): update endpoint handler to use new controller feat(customer-order.function.ts): add support for fetching order range feat(customer-order.function.ts): update endpoint name to customerOrderRange feat(customer-order.function.ts): update handler to use CustomerOrderControllerRange feat(customer/services/order/_types.ts): remove unused types and lookup properties feat(customer/services/order/get-lineitem.ts): remove unused file and specs chore(get-shipping.spec.ts): remove unused test file chore(get-shipping.ts): remove unused file chore(get.spec.ts): fix typo in import statement chore(get.ts): refactor and optimize code for getting order details chore(order): remove unused CustomerOrderServiceList file feat(order): add CustomerOrderServiceRange for fetching orders within a range feat(order): add CustomerOrderServicePaginate for paginating order results feat(order): add CustomerOrderServiceRange function to retrieve orders within a specified date range for a customer --- src/functions/customer-order.function.ts | 30 +- .../customer/services/order/_types.ts | 138 --------- .../services/order/get-lineitem.spec.ts | 66 ----- .../customer/services/order/get-lineitem.ts | 181 ------------ .../services/order/get-shipping.spec.ts | 34 --- .../customer/services/order/get-shipping.ts | 169 ----------- .../customer/services/order/get.spec.ts | 2 +- src/functions/customer/services/order/get.ts | 138 ++++++++- .../customer/services/order/list.spec.ts | 25 -- src/functions/customer/services/order/list.ts | 157 ---------- .../customer/services/order/paginate.ts | 7 +- .../customer/services/order/range.spec.ts | 74 +++++ .../customer/services/order/range.ts | 272 ++++++++++++++++++ .../customer/services/order/shipping.spec.ts | 42 --- .../customer/services/order/shipping.ts | 197 ------------- 15 files changed, 483 insertions(+), 1049 deletions(-) delete mode 100644 src/functions/customer/services/order/_types.ts delete mode 100644 src/functions/customer/services/order/get-lineitem.spec.ts delete mode 100644 src/functions/customer/services/order/get-lineitem.ts delete mode 100644 src/functions/customer/services/order/get-shipping.spec.ts delete mode 100644 src/functions/customer/services/order/get-shipping.ts delete mode 100644 src/functions/customer/services/order/list.spec.ts delete mode 100644 src/functions/customer/services/order/list.ts create mode 100644 src/functions/customer/services/order/range.spec.ts create mode 100644 src/functions/customer/services/order/range.ts delete mode 100644 src/functions/customer/services/order/shipping.spec.ts delete mode 100644 src/functions/customer/services/order/shipping.ts diff --git a/src/functions/customer-order.function.ts b/src/functions/customer-order.function.ts index c885d2c5..41373241 100644 --- a/src/functions/customer-order.function.ts +++ b/src/functions/customer-order.function.ts @@ -3,17 +3,7 @@ import "module-alias/register"; import { app } from "@azure/functions"; import { CustomerOrderControllerGet } from "./customer/controllers/order/get"; -import { CustomerOrderControllerGetLineItem } from "./customer/controllers/order/get-lineitem"; -import { CustomerOrderControllerGetShipping } from "./customer/controllers/order/get-shipping"; -import { CustomerOrderControllerList } from "./customer/controllers/order/list"; -import { CustomerOrderControllerShipping } from "./customer/controllers/order/shipping"; - -app.http("customerOrderGetLineItem", { - methods: ["GET"], - authLevel: "anonymous", - route: "customer/{customerId?}/lineItem/{lineItemId?}", - handler: CustomerOrderControllerGetLineItem, -}); +import { CustomerOrderControllerRange } from "./customer/controllers/order/range"; app.http("customerOrderGet", { methods: ["GET"], @@ -22,23 +12,9 @@ app.http("customerOrderGet", { handler: CustomerOrderControllerGet, }); -app.http("customerOrderList", { +app.http("customerOrderRange", { methods: ["GET"], authLevel: "anonymous", route: "customer/{customerId?}/orders-range", - handler: CustomerOrderControllerList, -}); - -app.http("customerOrderShipping", { - methods: ["GET"], - authLevel: "anonymous", - route: "customer/{customerId?}/shipping-range", - handler: CustomerOrderControllerShipping, -}); - -app.http("customerOrderGetShipping", { - methods: ["GET"], - authLevel: "anonymous", - route: "customer/{customerId?}/get-shipping/{id?}", - handler: CustomerOrderControllerGetShipping, + handler: CustomerOrderControllerRange, }); diff --git a/src/functions/customer/services/order/_types.ts b/src/functions/customer/services/order/_types.ts deleted file mode 100644 index 9740993f..00000000 --- a/src/functions/customer/services/order/_types.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Location } from "~/functions/location"; -import { - Order, - OrderFulfillment, - OrderLineItem, - OrderRefund, - OrderRefundLineItem, -} from "~/functions/order/order.types"; -import { Shipping } from "~/functions/shipping/shipping.types"; -import { User } from "~/functions/user"; - -export type OrderLineItemsAggreate = OrderLineItem & { - user: Pick< - User, - "customerId" | "username" | "fullname" | "images" | "shortDescription" - >; - location: Pick< - Location, - "name" | "fullAddress" | "locationType" | "originType" - >; - shipping?: Pick; -}; - -export type OrderAggregate = Omit< - Order, - "line_items" | "refunds" | "fulfillments" -> & { - line_items: OrderLineItemsAggreate; - fulfillments: Array>; - refunds: Array< - Omit & { - refund_line_items: Array>; - } - >; -}; - -export const OrderLookupProperties = [ - { - $lookup: { - from: "User", - let: { customerId: "$line_items.properties.customerId" }, - pipeline: [ - { - $match: { - $expr: { - $eq: ["$customerId", "$$customerId"], - }, - }, - }, - { - $project: { - customerId: 1, - username: 1, - createdAt: 1, - fullname: 1, - shortDescription: 1, - "images.profile": "$images.profile", - }, - }, - ], - as: "line_items.user", - }, - }, - { - $unwind: { - path: "$line_items.user", - preserveNullAndEmptyArrays: true, // Set to false if you always expect a match - }, - }, - { - $lookup: { - from: "Location", - let: { locationId: "$line_items.properties.locationId" }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { - $eq: ["$_id", { $toObjectId: "$$locationId" }], - }, - ], - }, - }, - }, - { - $project: { - name: 1, - fullAddress: 1, - originType: 1, - locationType: 1, - }, - }, - ], - as: "line_items.location", - }, - }, - { - $unwind: { - path: "$line_items.location", - preserveNullAndEmptyArrays: true, // Set to false if you always expect a match - }, - }, - { - $lookup: { - from: "Shipping", - let: { shippingId: "$line_items.properties.shippingId" }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { - $eq: ["$_id", { $toObjectId: "$$shippingId" }], - }, - ], - }, - }, - }, - { - $project: { - destination: 1, - duration: 1, - distance: 1, - cost: 1, - }, - }, - ], - as: "line_items.shipping", - }, - }, - { - $unwind: { - path: "$line_items.shipping", - preserveNullAndEmptyArrays: true, - }, - }, -]; diff --git a/src/functions/customer/services/order/get-lineitem.spec.ts b/src/functions/customer/services/order/get-lineitem.spec.ts deleted file mode 100644 index e7e48b11..00000000 --- a/src/functions/customer/services/order/get-lineitem.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { LocationTypes } from "~/functions/location"; -import { OrderModel } from "~/functions/order/order.models"; -import { Order } from "~/functions/order/order.types"; -import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-ordre-with-fullfilment-and-refunds"; -import { createUser } from "~/library/jest/helpers"; -import { createLocation } from "~/library/jest/helpers/location"; -import { createSchedule } from "~/library/jest/helpers/schedule"; -import { createShipping } from "~/library/jest/helpers/shipping"; -import { CustomerOrderServiceGetLineItem } from "./get-lineitem"; -require("~/library/jest/mongoose/mongodb.jest"); - -describe("CustomerOrderServiceGetLineItem", () => { - it("should return order by line-item for customer", async () => { - const customerId = 7106990342471; - const user = await createUser({ customerId }); - const location = await createLocation({ customerId }); - const shipping = await createShipping({}); - - const schedule = await createSchedule( - { customerId }, - { - days: ["monday", "tuesday"], - totalProducts: 2, - locations: [ - { location: location._id, locationType: LocationTypes.ORIGIN }, - ], - } - ); - - const dumbData = Order.parse(orderWithfulfillmentAndRefunds); - dumbData.line_items[0].properties!.customerId = customerId; - dumbData.line_items[0].properties!.locationId = location._id.toString(); - dumbData.line_items[0].properties!.shippingId = shipping._id.toString(); - dumbData.line_items[0].product_id = schedule.products[0].productId; - dumbData.line_items[0].variant_id = schedule.products[0].variantId; - - const response = await OrderModel.create(dumbData); - - const lineItemId = response.line_items[0].id; - - const ownerCustomerId = response.customer.id; - const beautyCustomerId = response.line_items[0].properties?.customerId || 0; - - let order = await CustomerOrderServiceGetLineItem({ - customerId: beautyCustomerId, - lineItemId, - }); - - expect(order.line_items.selectedOptions).toBeDefined(); - expect(order.line_items.location).toBeDefined(); - expect(order.line_items.shipping).toBeDefined(); - expect(order.line_items.user.customerId).toBe(user.customerId); - expect(order.line_items.id).toEqual(lineItemId); - expect(order.fulfillments.length).toBe(1); - expect(order.refunds.length).toBe(1); - - order = await CustomerOrderServiceGetLineItem({ - customerId: ownerCustomerId, - lineItemId, - }); - - expect(order.line_items.id).toEqual(lineItemId); - expect(order.fulfillments.length).toBe(1); - expect(order.refunds.length).toBe(1); - }); -}); diff --git a/src/functions/customer/services/order/get-lineitem.ts b/src/functions/customer/services/order/get-lineitem.ts deleted file mode 100644 index b38027ef..00000000 --- a/src/functions/customer/services/order/get-lineitem.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { OrderModel } from "~/functions/order/order.models"; -import { ScheduleProduct } from "~/functions/schedule"; -import { NotFoundError } from "~/library/handler"; -import { - OrderAggregate, - OrderLineItemsAggreate, - OrderLookupProperties, -} from "./_types"; - -export type CustomerOrderServiceGetLineItemAggregate = Omit< - OrderAggregate, - "line_items" -> & { - line_items: OrderLineItemsAggreate & Pick; -}; - -export type CustomerOrderServiceGetLineItemProps = { - customerId: number; - lineItemId: number; -}; - -export const CustomerOrderServiceGetLineItem = async ({ - customerId, - lineItemId, -}: CustomerOrderServiceGetLineItemProps) => { - const orders = - await OrderModel.aggregate([ - { - $match: { - $and: [ - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - line_items: { - $elemMatch: { id: lineItemId }, - }, - }, - ], - }, - }, - { $unwind: "$line_items" }, - { - $match: { - $and: [ - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - "line_items.id": lineItemId, - }, - ], - }, - }, - ...OrderLookupProperties, - - { - $lookup: { - from: "Schedule", - let: { - productId: "$line_items.product_id", - variantId: "$line_items.variant_id", - }, - pipeline: [ - { - $unwind: "$products", - }, - { - $match: { - $expr: { - $and: [ - { $eq: ["$products.productId", "$$productId"] }, - { $eq: ["$products.variantId", "$$variantId"] }, - ], - }, - }, - }, - { - $project: { - name: "$products.selectedOptions.name", - value: "$products.selectedOptions.value", - _id: 0, - }, - }, - ], - as: "line_items.selectedOptions", - }, - }, - { - $unwind: { - path: "$line_items.selectedOptions", - preserveNullAndEmptyArrays: true, - }, - }, - { - $addFields: { - refunds: { - $filter: { - input: "$refunds", - as: "refund", - cond: { - $anyElementTrue: { - $map: { - input: "$$refund.refund_line_items", - as: "refund_line_item", - in: { - $eq: [ - "$$refund_line_item.line_item_id", - "$line_items.id", - ], - }, - }, - }, - }, - }, - }, - fulfillments: { - $filter: { - input: "$fulfillments", - as: "fulfillment", - cond: { - $anyElementTrue: { - $map: { - input: "$$fulfillment.line_items", - as: "fulfillment_line_item", - in: { - $eq: ["$$fulfillment_line_item.id", "$line_items.id"], - }, - }, - }, - }, - }, - }, - }, - }, - { - $project: { - id: 1, - line_items: 1, - customer: 1, - order_number: 1, - fulfillment_status: 1, - financial_status: 1, - created_at: 1, - updated_at: 1, - cancel_reason: 1, - cancelled_at: 1, - note: 1, - note_attributes: 1, - fulfillments: 1, - refunds: 1, - }, - }, - ]); - - if (orders.length === 0) { - throw new NotFoundError([ - { - code: "custom", - message: "ORDER_NOT_FOUND", - path: ["lineItemId"], - }, - ]); - } - - return orders[0]; -}; diff --git a/src/functions/customer/services/order/get-shipping.spec.ts b/src/functions/customer/services/order/get-shipping.spec.ts deleted file mode 100644 index 040794b9..00000000 --- a/src/functions/customer/services/order/get-shipping.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { OrderModel } from "~/functions/order/order.models"; -import { Order } from "~/functions/order/order.types"; -import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-ordre-with-fullfilment-and-refunds"; -import { createUser } from "~/library/jest/helpers"; -import { createLocation } from "~/library/jest/helpers/location"; -import { createShipping } from "~/library/jest/helpers/shipping"; -import { CustomerOrderServiceGetShipping } from "./get-shipping"; -require("~/library/jest/mongoose/mongodb.jest"); - -describe("CustomerOrderServiceGetShipping", () => { - it("should return one shipping order for customer", async () => { - const customerId = 7106990342471; - const user = await createUser({ customerId: 7106990342471 }); - const location = await createLocation({ customerId: user.customerId }); - const shipping = await createShipping({ location: location.id }); - - const dumbData = Order.parse(orderWithfulfillmentAndRefunds); - dumbData.line_items = dumbData.line_items.map((lineItems) => { - lineItems.properties!.customerId = customerId; - lineItems.properties!.locationId = location._id.toString(); - lineItems.properties!.shippingId = shipping._id.toString(); - return lineItems; - }); - - await OrderModel.create(dumbData); - - const order = await CustomerOrderServiceGetShipping({ - customerId: customerId, - id: orderWithfulfillmentAndRefunds.id, - }); - - expect(order.shipping._id).toEqual(shipping._id); - }); -}); diff --git a/src/functions/customer/services/order/get-shipping.ts b/src/functions/customer/services/order/get-shipping.ts deleted file mode 100644 index 9665d873..00000000 --- a/src/functions/customer/services/order/get-shipping.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { subMinutes } from "date-fns"; -import { OrderModel } from "~/functions/order/order.models"; -import { NotFoundError } from "~/library/handler"; -import { CustomerOrderServiceShippingAggregate } from "./shipping"; - -export type CustomerOrderServiceGetShippingProps = { - id: number; - customerId: number; -}; - -export const CustomerOrderServiceGetShipping = async ({ - customerId, - id, -}: CustomerOrderServiceGetShippingProps) => { - let orders = - await OrderModel.aggregate([ - { - $match: { - $and: [ - { - id, - }, - { "line_items.properties.shippingId": { $exists: true } }, - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - ], - }, - }, - { $unwind: "$line_items" }, - { - $match: { - $and: [ - { - id, - }, - { "line_items.properties.shippingId": { $exists: true } }, - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - ], - }, - }, - { - $addFields: { - start: "$line_items.properties.from", - end: "$line_items.properties.to", - title: "$line_items.title", - refunds: { - $filter: { - input: "$refunds", - as: "refund", - cond: { - $anyElementTrue: { - $map: { - input: "$$refund.refund_line_items", - as: "refund_line_item", - in: { - $eq: [ - "$$refund_line_item.line_item_id", - "$line_items.id", - ], - }, - }, - }, - }, - }, - }, - }, - }, - { - $match: { - refunds: { $size: 0 }, - }, - }, - { - $lookup: { - from: "Shipping", - let: { shippingId: "$line_items.properties.shippingId" }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { - $eq: ["$_id", { $toObjectId: "$$shippingId" }], - }, - ], - }, - }, - }, - { - $project: { - destination: 1, - duration: 1, - distance: 1, - cost: 1, - }, - }, - ], - as: "shipping", - }, - }, - { - $unwind: { - path: "$shipping", - preserveNullAndEmptyArrays: true, - }, - }, - { - $sort: { start: 1 }, - }, - { - $group: { - _id: "$shipping._id", - shipping: { $first: "$shipping" }, - id: { $first: "$id" }, - start: { $first: "$start" }, - end: { $first: "$end" }, - title: { $first: "$title" }, - customer: { $first: "$customer" }, - order_number: { $first: "$order_number" }, - }, - }, - { - $project: { - id: 1, - start: 1, - end: 1, - title: 1, - customer: 1, - order_number: 1, - shipping: 1, - }, - }, - ]); - - orders = orders.map((order) => { - order.end = order.start; - order.start = subMinutes(order.start, order.shipping.duration.value); - return order; - }); - - if (orders.length === 0) { - throw new NotFoundError([ - { - code: "custom", - message: "SHIPPING_NOT_FOUND", - path: ["shippingId"], - }, - ]); - } - - return orders[0]; -}; diff --git a/src/functions/customer/services/order/get.spec.ts b/src/functions/customer/services/order/get.spec.ts index 63b271f7..7cb319b5 100644 --- a/src/functions/customer/services/order/get.spec.ts +++ b/src/functions/customer/services/order/get.spec.ts @@ -1,6 +1,6 @@ import { OrderModel } from "~/functions/order/order.models"; import { Order } from "~/functions/order/order.types"; -import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-ordre-with-fullfilment-and-refunds"; +import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-order-with-fullfilment-and-refunds"; import { createUser } from "~/library/jest/helpers"; import { createLocation } from "~/library/jest/helpers/location"; import { CustomerOrderServiceGet } from "./get"; diff --git a/src/functions/customer/services/order/get.ts b/src/functions/customer/services/order/get.ts index 9700edb5..873db7a7 100644 --- a/src/functions/customer/services/order/get.ts +++ b/src/functions/customer/services/order/get.ts @@ -1,16 +1,39 @@ +import { Location } from "~/functions/location"; import { OrderModel } from "~/functions/order/order.models"; -import { NotFoundError } from "~/library/handler"; import { - OrderAggregate, - OrderLineItemsAggreate, - OrderLookupProperties, -} from "./_types"; + Order, + OrderFulfillment, + OrderLineItem, + OrderRefund, + OrderRefundLineItem, +} from "~/functions/order/order.types"; +import { Shipping } from "~/functions/shipping/shipping.types"; +import { User } from "~/functions/user"; +import { NotFoundError } from "~/library/handler"; + +export type OrderLineItemsAggreate = OrderLineItem & { + user: Pick< + User, + "customerId" | "username" | "fullname" | "images" | "shortDescription" + >; + location: Pick< + Location, + "name" | "fullAddress" | "locationType" | "originType" + >; + shipping?: Pick; +}; export type CustomerOrderServiceGetAggregate = Omit< - OrderAggregate, - "line_items" + Order, + "line_items" | "refunds" | "fulfillments" > & { line_items: Array; + fulfillments: Array>; + refunds: Array< + Omit & { + refund_line_items: Array>; + } + >; }; export type CustomerOrderServiceGetProps = { @@ -43,7 +66,106 @@ export const CustomerOrderServiceGet = async ({ }, }, { $unwind: "$line_items" }, - ...OrderLookupProperties, + { + $lookup: { + from: "User", + let: { customerId: "$line_items.properties.customerId" }, + pipeline: [ + { + $match: { + $expr: { + $eq: ["$customerId", "$$customerId"], + }, + }, + }, + { + $project: { + customerId: 1, + username: 1, + createdAt: 1, + fullname: 1, + shortDescription: 1, + "images.profile": "$images.profile", + }, + }, + ], + as: "line_items.user", + }, + }, + { + $unwind: { + path: "$line_items.user", + preserveNullAndEmptyArrays: true, // Set to false if you always expect a match + }, + }, + { + $lookup: { + from: "Location", + let: { locationId: "$line_items.properties.locationId" }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { + $eq: ["$_id", { $toObjectId: "$$locationId" }], + }, + ], + }, + }, + }, + { + $project: { + name: 1, + fullAddress: 1, + originType: 1, + locationType: 1, + }, + }, + ], + as: "line_items.location", + }, + }, + { + $unwind: { + path: "$line_items.location", + preserveNullAndEmptyArrays: true, // Set to false if you always expect a match + }, + }, + { + $lookup: { + from: "Shipping", + let: { shippingId: "$line_items.properties.shippingId" }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { + $eq: ["$_id", { $toObjectId: "$$shippingId" }], + }, + ], + }, + }, + }, + { + $project: { + destination: 1, + duration: 1, + distance: 1, + cost: 1, + }, + }, + ], + as: "line_items.shipping", + }, + }, + { + $unwind: { + path: "$line_items.shipping", + preserveNullAndEmptyArrays: true, + }, + }, { $addFields: { refunds: { diff --git a/src/functions/customer/services/order/list.spec.ts b/src/functions/customer/services/order/list.spec.ts deleted file mode 100644 index 0c3ff762..00000000 --- a/src/functions/customer/services/order/list.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OrderModel } from "~/functions/order/order.models"; -import { Order } from "~/functions/order/order.types"; -import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-ordre-with-fullfilment-and-refunds"; -import { createUser } from "~/library/jest/helpers"; -import { CustomerOrderServiceList } from "./list"; -require("~/library/jest/mongoose/mongodb.jest"); - -describe("CustomerOrderServiceList", () => { - it("should return orders for customer on range of start/end", async () => { - await createUser({ customerId: 7106990342471 }); - - const dumbData = Order.parse(orderWithfulfillmentAndRefunds); - const response = await OrderModel.create(dumbData); - - const customerId = response.line_items[0].properties?.customerId || 0; - - const orders = await CustomerOrderServiceList({ - customerId: customerId, - start: "2023-11-26T00:00:00+03:00", - end: "2024-01-07T00:00:00+03:00", - }); - - expect(orders.length).toBe(2); - }); -}); diff --git a/src/functions/customer/services/order/list.ts b/src/functions/customer/services/order/list.ts deleted file mode 100644 index d54b1507..00000000 --- a/src/functions/customer/services/order/list.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { OrderModel } from "~/functions/order/order.models"; -import { OrderLineItem } from "~/functions/order/order.types"; -import { OrderAggregate } from "./_types"; - -export type CustomerOrderServiceListProps = { - customerId: number; - start: string; - end: string; -}; - -export type CustomerOrderServiceListAggregate = Omit< - OrderAggregate, - "line_items" -> & { - start: Date; - end: Date; - title: Date; - line_items: OrderLineItem; -}; - -export const CustomerOrderServiceList = async ({ - customerId, - start, - end, -}: CustomerOrderServiceListProps) => { - const startDate = new Date(start); - const endDate = new Date(end); - - return OrderModel.aggregate([ - { - $match: { - $and: [ - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte: startDate, - }, - }, - { - "line_items.properties.to": { - $lte: endDate, - }, - }, - ], - }, - ], - }, - }, - { $unwind: "$line_items" }, - { - $match: { - $and: [ - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte: startDate, - }, - }, - { - "line_items.properties.to": { - $lte: endDate, - }, - }, - ], - }, - ], - }, - }, - { - $addFields: { - start: "$line_items.properties.from", - end: "$line_items.properties.to", - title: "$line_items.title", - refunds: { - $filter: { - input: "$refunds", - as: "refund", - cond: { - $anyElementTrue: { - $map: { - input: "$$refund.refund_line_items", - as: "refund_line_item", - in: { - $eq: ["$$refund_line_item.line_item_id", "$line_items.id"], - }, - }, - }, - }, - }, - }, - fulfillments: { - $filter: { - input: "$fulfillments", - as: "fulfillment", - cond: { - $anyElementTrue: { - $map: { - input: "$$fulfillment.line_items", - as: "fulfillment_line_item", - in: { - $eq: ["$$fulfillment_line_item.id", "$line_items.id"], - }, - }, - }, - }, - }, - }, - }, - }, - { - $sort: { start: 1 }, - }, - { - $project: { - id: 1, - start: 1, - end: 1, - title: 1, - line_items: 1, - customer: 1, - order_number: 1, - fulfillment_status: 1, - financial_status: 1, - created_at: 1, - updated_at: 1, - cancel_reason: 1, - cancelled_at: 1, - note: 1, - note_attributes: 1, - fulfillments: 1, - refunds: 1, - }, - }, - ]); -}; diff --git a/src/functions/customer/services/order/paginate.ts b/src/functions/customer/services/order/paginate.ts index a2bf85ea..f6403f4f 100644 --- a/src/functions/customer/services/order/paginate.ts +++ b/src/functions/customer/services/order/paginate.ts @@ -1,6 +1,6 @@ import { PipelineStage } from "mongoose"; import { OrderModel } from "~/functions/order/order.models"; -import { CustomerOrderServiceListAggregate } from "./list"; +import { CustomerOrderServiceRangeAggregate } from "./range"; export type CustomerOrderServiceListProps = { customerId: number; @@ -109,9 +109,8 @@ export const CustomerOrderServicePaginate = async ({ } ); - const results = await OrderModel.aggregate( - pipeline - ); + const results = + await OrderModel.aggregate(pipeline); return { results, diff --git a/src/functions/customer/services/order/range.spec.ts b/src/functions/customer/services/order/range.spec.ts new file mode 100644 index 00000000..62a17245 --- /dev/null +++ b/src/functions/customer/services/order/range.spec.ts @@ -0,0 +1,74 @@ +import { addMinutes, subMinutes } from "date-fns"; +import { OrderModel } from "~/functions/order/order.models"; +import { Order } from "~/functions/order/order.types"; +import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-order-with-fullfilment-and-refunds"; +import { orderWithShipping } from "~/functions/webhook/data-order-with-shipping"; +import { createUser } from "~/library/jest/helpers"; +import { createLocation } from "~/library/jest/helpers/location"; +import { createShipping } from "~/library/jest/helpers/shipping"; +import { CustomerOrderServiceRange } from "./range"; +require("~/library/jest/mongoose/mongodb.jest"); + +describe("CustomerOrderServiceRange", () => { + it("should return orders for customer on range of start/end", async () => { + await createUser({ customerId: 7106990342471 }); + + const dumbData = Order.parse(orderWithfulfillmentAndRefunds); + const response = await OrderModel.create(dumbData); + + const customerId = response.line_items[0].properties?.customerId || 0; + + const orders = await CustomerOrderServiceRange({ + customerId: customerId, + start: "2023-11-26T00:00:00+03:00", + end: "2024-01-07T00:00:00+03:00", + }); + + expect(orders.length).toBe(2); + }); + + it("should return shipping information", async () => { + const customerId = 7106990342471; + const user = await createUser({ customerId: 7106990342471 }); + const location = await createLocation({ customerId: user.customerId }); + const shipping1 = await createShipping({ location: location.id }); + + const dumbData = Order.parse(orderWithShipping); + + dumbData.line_items[0].properties!.customerId = customerId; + dumbData.line_items[0].properties!.locationId = location._id.toString(); + dumbData.line_items[0].properties!.shippingId = shipping1._id.toString(); + dumbData.line_items[1].properties!.customerId = customerId; + dumbData.line_items[1].properties!.locationId = location._id.toString(); + dumbData.line_items[1].properties!.shippingId = shipping1._id.toString(); + + await OrderModel.create(dumbData); + + const orders = await CustomerOrderServiceRange({ + customerId: customerId, + start: "2023-11-26T00:00:00+03:00", + end: "2024-01-07T00:00:00+03:00", + }); + + const shippingIds = orders.map((item) => item.shipping?._id.toString()); + const uniqueShippingIds = new Set(shippingIds); + expect(shippingIds.length).toBe(uniqueShippingIds.size); + + //atleast one with shipping + const hasShipping = orders.find((obj) => obj.hasOwnProperty("shipping")); + expect(hasShipping).toBeDefined(); + + const start = subMinutes( + new Date(dumbData.line_items[0].properties!.from), + hasShipping?.shipping?.duration.value || 0 + ); + + const end = addMinutes( + new Date(dumbData.line_items[1].properties!.to), + hasShipping?.shipping?.duration.value || 0 + ); + + expect(hasShipping?.start).toEqual(start); + expect(hasShipping?.end).toEqual(end); + }); +}); diff --git a/src/functions/customer/services/order/range.ts b/src/functions/customer/services/order/range.ts new file mode 100644 index 00000000..bd1bd0c2 --- /dev/null +++ b/src/functions/customer/services/order/range.ts @@ -0,0 +1,272 @@ +import { OrderModel } from "~/functions/order/order.models"; +import { + Order, + OrderFulfillment, + OrderLineItem, + OrderRefund, + OrderRefundLineItem, +} from "~/functions/order/order.types"; +import { Shipping } from "~/functions/shipping/shipping.types"; + +export type CustomerOrderServiceRangeProps = { + customerId: number; + start: string; + end: string; +}; + +export type CustomerOrderServiceRangeAggregate = Omit< + Order, + "line_items" | "refunds" | "fulfillments" +> & { + start: Date; + end: Date; + title: Date; + line_items: OrderLineItem; + shipping?: Shipping; + fulfillments: Array>; + refunds: Array< + Omit & { + refund_line_items: Array>; + } + >; +}; + +export const CustomerOrderServiceRange = async ({ + customerId, + start, + end, +}: CustomerOrderServiceRangeProps) => { + const startDate = new Date(start); + const endDate = new Date(end); + + return OrderModel.aggregate([ + { + $match: { + $and: [ + { + $or: [ + { + "line_items.properties.customerId": customerId, + }, + { + "customer.id": customerId, + }, + ], + }, + { + $and: [ + { + "line_items.properties.from": { + $gte: startDate, + }, + }, + { + "line_items.properties.to": { + $lte: endDate, + }, + }, + ], + }, + ], + }, + }, + { $unwind: "$line_items" }, + { + $match: { + $and: [ + { + $or: [ + { + "line_items.properties.customerId": customerId, + }, + { + "customer.id": customerId, + }, + ], + }, + { + $and: [ + { + "line_items.properties.from": { + $gte: startDate, + }, + }, + { + "line_items.properties.to": { + $lte: endDate, + }, + }, + ], + }, + ], + }, + }, + { + $addFields: { + refunds: { + $filter: { + input: "$refunds", + as: "refund", + cond: { + $anyElementTrue: { + $map: { + input: "$$refund.refund_line_items", + as: "refund_line_item", + in: { + $eq: ["$$refund_line_item.line_item_id", "$line_items.id"], + }, + }, + }, + }, + }, + }, + fulfillments: { + $filter: { + input: "$fulfillments", + as: "fulfillment", + cond: { + $anyElementTrue: { + $map: { + input: "$$fulfillment.line_items", + as: "fulfillment_line_item", + in: { + $eq: ["$$fulfillment_line_item.id", "$line_items.id"], + }, + }, + }, + }, + }, + }, + }, + }, + { + $sort: { + "line_items.properties.groupId": 1, + "line_items.properties.from": 1, + }, + }, + { + $group: { + _id: "$line_items.properties.groupId", + line_items: { $push: "$line_items" }, + customer: { $first: "$customer" }, + orderNumber: { $first: "$order_number" }, + fulfillmentStatus: { $first: "$fulfillment_status" }, + financialStatus: { $first: "$financial_status" }, + createdAt: { $first: "$created_at" }, + updatedAt: { $first: "$updated_at" }, + cancelReason: { $first: "$cancel_reason" }, + cancelledAt: { $first: "$cancelled_at" }, + note: { $first: "$note" }, + noteAttributes: { $first: "$note_attributes" }, + fulfillmentsArray: { $push: "$fulfillments" }, + refundsArray: { $push: "$refunds" }, + }, + }, + { + $addFields: { + shippingId: { $first: "$line_items.properties.shippingId" }, + end: { $last: "$line_items.properties.to" }, + start: { $first: "$line_items.properties.from" }, + }, + }, + { + $lookup: { + from: "Shipping", + let: { shippingId: "$shippingId" }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { + $eq: ["$_id", { $toObjectId: "$$shippingId" }], + }, + ], + }, + }, + }, + { + $project: { + origin: 1, + destination: 1, + duration: 1, + distance: 1, + cost: 1, + }, + }, + ], + as: "shipping", + }, + }, + { + $unwind: { + path: "$shipping", + preserveNullAndEmptyArrays: true, + }, + }, + { + $addFields: { + start: { + $cond: { + if: { $gt: ["$shipping.duration.value", 0] }, + then: { + $dateSubtract: { + startDate: "$start", + unit: "minute", + amount: { $toInt: "$shipping.duration.value" }, + }, + }, + else: "$start", + }, + }, + end: { + $cond: { + if: { $gt: ["$shipping.duration.value", 0] }, + then: { + $dateAdd: { + startDate: "$end", + unit: "minute", + amount: { $toInt: "$shipping.duration.value" }, + }, + }, + else: "$end", + }, + }, + }, + }, + { + $project: { + id: "$_id", + start: 1, + end: 1, + shipping: 1, + line_items: 1, + customer: 1, + orderNumber: 1, + fulfillmentStatus: 1, + financialStatus: 1, + createdAt: 1, + updatedAt: 1, + cancelReason: 1, + cancelledAt: 1, + note: 1, + noteAttributes: 1, + fulfillments: { + $reduce: { + input: "$fulfillmentsArray", + initialValue: [], + in: { $concatArrays: ["$$value", "$$this"] }, + }, + }, + refunds: { + $reduce: { + input: "$refundsArray", + initialValue: [], + in: { $concatArrays: ["$$value", "$$this"] }, + }, + }, + }, + }, + ]); +}; diff --git a/src/functions/customer/services/order/shipping.spec.ts b/src/functions/customer/services/order/shipping.spec.ts deleted file mode 100644 index f41b2105..00000000 --- a/src/functions/customer/services/order/shipping.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { OrderModel } from "~/functions/order/order.models"; -import { Order } from "~/functions/order/order.types"; -import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-ordre-with-fullfilment-and-refunds"; -import { createUser } from "~/library/jest/helpers"; -import { createLocation } from "~/library/jest/helpers/location"; -import { createShipping } from "~/library/jest/helpers/shipping"; -import { CustomerOrderServiceShipping } from "./shipping"; -require("~/library/jest/mongoose/mongodb.jest"); - -describe("CustomerOrderServiceShipping", () => { - it("should return shipping orders for customer on range of start/end", async () => { - const customerId = 7106990342471; - const user = await createUser({ customerId: 7106990342471 }); - const location = await createLocation({ customerId: user.customerId }); - const shipping = await createShipping({ location: location.id }); - - const dumbData = Order.parse(orderWithfulfillmentAndRefunds); - dumbData.line_items = dumbData.line_items.map((lineItems) => { - lineItems.properties!.customerId = customerId; - lineItems.properties!.locationId = location._id.toString(); - lineItems.properties!.shippingId = shipping._id.toString(); - return lineItems; - }); - - await OrderModel.create(dumbData); - - const orders = await CustomerOrderServiceShipping({ - customerId: customerId, - start: "2023-11-26T00:00:00+03:00", - end: "2024-01-07T00:00:00+03:00", - }); - - const shippingIds = orders.map((item) => item.shipping._id.toString()); - const uniqueShippingIds = new Set(shippingIds); - expect(shippingIds.length).toBe(uniqueShippingIds.size); - - orders.forEach((item) => { - expect(item).toHaveProperty("shipping"); - expect(item.shipping).toBeDefined(); - }); - }); -}); diff --git a/src/functions/customer/services/order/shipping.ts b/src/functions/customer/services/order/shipping.ts deleted file mode 100644 index 93487656..00000000 --- a/src/functions/customer/services/order/shipping.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { addMinutes, subMinutes } from "date-fns"; -import { Location } from "~/functions/location"; -import { OrderModel } from "~/functions/order/order.models"; -import { Shipping } from "~/functions/shipping/shipping.types"; -import { OrderAggregate } from "./_types"; - -export type CustomerOrderServiceShippingProps = { - customerId: number; - start: string | Date; - end: string | Date; -}; - -export type CustomerOrderServiceShippingAggregate = Pick< - OrderAggregate, - "id" | "customer" | "order_number" -> & { - start: Date; - end: Date; - title: Date; - shipping: Shipping; - location: Location; -}; - -export const CustomerOrderServiceShipping = async ({ - customerId, - start, - end, -}: CustomerOrderServiceShippingProps) => { - const startDate = new Date(start); - const endDate = new Date(end); - - const orders = - await OrderModel.aggregate([ - { - $match: { - $and: [ - { "line_items.properties.shippingId": { $exists: true } }, - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte: startDate, - }, - }, - { - "line_items.properties.to": { - $lte: endDate, - }, - }, - ], - }, - ], - }, - }, - { $unwind: "$line_items" }, - { - $match: { - $and: [ - { "line_items.properties.shippingId": { $exists: true } }, - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte: startDate, - }, - }, - { - "line_items.properties.to": { - $lte: endDate, - }, - }, - ], - }, - ], - }, - }, - { - $addFields: { - start: "$line_items.properties.from", - end: "$line_items.properties.to", - title: "$line_items.title", - refunds: { - $filter: { - input: "$refunds", - as: "refund", - cond: { - $anyElementTrue: { - $map: { - input: "$$refund.refund_line_items", - as: "refund_line_item", - in: { - $eq: [ - "$$refund_line_item.line_item_id", - "$line_items.id", - ], - }, - }, - }, - }, - }, - }, - }, - }, - { - $match: { - refunds: { $size: 0 }, - }, - }, - { - $lookup: { - from: "Shipping", - let: { shippingId: "$line_items.properties.shippingId" }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { - $eq: ["$_id", { $toObjectId: "$$shippingId" }], - }, - ], - }, - }, - }, - { - $project: { - origin: 1, - destination: 1, - duration: 1, - distance: 1, - cost: 1, - }, - }, - ], - as: "shipping", - }, - }, - { - $unwind: { - path: "$shipping", - preserveNullAndEmptyArrays: true, - }, - }, - { - $sort: { start: 1 }, - }, - { - $group: { - _id: "$shipping._id", - shipping: { $first: "$shipping" }, - id: { $first: "$id" }, - start: { $first: "$start" }, - end: { $first: "$end" }, - title: { $first: "$title" }, - customer: { $first: "$customer" }, - order_number: { $first: "$order_number" }, - }, - }, - { - $project: { - id: 1, - start: 1, - end: 1, - title: 1, - customer: 1, - order_number: 1, - shipping: 1, - }, - }, - ]); - - return orders.map((order) => { - order.start = subMinutes(order.start, order.shipping.duration.value); - order.end = addMinutes(order.end, order.shipping.duration.value); - return order; - }); -}; From a13d167108d110c6f57341961f40990fcb6d7ebb Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Wed, 28 Feb 2024 13:17:16 +0100 Subject: [PATCH 2/6] refactor(order): remove unused order controller files feat(order): add groupId field to LineItemSchema feat(order): add groupId field to Properties interface feat(order): add range endpoint to order controllers feat(order): add shippingId field to Properties interface feat(order): update get-shipping-booked-time service to handle multiple line items feat(order): update get-shipping-booked-time service to sort by shippingId and from date feat(webhook): add data for an order with fulfillment and refunds feat(webhook): add data for an order with shipping information feat(webhook): add data for order with no fulfillment to webhook data-order.ts fix(order.spec.ts): correct file names for data imports feat(order.ts): add groupId field to getOrderObject function --- .../controllers/order/get-lineitem.ts | 24 - .../controllers/order/get-shipping.ts | 25 -- .../customer/controllers/order/list.ts | 26 -- .../customer/controllers/order/range.ts | 26 ++ .../customer/controllers/order/shipping.ts | 26 -- src/functions/order/order.schema.ts | 1 + src/functions/order/order.types.ts | 1 + .../get-shipping-booked-time.spec.ts | 41 +- .../availability/get-shipping-booked-time.ts | 18 +- ...ata-order-with-fullfilment-and-refunds.ts} | 50 +++ .../webhook/data-order-with-shipping.ts | 414 ++++++++++++++++++ .../webhook/{data-ordre.ts => data-order.ts} | 2 + src/functions/webhook/order.spec.ts | 4 +- src/library/jest/helpers/order.ts | 1 + 14 files changed, 531 insertions(+), 128 deletions(-) delete mode 100644 src/functions/customer/controllers/order/get-lineitem.ts delete mode 100644 src/functions/customer/controllers/order/get-shipping.ts delete mode 100644 src/functions/customer/controllers/order/list.ts create mode 100644 src/functions/customer/controllers/order/range.ts delete mode 100644 src/functions/customer/controllers/order/shipping.ts rename src/functions/webhook/{data-ordre-with-fullfilment-and-refunds.ts => data-order-with-fullfilment-and-refunds.ts} (92%) create mode 100644 src/functions/webhook/data-order-with-shipping.ts rename src/functions/webhook/{data-ordre.ts => data-order.ts} (99%) diff --git a/src/functions/customer/controllers/order/get-lineitem.ts b/src/functions/customer/controllers/order/get-lineitem.ts deleted file mode 100644 index 92ae3d96..00000000 --- a/src/functions/customer/controllers/order/get-lineitem.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from "zod"; -import { _ } from "~/library/handler"; -import { NumberOrStringType } from "~/library/zod"; -import { CustomerOrderServiceGetLineItem } from "../../services/order/get-lineitem"; - -export type CustomerOrderControllerGetLineItemRequest = { - query: z.infer; -}; - -export const CustomerOrderControllerGetLineItemSchema = z.object({ - customerId: NumberOrStringType, - lineItemId: NumberOrStringType, -}); - -export type CustomerOrderControllerGetLineItemResponse = Awaited< - ReturnType ->; - -export const CustomerOrderControllerGetLineItem = _( - async ({ query }: CustomerOrderControllerGetLineItemRequest) => { - const validateData = CustomerOrderControllerGetLineItemSchema.parse(query); - return CustomerOrderServiceGetLineItem(validateData); - } -); diff --git a/src/functions/customer/controllers/order/get-shipping.ts b/src/functions/customer/controllers/order/get-shipping.ts deleted file mode 100644 index 8af5378f..00000000 --- a/src/functions/customer/controllers/order/get-shipping.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { _ } from "~/library/handler"; - -import { z } from "zod"; -import { NumberOrStringType } from "~/library/zod"; -import { CustomerOrderServiceGetShipping } from "../../services/order/get-shipping"; - -export type CustomerOrderControllerGetShippingRequest = { - query: z.infer; -}; - -export const CustomerOrderControllerGetShippingSchema = z.object({ - customerId: NumberOrStringType, - id: NumberOrStringType, -}); - -export type CustomerOrderControllerGetResponse = Awaited< - ReturnType ->; - -export const CustomerOrderControllerGetShipping = _( - async ({ query }: CustomerOrderControllerGetShippingRequest) => { - const validateData = CustomerOrderControllerGetShippingSchema.parse(query); - return CustomerOrderServiceGetShipping(validateData); - } -); diff --git a/src/functions/customer/controllers/order/list.ts b/src/functions/customer/controllers/order/list.ts deleted file mode 100644 index ea3ad43d..00000000 --- a/src/functions/customer/controllers/order/list.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { _ } from "~/library/handler"; - -import { z } from "zod"; -import { NumberOrStringType } from "~/library/zod"; -import { CustomerOrderServiceList } from "../../services/order/list"; - -export type CustomerOrderControllerListRequest = { - query: z.infer; -}; - -export const CustomerOrderControllerListSchema = z.object({ - customerId: NumberOrStringType, - start: z.string(), - end: z.string(), -}); - -export type CustomerOrderControllerListResponse = Awaited< - ReturnType ->; - -export const CustomerOrderControllerList = _( - async ({ query }: CustomerOrderControllerListRequest) => { - const validateData = CustomerOrderControllerListSchema.parse(query); - return CustomerOrderServiceList(validateData); - } -); diff --git a/src/functions/customer/controllers/order/range.ts b/src/functions/customer/controllers/order/range.ts new file mode 100644 index 00000000..e38c27ce --- /dev/null +++ b/src/functions/customer/controllers/order/range.ts @@ -0,0 +1,26 @@ +import { _ } from "~/library/handler"; + +import { z } from "zod"; +import { NumberOrStringType } from "~/library/zod"; +import { CustomerOrderServiceRange } from "../../services/order/range"; + +export type CustomerOrderControllerRangeRequest = { + query: z.infer; +}; + +export const CustomerOrderControllerRangeSchema = z.object({ + customerId: NumberOrStringType, + start: z.string(), + end: z.string(), +}); + +export type CustomerOrderControllerRangeResponse = Awaited< + ReturnType +>; + +export const CustomerOrderControllerRange = _( + async ({ query }: CustomerOrderControllerRangeRequest) => { + const validateData = CustomerOrderControllerRangeSchema.parse(query); + return CustomerOrderServiceRange(validateData); + } +); diff --git a/src/functions/customer/controllers/order/shipping.ts b/src/functions/customer/controllers/order/shipping.ts deleted file mode 100644 index a99089ae..00000000 --- a/src/functions/customer/controllers/order/shipping.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { _ } from "~/library/handler"; - -import { z } from "zod"; -import { NumberOrStringType } from "~/library/zod"; -import { CustomerOrderServiceShipping } from "../../services/order/shipping"; - -export type CustomerOrderControllerShippingRequest = { - query: z.infer; -}; - -export const CustomerOrderControllerShippingSchema = z.object({ - customerId: NumberOrStringType, - start: z.string(), - end: z.string(), -}); - -export type CustomerOrderControllerGetResponse = Awaited< - ReturnType ->; - -export const CustomerOrderControllerShipping = _( - async ({ query }: CustomerOrderControllerShippingRequest) => { - const validateData = CustomerOrderControllerShippingSchema.parse(query); - return CustomerOrderServiceShipping(validateData); - } -); diff --git a/src/functions/order/order.schema.ts b/src/functions/order/order.schema.ts index 278e20dd..1673682e 100644 --- a/src/functions/order/order.schema.ts +++ b/src/functions/order/order.schema.ts @@ -126,6 +126,7 @@ const LineItemSchema = new Schema( index: true, }, customerId: { type: Number, index: true }, + groupId: { type: String, index: true }, locationId: String, shippingId: String, }, diff --git a/src/functions/order/order.types.ts b/src/functions/order/order.types.ts index 58e1600c..5aa022d7 100644 --- a/src/functions/order/order.types.ts +++ b/src/functions/order/order.types.ts @@ -93,6 +93,7 @@ interface Properties { from: Date; to: Date; locationId: string; + groupId: string; shippingId?: string; } diff --git a/src/functions/user/services/availability/get-shipping-booked-time.spec.ts b/src/functions/user/services/availability/get-shipping-booked-time.spec.ts index 4ce102a3..8217e5c0 100644 --- a/src/functions/user/services/availability/get-shipping-booked-time.spec.ts +++ b/src/functions/user/services/availability/get-shipping-booked-time.spec.ts @@ -1,10 +1,12 @@ +import { addMinutes, subMinutes } from "date-fns"; import { OrderModel } from "~/functions/order/order.models"; import { Order } from "~/functions/order/order.types"; -import { orderWithfulfillmentAndRefunds } from "~/functions/webhook/data-ordre-with-fullfilment-and-refunds"; +import { orderWithShipping } from "~/functions/webhook/data-order-with-shipping"; import { createUser } from "~/library/jest/helpers"; import { createLocation } from "~/library/jest/helpers/location"; import { createShipping } from "~/library/jest/helpers/shipping"; import { CustomerOrderServiceGetShippingBookedTime } from "./get-shipping-booked-time"; + require("~/library/jest/mongoose/mongodb.jest"); describe("CustomerOrderServiceGetShippingBookedTime", () => { @@ -12,15 +14,16 @@ describe("CustomerOrderServiceGetShippingBookedTime", () => { const customerId = 7106990342471; const user = await createUser({ customerId: 7106990342471 }); const location = await createLocation({ customerId: user.customerId }); - const shipping = await createShipping({ location: location.id }); - - const dumbData = Order.parse(orderWithfulfillmentAndRefunds); - dumbData.line_items = dumbData.line_items.map((lineItems) => { - lineItems.properties!.customerId = customerId; - lineItems.properties!.locationId = location._id.toString(); - lineItems.properties!.shippingId = shipping._id.toString(); - return lineItems; - }); + const shipping1 = await createShipping({ location: location.id }); + + const dumbData = Order.parse(orderWithShipping); + + dumbData.line_items[0].properties!.customerId = customerId; + dumbData.line_items[0].properties!.locationId = location._id.toString(); + dumbData.line_items[0].properties!.shippingId = shipping1._id.toString(); + dumbData.line_items[1].properties!.customerId = customerId; + dumbData.line_items[1].properties!.locationId = location._id.toString(); + dumbData.line_items[1].properties!.shippingId = shipping1._id.toString(); await OrderModel.create(dumbData); @@ -30,13 +33,17 @@ describe("CustomerOrderServiceGetShippingBookedTime", () => { end: "2024-01-07T00:00:00+03:00", }); - const shippingIds = orders.map((item) => item.shipping._id.toString()); - const uniqueShippingIds = new Set(shippingIds); - expect(shippingIds.length).toBe(uniqueShippingIds.size); + const start = subMinutes( + new Date(dumbData.line_items[0].properties!.from), + orders[0].shipping.duration.value + ); - orders.forEach((item) => { - expect(item).toHaveProperty("shipping"); - expect(item.shipping).toBeDefined(); - }); + const end = addMinutes( + new Date(dumbData.line_items[1].properties!.to), + orders[0].shipping.duration.value + ); + + expect(orders[0].from).toEqual(start); + expect(orders[0].to).toEqual(end); }); }); diff --git a/src/functions/user/services/availability/get-shipping-booked-time.ts b/src/functions/user/services/availability/get-shipping-booked-time.ts index b277b6b2..63cd74a1 100644 --- a/src/functions/user/services/availability/get-shipping-booked-time.ts +++ b/src/functions/user/services/availability/get-shipping-booked-time.ts @@ -88,10 +88,16 @@ export const CustomerOrderServiceGetShippingBookedTime = async ({ ], }, }, + { + $sort: { + "line_items.properties.shippingId": 1, + "line_items.properties.from": 1, + }, + }, { $addFields: { - from: "$line_items.properties.from", - to: "$line_items.properties.to", + shippingId: "$line_items.properties.shippingId", + title: "$line_items.title", refunds: { $filter: { input: "$refunds", @@ -150,16 +156,13 @@ export const CustomerOrderServiceGetShippingBookedTime = async ({ preserveNullAndEmptyArrays: true, }, }, - { - $sort: { start: 1 }, - }, { $group: { _id: "$shipping._id", shipping: { $first: "$shipping" }, id: { $first: "$id" }, - from: { $first: "$start" }, - to: { $first: "$end" }, + from: { $first: "$line_items.properties.from" }, + to: { $last: "$line_items.properties.to" }, }, }, { @@ -167,7 +170,6 @@ export const CustomerOrderServiceGetShippingBookedTime = async ({ id: 1, from: 1, to: 1, - shipping: 1, }, }, ] diff --git a/src/functions/webhook/data-ordre-with-fullfilment-and-refunds.ts b/src/functions/webhook/data-order-with-fullfilment-and-refunds.ts similarity index 92% rename from src/functions/webhook/data-ordre-with-fullfilment-and-refunds.ts rename to src/functions/webhook/data-order-with-fullfilment-and-refunds.ts index fa4b49ed..3f0426a4 100644 --- a/src/functions/webhook/data-ordre-with-fullfilment-and-refunds.ts +++ b/src/functions/webhook/data-order-with-fullfilment-and-refunds.ts @@ -266,6 +266,7 @@ export const orderWithfulfillmentAndRefunds = { { name: "_from", value: "2023-12-18T07:00:00.000Z" }, { name: "_to", value: "2023-12-18T08:15:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, { name: "Tid", value: "mandag, 18. december 10:00" }, { name: "Varighed", value: "1 time(r)" }, ], @@ -328,6 +329,7 @@ export const orderWithfulfillmentAndRefunds = { { name: "_from", value: "2024-01-01T06:15:00.000Z" }, { name: "_to", value: "2024-01-01T07:30:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, { name: "Skønhedsekspert", value: "hana nielsen" }, { name: "Tid", value: "mandag, 1. januar 09:15" }, { name: "Varighed", value: "1 time(r)" }, @@ -437,6 +439,7 @@ export const orderWithfulfillmentAndRefunds = { { name: "_from", value: "2023-12-18T07:00:00.000Z" }, { name: "_to", value: "2023-12-18T08:15:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, { name: "Skønhedsekspert", value: "hana nielsen" }, { name: "Tid", value: "mandag, 18. december 10:00" }, { name: "Varighed", value: "1 time(r)" }, @@ -483,6 +486,7 @@ export const orderWithfulfillmentAndRefunds = { { name: "_from", value: "2024-01-01T06:15:00.000Z" }, { name: "_to", value: "2024-01-01T07:30:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "12345" }, { name: "_locationId", value: "65dc739939e83647f048b4d5" }, { name: "_shippingId", value: "65dc739939e83647f048b4d5" }, { name: "Skønhedsekspert", value: "hana nielsen" }, @@ -573,6 +577,52 @@ export const orderWithfulfillmentAndRefunds = { { name: "_from", value: "2023-12-18T07:00:00.000Z" }, { name: "_to", value: "2023-12-18T08:15:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, + { name: "_locationId", value: "65dc739939e83647f048b4d5" }, + { name: "_shippingId", value: "65dc739939e83647f048b4d5" }, + { name: "Skønhedsekspert", value: "hana nielsen" }, + { name: "Tid", value: "mandag, 18. december 10:00" }, + { name: "Varighed", value: "1 time(r)" }, + ], + quantity: 1, + requires_shipping: true, + sku: "", + taxable: false, + title: "Børneklip (fra 6 år)", + total_discount: "0.00", + total_discount_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + variant_id: 46727191036231, + variant_inventory_management: null, + variant_title: "Artist 0", + vendor: "By Sisters", + tax_lines: [], + duties: [], + discount_allocations: [], + }, + { + id: 15174509166919, + admin_graphql_api_id: "gid://shopify/LineItem/15174509166919", + fulfillable_quantity: 0, + fulfillment_service: "manual", + fulfillment_status: "fulfilled", + gift_card: false, + grams: 0, + name: "Herreklippning 30 - Artist 0", + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + product_exists: true, + product_id: 8022088646930, + properties: [ + { name: "_from", value: "2023-12-18T08:15:00.000Z" }, + { name: "_to", value: "2023-12-18T09:15:00.000Z" }, + { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, { name: "_locationId", value: "65dc739939e83647f048b4d5" }, { name: "_shippingId", value: "65dc739939e83647f048b4d5" }, { name: "Skønhedsekspert", value: "hana nielsen" }, diff --git a/src/functions/webhook/data-order-with-shipping.ts b/src/functions/webhook/data-order-with-shipping.ts new file mode 100644 index 00000000..02e61202 --- /dev/null +++ b/src/functions/webhook/data-order-with-shipping.ts @@ -0,0 +1,414 @@ +export const orderWithShipping = { + id: 5874046992711, + admin_graphql_api_id: "gid://shopify/Order/5874046992711", + app_id: 31210962945, + browser_ip: "176.89.108.222", + buyer_accepts_marketing: true, + cancel_reason: null, + cancelled_at: null, + cart_token: "Z2NwLWV1cm9wZS13ZXN0MzowMUhINko1MFpFQlJWMTM1SFY5MjZUUktOMw", + checkout_id: 42324827701575, + checkout_token: "51210de099db2630837a9ff20d2d6ba6", + client_details: { + accept_language: "da-DK", + browser_height: null, + browser_ip: "176.89.108.222", + browser_width: null, + session_hash: null, + user_agent: + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", + }, + closed_at: null, + confirmation_number: "XP2J5MIG8", + confirmed: true, + contact_email: "test@soueidan.com", + created_at: "2023-12-09T06:53:51+01:00", + currency: "DKK", + current_subtotal_price: "0.00", + current_subtotal_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + current_total_additional_fees_set: null, + current_total_discounts: "0.00", + current_total_discounts_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + current_total_duties_set: null, + current_total_price: "0.00", + current_total_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + current_total_tax: "0.00", + current_total_tax_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + customer_locale: "da-DK", + device_id: null, + discount_codes: [], + email: "test@soueidan.com", + estimated_taxes: false, + financial_status: "paid", + fulfillment_status: null, + landing_site: "/", + landing_site_ref: null, + location_id: null, + merchant_of_record_app_id: null, + name: "#1014", + note: null, + note_attributes: [], + number: 14, + order_number: 1014, + order_status_url: + "https://bysistersdk.myshopify.com/68240605458/orders/3886ff2088637150765aa513c246dc16/authenticate?key=f16c8779934ac083f2016d65899baa0e", + original_total_additional_fees_set: null, + original_total_duties_set: null, + payment_gateway_names: [], + phone: null, + po_number: null, + presentment_currency: "DKK", + processed_at: "2023-12-09T06:53:50+01:00", + reference: "95a2cd9dad9350a742e0ba7e30cf1b3c", + referring_site: "", + source_identifier: "95a2cd9dad9350a742e0ba7e30cf1b3c", + source_name: "31210962945", + source_url: null, + subtotal_price: "0.00", + subtotal_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + tags: "", + tax_exempt: false, + tax_lines: [], + taxes_included: true, + test: false, + token: "3886ff2088637150765aa513c246dc16", + total_discounts: "0.00", + total_discounts_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + total_line_items_price: "0.00", + total_line_items_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + total_outstanding: "0.00", + total_price: "0.00", + total_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + total_shipping_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + total_tax: "0.00", + total_tax_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + total_tip_received: "0.00", + total_weight: 0, + updated_at: "2023-12-09T07:24:58+01:00", + user_id: null, + billing_address: { + first_name: "ja", + address1: "Odensegade", + phone: "25 12 33 12", + city: "Østerbro", + zip: "2100", + province: null, + country: "Denmark", + last_name: "misa", + address2: null, + company: null, + latitude: null, + longitude: null, + name: "ja misa", + country_code: "DK", + province_code: null, + }, + customer: { + id: 7123671482695, + email: "test@soueidan.com", + accepts_marketing: true, + created_at: "2023-05-23T22:53:49+02:00", + updated_at: "2023-12-09T06:53:51+01:00", + first_name: "kunde", + last_name: "soueidan", + state: "invited", + note: null, + verified_email: true, + multipass_identifier: null, + tax_exempt: false, + phone: null, + email_marketing_consent: { + state: "subscribed", + opt_in_level: "single_opt_in", + consent_updated_at: "2023-05-23T22:53:50+02:00", + }, + sms_marketing_consent: null, + tags: "", + currency: "DKK", + accepts_marketing_updated_at: "2023-05-23T22:53:50+02:00", + marketing_opt_in_level: "single_opt_in", + tax_exemptions: [], + admin_graphql_api_id: "gid://shopify/Customer/7123671482695", + default_address: { + id: 9939867369799, + customer_id: 7123671482695, + first_name: "ja", + last_name: "misa", + company: null, + address1: "Odensegade", + address2: null, + city: "Østerbro", + province: null, + country: "Denmark", + zip: "2100", + phone: "25 12 33 12", + name: "ja misa", + province_code: null, + country_code: "DK", + country_name: "Denmark", + default: true, + }, + }, + discount_applications: [], + fulfillments: [], + line_items: [ + { + id: 15174509101383, + admin_graphql_api_id: "gid://shopify/LineItem/15174509101383", + fulfillable_quantity: 1, + fulfillment_service: "manual", + fulfillment_status: null, + gift_card: false, + grams: 0, + name: "Brudemakeup - Artist 0", + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + product_exists: true, + product_id: 8022088745234, + properties: [ + { name: "_from", value: "2024-01-01T06:15:00.000Z" }, + { name: "_to", value: "2024-01-01T07:00:00.000Z" }, + { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, + { name: "Skønhedsekspert", value: "hana nielsen" }, + { name: "Tid", value: "mandag, 1. januar 09:15" }, + { name: "Varighed", value: "1 time(r)" }, + ], + quantity: 1, + requires_shipping: false, + sku: "", + taxable: false, + title: "Brudemakeup", + total_discount: "0.00", + total_discount_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + variant_id: 48807163691335, + variant_inventory_management: null, + variant_title: "Artist 0", + vendor: "By Sisters", + tax_lines: [], + duties: [], + discount_allocations: [], + }, + { + id: 15174509101383, + admin_graphql_api_id: "gid://shopify/LineItem/15174509101383", + fulfillable_quantity: 1, + fulfillment_service: "manual", + fulfillment_status: null, + gift_card: false, + grams: 0, + name: "Børneklip - Artist 0", + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + product_exists: true, + product_id: 8022088745234, + properties: [ + { name: "_from", value: "2024-01-01T07:00:00.000Z" }, + { name: "_to", value: "2024-01-01T08:00:00.000Z" }, + { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, + { name: "Skønhedsekspert", value: "hana nielsen" }, + { name: "Tid", value: "mandag, 1. januar 09:15" }, + { name: "Varighed", value: "1 time(r)" }, + ], + quantity: 1, + requires_shipping: false, + sku: "", + taxable: false, + title: "Brudemakeup", + total_discount: "0.00", + total_discount_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + variant_id: 48807163691335, + variant_inventory_management: null, + variant_title: "Artist 0", + vendor: "By Sisters", + tax_lines: [], + duties: [], + discount_allocations: [], + }, + { + id: 15174509134151, + admin_graphql_api_id: "gid://shopify/LineItem/15174509134151", + fulfillable_quantity: 1, + fulfillment_service: "manual", + fulfillment_status: null, + gift_card: false, + grams: 0, + name: "Biotin og Capilia Longa", + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + product_exists: true, + product_id: 8420509614407, + properties: [], + quantity: 1, + requires_shipping: true, + sku: "", + taxable: true, + title: "Biotin og Capilia Longa", + total_discount: "0.00", + total_discount_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + variant_id: 46740488290631, + variant_inventory_management: "shopify", + variant_title: null, + vendor: "BySisters", + tax_lines: [ + { + channel_liable: false, + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + rate: 0.25, + title: "DK Moms", + }, + ], + duties: [], + discount_allocations: [], + }, + { + id: 15174509166919, + admin_graphql_api_id: "gid://shopify/LineItem/15174509166919", + fulfillable_quantity: 1, + fulfillment_service: "manual", + fulfillment_status: null, + gift_card: false, + grams: 0, + name: "Herreklip 30 år - Artist 0", + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + product_exists: true, + product_id: 8022088646930, + properties: [ + { name: "_from", value: "2023-12-18T07:00:00.000Z" }, + { name: "_to", value: "2023-12-18T08:15:00.000Z" }, + { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "1234" }, + { name: "Skønhedsekspert", value: "hana nielsen" }, + { name: "Tid", value: "mandag, 18. december 10:00" }, + { name: "Varighed", value: "1 time(r)" }, + ], + quantity: 1, + requires_shipping: true, + sku: "", + taxable: false, + title: "Børneklip (fra 6 år)", + total_discount: "0.00", + total_discount_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + variant_id: 46727191036231, + variant_inventory_management: null, + variant_title: "Artist 0", + vendor: "By Sisters", + tax_lines: [], + duties: [], + discount_allocations: [], + }, + ], + payment_terms: null, + refunds: [], + shipping_address: { + first_name: "ja", + address1: "Odensegade", + phone: "25 12 33 12", + city: "Østerbro", + zip: "2100", + province: null, + country: "Denmark", + last_name: "misa", + address2: null, + company: null, + latitude: 55.7001114, + longitude: 12.5807326, + name: "ja misa", + country_code: "DK", + province_code: null, + }, + shipping_lines: [ + { + id: 4957849485639, + carrier_identifier: "0c9403206891a20ecd54cf28f40ddb5b", + code: "backup_rate", + discounted_price: "0.00", + discounted_price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + phone: null, + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + requested_fulfillment_service_id: null, + source: "merchant_customized_by_order_value", + title: "Levering", + tax_lines: [ + { + channel_liable: false, + price: "0.00", + price_set: { + shop_money: { amount: "0.00", currency_code: "DKK" }, + presentment_money: { amount: "0.00", currency_code: "DKK" }, + }, + rate: 0.25, + title: "DK Moms", + }, + ], + discount_allocations: [], + }, + ], +}; diff --git a/src/functions/webhook/data-ordre.ts b/src/functions/webhook/data-order.ts similarity index 99% rename from src/functions/webhook/data-ordre.ts rename to src/functions/webhook/data-order.ts index 97b8a1a3..600b8f95 100644 --- a/src/functions/webhook/data-ordre.ts +++ b/src/functions/webhook/data-order.ts @@ -202,6 +202,7 @@ export const orderNofulfillment = { { name: "_from", value: "2024-01-01T06:15:00.000Z" }, { name: "_to", value: "2024-01-01T07:30:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, { name: "Skønhedsekspert", value: "hana nielsen" }, { name: "Tid", value: "mandag, 1. januar 09:15" }, { name: "Varighed", value: "1 time(r)" }, @@ -290,6 +291,7 @@ export const orderNofulfillment = { { name: "_from", value: "2023-12-18T07:00:00.000Z" }, { name: "_to", value: "2023-12-18T08:15:00.000Z" }, { name: "_customerId", value: "7106990342471" }, + { name: "_groupId", value: "123" }, { name: "Skønhedsekspert", value: "hana nielsen" }, { name: "Tid", value: "mandag, 18. december 10:00" }, { name: "Varighed", value: "1 time(r)" }, diff --git a/src/functions/webhook/order.spec.ts b/src/functions/webhook/order.spec.ts index 97361962..d69da60f 100644 --- a/src/functions/webhook/order.spec.ts +++ b/src/functions/webhook/order.spec.ts @@ -1,7 +1,7 @@ import { InvocationContext } from "@azure/functions"; import { createContext } from "~/library/jest/azure"; -import { orderNofulfillment } from "./data-ordre"; -import { orderWithfulfillmentAndRefunds } from "./data-ordre-with-fullfilment-and-refunds"; +import { orderNofulfillment } from "./data-order"; +import { orderWithfulfillmentAndRefunds } from "./data-order-with-fullfilment-and-refunds"; import { webhookOrderProcess } from "./order"; require("~/library/jest/mongoose/mongodb.jest"); diff --git a/src/library/jest/helpers/order.ts b/src/library/jest/helpers/order.ts index 2e9eb2e3..c7a9ab25 100644 --- a/src/library/jest/helpers/order.ts +++ b/src/library/jest/helpers/order.ts @@ -29,6 +29,7 @@ export const getOrderObject = ({ properties: { ...generateRandomDateRange(), customerId, + groupId: "12", locationId: "1", }, quantity: 1, From 9352e0706c06dce16f83323f0536614ccba225aa Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Wed, 28 Feb 2024 13:17:50 +0100 Subject: [PATCH 3/6] Update openapi --- openapi/openapi.yaml | 24 +++-------- .../paths/customer/order/_types/order.yaml | 2 + .../order/get-line-item/get-line-item.yaml | 15 ------- .../customer/order/get-line-item/index.yaml | 37 ---------------- .../order/get-line-item/response.yaml | 11 ----- .../customer/order/get-shipping/index.yaml | 36 ---------------- .../customer/order/get-shipping/list.yaml | 25 ----------- .../customer/order/get-shipping/response.yaml | 10 ----- .../customer/order/{list => range}/index.yaml | 8 ++-- .../customer/order/{list => range}/list.yaml | 0 .../order/{list => range}/response.yaml | 0 .../paths/customer/order/shipping/index.yaml | 42 ------------------- .../paths/customer/order/shipping/list.yaml | 25 ----------- .../customer/order/shipping/response.yaml | 12 ------ 14 files changed, 12 insertions(+), 235 deletions(-) delete mode 100644 openapi/paths/customer/order/get-line-item/get-line-item.yaml delete mode 100644 openapi/paths/customer/order/get-line-item/index.yaml delete mode 100644 openapi/paths/customer/order/get-line-item/response.yaml delete mode 100644 openapi/paths/customer/order/get-shipping/index.yaml delete mode 100644 openapi/paths/customer/order/get-shipping/list.yaml delete mode 100644 openapi/paths/customer/order/get-shipping/response.yaml rename openapi/paths/customer/order/{list => range}/index.yaml (82%) rename openapi/paths/customer/order/{list => range}/list.yaml (100%) rename openapi/paths/customer/order/{list => range}/response.yaml (100%) delete mode 100644 openapi/paths/customer/order/shipping/index.yaml delete mode 100644 openapi/paths/customer/order/shipping/list.yaml delete mode 100644 openapi/paths/customer/order/shipping/response.yaml diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 4ff63e23..19cde7c0 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -120,22 +120,14 @@ components: $ref: paths/customer/order/_types/shipping_lines.yaml CustomerOrderFulfillment: $ref: paths/customer/order/_types/fulfillment.yaml - CustomerOrderGetLineItem: - $ref: paths/customer/order/get-line-item/get-line-item.yaml - CustomerOrderGetLineItemResponse: - $ref: paths/customer/order/get-line-item/response.yaml CustomerOrderGet: $ref: paths/customer/order/get/get.yaml CustomerOrderGetResponse: $ref: paths/customer/order/get/response.yaml - CustomerOrderList: - $ref: paths/customer/order/list/list.yaml - CustomerOrderListResponse: - $ref: paths/customer/order/list/response.yaml - CustomerOrderShipping: - $ref: paths/customer/order/shipping/list.yaml - CustomerOrderShippingResponse: - $ref: paths/customer/order/shipping/response.yaml + CustomerBookingRange: + $ref: paths/customer/order/range/list.yaml + CustomerBookingRangeResponse: + $ref: paths/customer/order/range/response.yaml # Customer CustomerProductList: @@ -336,14 +328,10 @@ paths: $ref: "./paths/customer/product/list-ids/index.yaml" /customer/{customerId}/product/{productId}: $ref: "paths/customer/product/product.yaml" - /customer/{customerId}/orders-range: - $ref: "paths/customer/order/list/index.yaml" - /customer/{customerId}/shipping-range: - $ref: "paths/customer/order/shipping/index.yaml" - /customer/{customerId}/lineItem/{lineItemId}: - $ref: "paths/customer/order/get-line-item/index.yaml" /customer/{customerId}/orders/{orderId}: $ref: "paths/customer/order/get/index.yaml" + /customer/{customerId}/orders-range: + $ref: "paths/customer/order/range/index.yaml" # schedule /customer/{customerId}/schedule: diff --git a/openapi/paths/customer/order/_types/order.yaml b/openapi/paths/customer/order/_types/order.yaml index 3cf3f72f..f100f7ff 100644 --- a/openapi/paths/customer/order/_types/order.yaml +++ b/openapi/paths/customer/order/_types/order.yaml @@ -72,6 +72,8 @@ properties: type: string title: type: string + shipping: + $ref: ../../../shipping/_types/shipping.yaml required: - id - admin_graphql_api_id diff --git a/openapi/paths/customer/order/get-line-item/get-line-item.yaml b/openapi/paths/customer/order/get-line-item/get-line-item.yaml deleted file mode 100644 index 886b039f..00000000 --- a/openapi/paths/customer/order/get-line-item/get-line-item.yaml +++ /dev/null @@ -1,15 +0,0 @@ -allOf: - - $ref: "../_types/order.yaml" - - type: object - properties: - line_items: - allOf: - - $ref: ../_types/line-item-with-lookup.yaml - - type: object - properties: - selectedOptions: - $ref: ../../schedule/_types/product-selected-options.yaml - required: - - selectedOptions - required: - - line_items diff --git a/openapi/paths/customer/order/get-line-item/index.yaml b/openapi/paths/customer/order/get-line-item/index.yaml deleted file mode 100644 index d20f198f..00000000 --- a/openapi/paths/customer/order/get-line-item/index.yaml +++ /dev/null @@ -1,37 +0,0 @@ -get: - parameters: - - name: customerId - in: path - description: customerId for the customer - required: true - schema: - type: string - - name: lineItemId - in: path - description: lineItemId for the order - required: true - schema: - type: string - tags: - - CustomerOrder - operationId: customerOrderGetLineItem - summary: GET Get order with lineItem - description: This endpoint gets order with lineItem object - responses: - "200": - description: Response with payload - content: - application/json: - schema: - $ref: "./response.yaml" - - "400": - $ref: "../../../../responses/bad.yaml" - "401": - $ref: "../../../../responses/unauthorized.yaml" - "403": - $ref: "../../../../responses/forbidden.yaml" - "404": - $ref: "../../../../responses/not-found.yaml" - - security: [] diff --git a/openapi/paths/customer/order/get-line-item/response.yaml b/openapi/paths/customer/order/get-line-item/response.yaml deleted file mode 100644 index b74037f8..00000000 --- a/openapi/paths/customer/order/get-line-item/response.yaml +++ /dev/null @@ -1,11 +0,0 @@ -type: object -properties: - success: - type: boolean - example: true - payload: - $ref: get-line-item.yaml - -required: - - success - - payload diff --git a/openapi/paths/customer/order/get-shipping/index.yaml b/openapi/paths/customer/order/get-shipping/index.yaml deleted file mode 100644 index b2dd43d2..00000000 --- a/openapi/paths/customer/order/get-shipping/index.yaml +++ /dev/null @@ -1,36 +0,0 @@ -get: - parameters: - - name: customerId - in: path - description: customerId for the customer - required: true - schema: - type: string - - name: id - in: query - description: id for the order - required: true - schema: - type: string - tags: - - CustomerOrder - operationId: customerOrderGetShipping - summary: GET Get one order destinations for customer - description: Get one driving information that are included in the order - responses: - "200": - description: "Response" - content: - application/json: - schema: - $ref: "./response.yaml" - "400": - $ref: "../../../../responses/bad.yaml" - "401": - $ref: "../../../../responses/unauthorized.yaml" - "403": - $ref: "../../../../responses/forbidden.yaml" - "404": - $ref: "../../../../responses/not-found.yaml" - - security: [] diff --git a/openapi/paths/customer/order/get-shipping/list.yaml b/openapi/paths/customer/order/get-shipping/list.yaml deleted file mode 100644 index 2f16242c..00000000 --- a/openapi/paths/customer/order/get-shipping/list.yaml +++ /dev/null @@ -1,25 +0,0 @@ -type: object -properties: - id: - type: number - order_number: - type: number - customer: - $ref: ../_types/customer.yaml - start: - type: string - end: - type: string - title: - type: string - shipping: - $ref: ../../../shipping/_types/shipping.yaml - -required: - - id - - customer - - start - - end - - title - - order_number - - shipping diff --git a/openapi/paths/customer/order/get-shipping/response.yaml b/openapi/paths/customer/order/get-shipping/response.yaml deleted file mode 100644 index 4d44b2d8..00000000 --- a/openapi/paths/customer/order/get-shipping/response.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: object -properties: - success: - type: boolean - example: true - payload: - $ref: list.yaml -required: - - success - - payload diff --git a/openapi/paths/customer/order/list/index.yaml b/openapi/paths/customer/order/range/index.yaml similarity index 82% rename from openapi/paths/customer/order/list/index.yaml rename to openapi/paths/customer/order/range/index.yaml index 93cb18c7..d6ae4076 100644 --- a/openapi/paths/customer/order/list/index.yaml +++ b/openapi/paths/customer/order/range/index.yaml @@ -19,10 +19,10 @@ get: schema: type: string tags: - - CustomerOrder - operationId: customerOrderList - summary: GET Get all order for customer - description: This endpoint get all orders + - CustomerBooking + operationId: customerBookingRange + summary: GET Get all bookings for customer from orders + description: This endpoint get all bookings from orders responses: "200": description: "Response" diff --git a/openapi/paths/customer/order/list/list.yaml b/openapi/paths/customer/order/range/list.yaml similarity index 100% rename from openapi/paths/customer/order/list/list.yaml rename to openapi/paths/customer/order/range/list.yaml diff --git a/openapi/paths/customer/order/list/response.yaml b/openapi/paths/customer/order/range/response.yaml similarity index 100% rename from openapi/paths/customer/order/list/response.yaml rename to openapi/paths/customer/order/range/response.yaml diff --git a/openapi/paths/customer/order/shipping/index.yaml b/openapi/paths/customer/order/shipping/index.yaml deleted file mode 100644 index 9506bea9..00000000 --- a/openapi/paths/customer/order/shipping/index.yaml +++ /dev/null @@ -1,42 +0,0 @@ -get: - parameters: - - name: customerId - in: path - description: customerId for the customer - required: true - schema: - type: string - - name: start - in: query - description: start of date - required: true - schema: - type: string - - name: end - in: query - description: end of date - required: true - schema: - type: string - tags: - - CustomerOrder - operationId: customerOrderShipping - summary: GET Get only all orders destinations for customer - description: Get all driving information that are included in the orders - responses: - "200": - description: "Response" - content: - application/json: - schema: - $ref: "./response.yaml" - "400": - $ref: "../../../../responses/bad.yaml" - "401": - $ref: "../../../../responses/unauthorized.yaml" - "403": - $ref: "../../../../responses/forbidden.yaml" - "404": - $ref: "../../../../responses/not-found.yaml" - - security: [] diff --git a/openapi/paths/customer/order/shipping/list.yaml b/openapi/paths/customer/order/shipping/list.yaml deleted file mode 100644 index 2f16242c..00000000 --- a/openapi/paths/customer/order/shipping/list.yaml +++ /dev/null @@ -1,25 +0,0 @@ -type: object -properties: - id: - type: number - order_number: - type: number - customer: - $ref: ../_types/customer.yaml - start: - type: string - end: - type: string - title: - type: string - shipping: - $ref: ../../../shipping/_types/shipping.yaml - -required: - - id - - customer - - start - - end - - title - - order_number - - shipping diff --git a/openapi/paths/customer/order/shipping/response.yaml b/openapi/paths/customer/order/shipping/response.yaml deleted file mode 100644 index eb232183..00000000 --- a/openapi/paths/customer/order/shipping/response.yaml +++ /dev/null @@ -1,12 +0,0 @@ -type: object -properties: - success: - type: boolean - example: true - payload: - type: array - items: - $ref: list.yaml -required: - - success - - payload From 7e7cdda2417ad7c4fb86e5c1a4a3566de1368a1f Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Wed, 28 Feb 2024 18:32:41 +0100 Subject: [PATCH 4/6] fix(get.spec.ts): update expected length of line_items and fulfillments to 3 refactor(generate.ts): replace CustomerOrderServiceGetOrdersBookedTimes with UserAvailabilityServiceGetOrders feat(get-orders): add UserAvailabilityServiceGetOrders function feat(get-orders): add UserAvailabilityServiceGetOrders function chore(availability): remove unused file get-shipping-booked-time.ts --- .../customer/services/order/get.spec.ts | 4 +- .../user/services/availability/generate.ts | 15 +- .../availability/get-orders-booked-time.ts | 96 -------- ...booked-time.spec.ts => get-orders.spec.ts} | 20 +- .../user/services/availability/get-orders.ts | 215 ++++++++++++++++++ .../get-shipping-booked-time.spec.ts | 49 ---- .../availability/get-shipping-booked-time.ts | 183 --------------- 7 files changed, 224 insertions(+), 358 deletions(-) delete mode 100644 src/functions/user/services/availability/get-orders-booked-time.ts rename src/functions/user/services/availability/{get-orders-booked-time.spec.ts => get-orders.spec.ts} (52%) create mode 100644 src/functions/user/services/availability/get-orders.ts delete mode 100644 src/functions/user/services/availability/get-shipping-booked-time.spec.ts delete mode 100644 src/functions/user/services/availability/get-shipping-booked-time.ts diff --git a/src/functions/customer/services/order/get.spec.ts b/src/functions/customer/services/order/get.spec.ts index 7cb319b5..3398801b 100644 --- a/src/functions/customer/services/order/get.spec.ts +++ b/src/functions/customer/services/order/get.spec.ts @@ -24,8 +24,8 @@ describe("CustomerOrderServiceGet", () => { orderId, }); - expect(order.line_items.length).toBe(2); - expect(order.fulfillments.length).toBe(2); + expect(order.line_items.length).toBe(3); + expect(order.fulfillments.length).toBe(3); expect(order.refunds.length).toBe(1); }); }); diff --git a/src/functions/user/services/availability/generate.ts b/src/functions/user/services/availability/generate.ts index a93a9139..5cb5a41d 100644 --- a/src/functions/user/services/availability/generate.ts +++ b/src/functions/user/services/availability/generate.ts @@ -8,8 +8,7 @@ import { generateAvailability } from "~/library/availability/generate-availabili import { removeBookedSlots } from "~/library/availability/remove-booked-slots"; import { StringOrObjectId } from "~/library/zod"; import { UserServiceGetCustomerId } from "../user"; -import { CustomerOrderServiceGetOrdersBookedTimes } from "./get-orders-booked-time"; -import { CustomerOrderServiceGetShippingBookedTime } from "./get-shipping-booked-time"; +import { UserAvailabilityServiceGetOrders } from "./get-orders"; export type UserAvailabilityServiceGenerateProps = { username: string; @@ -57,19 +56,11 @@ export const UserAvailabilityServiceGenerate = async ( const date = findStartAndEndDate(availability); - const booked = await CustomerOrderServiceGetOrdersBookedTimes({ + const booked = await UserAvailabilityServiceGetOrders({ customerId: user.customerId, start: date.startDate, end: date.endDate, }); - availability = removeBookedSlots(availability, booked); - - const shippings = await CustomerOrderServiceGetShippingBookedTime({ - customerId: user.customerId, - start: date.startDate, - end: date.endDate, - }); - - return removeBookedSlots(availability, shippings); + return removeBookedSlots(availability, booked); }; diff --git a/src/functions/user/services/availability/get-orders-booked-time.ts b/src/functions/user/services/availability/get-orders-booked-time.ts deleted file mode 100644 index f4db861e..00000000 --- a/src/functions/user/services/availability/get-orders-booked-time.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { OrderModel } from "~/functions/order/order.models"; - -export type CustomerOrderServiceGetOrdersBookedTimesProps = { - customerId: number; - start: Date; - end: Date; -}; - -export type CustomerOrderServiceGetOrdersBookedTimesAggregate = { - from: Date; - to: Date; -}; - -export const CustomerOrderServiceGetOrdersBookedTimes = async ({ - customerId, - start: $gte, - end: $lte, -}: CustomerOrderServiceGetOrdersBookedTimesProps) => { - return OrderModel.aggregate( - [ - { - $match: { - $and: [ - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte, - }, - }, - { - "line_items.properties.to": { - $lte, - }, - }, - ], - }, - ], - }, - }, - { $unwind: "$line_items" }, - { - $match: { - $and: [ - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte, - }, - }, - { - "line_items.properties.to": { - $lte, - }, - }, - ], - }, - ], - }, - }, - { - $addFields: { - from: "$line_items.properties.from", - to: "$line_items.properties.to", - }, - }, - { - $project: { - from: 1, - to: 1, - }, - }, - ] - ); -}; diff --git a/src/functions/user/services/availability/get-orders-booked-time.spec.ts b/src/functions/user/services/availability/get-orders.spec.ts similarity index 52% rename from src/functions/user/services/availability/get-orders-booked-time.spec.ts rename to src/functions/user/services/availability/get-orders.spec.ts index c7083fe8..1a4fe228 100644 --- a/src/functions/user/services/availability/get-orders-booked-time.spec.ts +++ b/src/functions/user/services/availability/get-orders.spec.ts @@ -1,11 +1,11 @@ -import { addDays, isWithinInterval } from "date-fns"; +import { addDays } from "date-fns"; import { OrderModel } from "~/functions/order/order.models"; import { getOrderObject } from "~/library/jest/helpers/order"; -import { CustomerOrderServiceGetOrdersBookedTimes } from "./get-orders-booked-time"; +import { UserAvailabilityServiceGetOrders } from "./get-orders"; require("~/library/jest/mongoose/mongodb.jest"); -describe("CustomerOrderService", () => { +describe("CustomerOrderServiceGet", () => { it("should return booked lineItems from booking", async () => { const customerId = 1; const dumbData = getOrderObject({ customerId, lineItemsTotal: 50 }); @@ -14,24 +14,12 @@ describe("CustomerOrderService", () => { const start = new Date(); const end = addDays(new Date(), 7); - const filteredLineItems = dumbData.line_items.filter((lineItem) => { - const from = new Date(lineItem.properties!.from); - const to = new Date(lineItem.properties!.to); - - return ( - isWithinInterval(from, { start, end }) || - isWithinInterval(to, { start, end }) - ); - }); - - const orders = await CustomerOrderServiceGetOrdersBookedTimes({ + const orders = await UserAvailabilityServiceGetOrders({ customerId, start, end, }); - expect(orders.length).toEqual(filteredLineItems.length); - orders.forEach((order) => { expect(order.from.getTime()).toBeGreaterThanOrEqual(start.getTime()); expect(order.to.getTime()).toBeLessThanOrEqual(end.getTime()); diff --git a/src/functions/user/services/availability/get-orders.ts b/src/functions/user/services/availability/get-orders.ts new file mode 100644 index 00000000..bc72a374 --- /dev/null +++ b/src/functions/user/services/availability/get-orders.ts @@ -0,0 +1,215 @@ +import { OrderModel } from "~/functions/order/order.models"; + +export type UserAvailabilityServiceGetOrdersProps = { + customerId: number; + start: Date; + end: Date; +}; + +export type UserAvailabilityServiceGetOrdersAggregate = { + from: Date; + to: Date; +}; + +export const UserAvailabilityServiceGetOrders = async ({ + customerId, + start: $gte, + end: $lte, +}: UserAvailabilityServiceGetOrdersProps) => { + return OrderModel.aggregate([ + { + $match: { + $and: [ + { + $or: [ + { + "line_items.properties.customerId": customerId, + }, + { + "customer.id": customerId, + }, + ], + }, + { + $and: [ + { + "line_items.properties.from": { + $gte, + }, + }, + { + "line_items.properties.to": { + $lte, + }, + }, + ], + }, + ], + }, + }, + { $unwind: "$line_items" }, + { + $match: { + $and: [ + { + $or: [ + { + "line_items.properties.customerId": customerId, + }, + { + "customer.id": customerId, + }, + ], + }, + { + $and: [ + { + "line_items.properties.from": { + $gte, + }, + }, + { + "line_items.properties.to": { + $lte, + }, + }, + ], + }, + ], + }, + }, + { + $addFields: { + refunds: { + $filter: { + input: "$refunds", + as: "refund", + cond: { + $anyElementTrue: { + $map: { + input: "$$refund.refund_line_items", + as: "refund_line_item", + in: { + $eq: ["$$refund_line_item.line_item_id", "$line_items.id"], + }, + }, + }, + }, + }, + }, + fulfillments: { + $filter: { + input: "$fulfillments", + as: "fulfillment", + cond: { + $anyElementTrue: { + $map: { + input: "$$fulfillment.line_items", + as: "fulfillment_line_item", + in: { + $eq: ["$$fulfillment_line_item.id", "$line_items.id"], + }, + }, + }, + }, + }, + }, + }, + }, + { + $sort: { + "line_items.properties.groupId": 1, + "line_items.properties.from": 1, + }, + }, + { + $group: { + _id: "$line_items.properties.groupId", + line_items: { $push: "$line_items" }, + orderNumber: { $first: "$order_number" }, + fulfillmentsArray: { $push: "$fulfillments" }, + refundsArray: { $push: "$refunds" }, + }, + }, + { + $match: { + refundsArray: { $size: 0 }, + }, + }, + { + $addFields: { + shippingId: { $first: "$line_items.properties.shippingId" }, + end: { $last: "$line_items.properties.to" }, + start: { $first: "$line_items.properties.from" }, + }, + }, + { + $lookup: { + from: "Shipping", + let: { shippingId: "$shippingId" }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { + $eq: ["$_id", { $toObjectId: "$$shippingId" }], + }, + ], + }, + }, + }, + { + $project: { + duration: 1, + }, + }, + ], + as: "shipping", + }, + }, + { + $unwind: { + path: "$shipping", + preserveNullAndEmptyArrays: true, + }, + }, + { + $addFields: { + start: { + $cond: { + if: { $gt: ["$shipping.duration.value", 0] }, + then: { + $dateSubtract: { + startDate: "$start", + unit: "minute", + amount: { $toInt: "$shipping.duration.value" }, + }, + }, + else: "$start", + }, + }, + end: { + $cond: { + if: { $gt: ["$shipping.duration.value", 0] }, + then: { + $dateAdd: { + startDate: "$end", + unit: "minute", + amount: { $toInt: "$shipping.duration.value" }, + }, + }, + else: "$end", + }, + }, + }, + }, + { + $project: { + id: "$_id", + start: 1, + end: 1, + }, + }, + ]); +}; diff --git a/src/functions/user/services/availability/get-shipping-booked-time.spec.ts b/src/functions/user/services/availability/get-shipping-booked-time.spec.ts deleted file mode 100644 index 8217e5c0..00000000 --- a/src/functions/user/services/availability/get-shipping-booked-time.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { addMinutes, subMinutes } from "date-fns"; -import { OrderModel } from "~/functions/order/order.models"; -import { Order } from "~/functions/order/order.types"; -import { orderWithShipping } from "~/functions/webhook/data-order-with-shipping"; -import { createUser } from "~/library/jest/helpers"; -import { createLocation } from "~/library/jest/helpers/location"; -import { createShipping } from "~/library/jest/helpers/shipping"; -import { CustomerOrderServiceGetShippingBookedTime } from "./get-shipping-booked-time"; - -require("~/library/jest/mongoose/mongodb.jest"); - -describe("CustomerOrderServiceGetShippingBookedTime", () => { - it("should return shipping orders for customer on range of start/end", async () => { - const customerId = 7106990342471; - const user = await createUser({ customerId: 7106990342471 }); - const location = await createLocation({ customerId: user.customerId }); - const shipping1 = await createShipping({ location: location.id }); - - const dumbData = Order.parse(orderWithShipping); - - dumbData.line_items[0].properties!.customerId = customerId; - dumbData.line_items[0].properties!.locationId = location._id.toString(); - dumbData.line_items[0].properties!.shippingId = shipping1._id.toString(); - dumbData.line_items[1].properties!.customerId = customerId; - dumbData.line_items[1].properties!.locationId = location._id.toString(); - dumbData.line_items[1].properties!.shippingId = shipping1._id.toString(); - - await OrderModel.create(dumbData); - - const orders = await CustomerOrderServiceGetShippingBookedTime({ - customerId: customerId, - start: "2023-11-26T00:00:00+03:00", - end: "2024-01-07T00:00:00+03:00", - }); - - const start = subMinutes( - new Date(dumbData.line_items[0].properties!.from), - orders[0].shipping.duration.value - ); - - const end = addMinutes( - new Date(dumbData.line_items[1].properties!.to), - orders[0].shipping.duration.value - ); - - expect(orders[0].from).toEqual(start); - expect(orders[0].to).toEqual(end); - }); -}); diff --git a/src/functions/user/services/availability/get-shipping-booked-time.ts b/src/functions/user/services/availability/get-shipping-booked-time.ts deleted file mode 100644 index 63cd74a1..00000000 --- a/src/functions/user/services/availability/get-shipping-booked-time.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { addMinutes, subMinutes } from "date-fns"; -import { OrderModel } from "~/functions/order/order.models"; -import { Shipping } from "~/functions/shipping/shipping.types"; - -export type CustomerOrderServiceGetShippingBookedTimeProps = { - customerId: number; - start: string | Date; - end: string | Date; -}; - -export type CustomerOrderServiceGetShippingBookedTimeAggregate = { - from: Date; - to: Date; - shipping: Shipping; -}; - -export const CustomerOrderServiceGetShippingBookedTime = async ({ - customerId, - start, - end, -}: CustomerOrderServiceGetShippingBookedTimeProps) => { - const startDate = new Date(start); - const endDate = new Date(end); - - const orders = - await OrderModel.aggregate( - [ - { - $match: { - $and: [ - { "line_items.properties.shippingId": { $exists: true } }, - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte: startDate, - }, - }, - { - "line_items.properties.to": { - $lte: endDate, - }, - }, - ], - }, - ], - }, - }, - { $unwind: "$line_items" }, - { - $match: { - $and: [ - { "line_items.properties.shippingId": { $exists: true } }, - { - $or: [ - { - "line_items.properties.customerId": customerId, - }, - { - "customer.id": customerId, - }, - ], - }, - { - $and: [ - { - "line_items.properties.from": { - $gte: startDate, - }, - }, - { - "line_items.properties.to": { - $lte: endDate, - }, - }, - ], - }, - ], - }, - }, - { - $sort: { - "line_items.properties.shippingId": 1, - "line_items.properties.from": 1, - }, - }, - { - $addFields: { - shippingId: "$line_items.properties.shippingId", - title: "$line_items.title", - refunds: { - $filter: { - input: "$refunds", - as: "refund", - cond: { - $anyElementTrue: { - $map: { - input: "$$refund.refund_line_items", - as: "refund_line_item", - in: { - $eq: [ - "$$refund_line_item.line_item_id", - "$line_items.id", - ], - }, - }, - }, - }, - }, - }, - }, - }, - { - $match: { - refunds: { $size: 0 }, - }, - }, - { - $lookup: { - from: "Shipping", - let: { shippingId: "$line_items.properties.shippingId" }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { - $eq: ["$_id", { $toObjectId: "$$shippingId" }], - }, - ], - }, - }, - }, - { - $project: { - duration: 1, - }, - }, - ], - as: "shipping", - }, - }, - { - $unwind: { - path: "$shipping", - preserveNullAndEmptyArrays: true, - }, - }, - { - $group: { - _id: "$shipping._id", - shipping: { $first: "$shipping" }, - id: { $first: "$id" }, - from: { $first: "$line_items.properties.from" }, - to: { $last: "$line_items.properties.to" }, - }, - }, - { - $project: { - id: 1, - from: 1, - to: 1, - }, - }, - ] - ); - - return orders.map((order) => { - order.from = subMinutes(order.from, order.shipping.duration.value); - order.to = addMinutes(order.to, order.shipping.duration.value); - return order; - }); -}; From 61895032910de25855d4c1a0b2621a742f769f7f Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Wed, 28 Feb 2024 21:38:27 +0100 Subject: [PATCH 5/6] fix(generate.spec.ts): add import statement for createShipping function fix(generate.spec.ts): update targetToTime calculation to add 30 minutes fix(generate.spec.ts): remove unnecessary response variable assignment fix(generate.spec.ts): update slotExists check to use targetFromTime and targetToTime feat(generate.spec.ts): add test case for handling shipping time in available slots fix(get-orders.spec.ts): update property names in order object assertions fix(get-orders.ts): update property names in UserAvailabilityServiceGetOrdersAggregate fix(get-orders.ts): update $and to $or in MongoDB query conditions fix(remove-booked-slots.ts): refactor removeBookedSlots function to handle overlapping intervals fix(shipping.ts): set shipping duration from filter or default value --- .../services/availability/generate.spec.ts | 76 ++++++++++++++++++- .../services/availability/get-orders.spec.ts | 4 +- .../user/services/availability/get-orders.ts | 13 +--- .../availability/remove-booked-slots.ts | 39 +++++----- src/library/jest/helpers/shipping.ts | 2 +- 5 files changed, 99 insertions(+), 35 deletions(-) diff --git a/src/functions/user/services/availability/generate.spec.ts b/src/functions/user/services/availability/generate.spec.ts index 8b8b7f84..b10efa18 100644 --- a/src/functions/user/services/availability/generate.spec.ts +++ b/src/functions/user/services/availability/generate.spec.ts @@ -1,5 +1,6 @@ import { addDays, + addMinutes, differenceInMinutes, format, isWithinInterval, @@ -18,6 +19,7 @@ import { } from "~/library/jest/helpers/location"; import { getOrderObject } from "~/library/jest/helpers/order"; import { createSchedule } from "~/library/jest/helpers/schedule"; +import { createShipping } from "~/library/jest/helpers/shipping"; import { UserAvailabilityServiceGenerate } from "./generate"; require("~/library/jest/mongoose/mongodb.jest"); @@ -183,23 +185,24 @@ describe("UserAvailabilityServiceGenerate", () => { (product) => product.productId ); + const fromDate = new Date().toISOString(); let result = await UserAvailabilityServiceGenerate( { username: user.username!, locationId: locationOrigin._id, }, - { productIds, fromDate: new Date().toISOString() } + { productIds, fromDate } ); const targetFromTime = result[0].slots[0].from; - const targetToTime = result[0].slots[0].to; + const targetToTime = addMinutes(targetFromTime, 30); const dumbData = getOrderObject({ customerId, lineItemsTotal: 1 }); dumbData.line_items[0].properties!.customerId = customerId; dumbData.line_items[0].properties!.from = targetFromTime; dumbData.line_items[0].properties!.to = targetToTime; - const response = await OrderModel.create(dumbData); + await OrderModel.create(dumbData); result = await UserAvailabilityServiceGenerate( { @@ -208,7 +211,72 @@ describe("UserAvailabilityServiceGenerate", () => { }, { productIds, - fromDate: new Date().toISOString(), + fromDate, + } + ); + + const availabilityForDate = result.find( + (a) => + a.date.getFullYear() === targetFromTime.getFullYear() && + a.date.getMonth() === targetFromTime.getMonth() && + a.date.getDate() === targetFromTime.getDate() + ); + + const slotExists = availabilityForDate?.slots.some((slot) => + slot.products.some( + (product) => + product.from.getTime() >= targetFromTime.getTime() && + product.to.getTime() <= targetToTime.getTime() + ) + ); + + expect(slotExists).toBeFalsy(); + }); + + it("should return available slots and handle shipping time", async () => { + const productIds = arrayElements(schedule.products, 2).map( + (product) => product.productId + ); + + const location = await createLocation({ customerId: user.customerId }); + const shipping = await createShipping({ + location: location.id, + duration: { text: "10 minutes", value: 10 }, + }); + + const fromDate = new Date().toISOString(); + let result = await UserAvailabilityServiceGenerate( + { + username: user.username!, + locationId: locationOrigin._id, + }, + { productIds, fromDate } + ); + + const targetFromTime = result[0].slots[0].from; + const targetToTime = addMinutes(targetFromTime, 15); + + const dumbData = getOrderObject({ customerId, lineItemsTotal: 2 }); + dumbData.line_items[0].properties!.customerId = customerId; + dumbData.line_items[0].properties!.from = targetFromTime; + dumbData.line_items[0].properties!.to = targetToTime; + dumbData.line_items[0].properties!.shippingId = shipping._id.toString(); + + dumbData.line_items[1].properties!.customerId = customerId; + dumbData.line_items[1].properties!.from = targetToTime; + dumbData.line_items[1].properties!.to = addMinutes(targetToTime, 30); + dumbData.line_items[1].properties!.shippingId = shipping._id.toString(); + + const order = await OrderModel.create(dumbData); + + result = await UserAvailabilityServiceGenerate( + { + username: user.username!, + locationId: locationOrigin._id, + }, + { + productIds, + fromDate, } ); diff --git a/src/functions/user/services/availability/get-orders.spec.ts b/src/functions/user/services/availability/get-orders.spec.ts index 1a4fe228..26097d50 100644 --- a/src/functions/user/services/availability/get-orders.spec.ts +++ b/src/functions/user/services/availability/get-orders.spec.ts @@ -21,8 +21,8 @@ describe("CustomerOrderServiceGet", () => { }); orders.forEach((order) => { - expect(order.from.getTime()).toBeGreaterThanOrEqual(start.getTime()); - expect(order.to.getTime()).toBeLessThanOrEqual(end.getTime()); + expect(order.start.getTime()).toBeGreaterThanOrEqual(start.getTime()); + expect(order.end.getTime()).toBeLessThanOrEqual(end.getTime()); }); }); }); diff --git a/src/functions/user/services/availability/get-orders.ts b/src/functions/user/services/availability/get-orders.ts index bc72a374..4818f757 100644 --- a/src/functions/user/services/availability/get-orders.ts +++ b/src/functions/user/services/availability/get-orders.ts @@ -7,8 +7,8 @@ export type UserAvailabilityServiceGetOrdersProps = { }; export type UserAvailabilityServiceGetOrdersAggregate = { - from: Date; - to: Date; + start: Date; + end: Date; }; export const UserAvailabilityServiceGetOrders = async ({ @@ -31,7 +31,7 @@ export const UserAvailabilityServiceGetOrders = async ({ ], }, { - $and: [ + $or: [ { "line_items.properties.from": { $gte, @@ -62,7 +62,7 @@ export const UserAvailabilityServiceGetOrders = async ({ ], }, { - $and: [ + $or: [ { "line_items.properties.from": { $gte, @@ -131,11 +131,6 @@ export const UserAvailabilityServiceGetOrders = async ({ refundsArray: { $push: "$refunds" }, }, }, - { - $match: { - refundsArray: { $size: 0 }, - }, - }, { $addFields: { shippingId: { $first: "$line_items.properties.shippingId" }, diff --git a/src/library/availability/remove-booked-slots.ts b/src/library/availability/remove-booked-slots.ts index 7400c5f0..cc771a17 100644 --- a/src/library/availability/remove-booked-slots.ts +++ b/src/library/availability/remove-booked-slots.ts @@ -1,29 +1,30 @@ -import { isAfter, isBefore, isEqual } from "date-fns"; +import { areIntervalsOverlapping } from "date-fns"; import { Availability } from "~/functions/availability"; -type DateInterval = { from: Date; to: Date }; +type DateInterval = { start: Date; end: Date }; export const removeBookedSlots = ( availability: Availability[], bookedSlots: DateInterval[] ) => { - const isOverlap = (slot1: DateInterval, slot2: DateInterval) => { - return ( - ((isAfter(slot1.from, slot2.from) || isEqual(slot1.from, slot2.from)) && - isBefore(slot1.from, slot2.to)) || - ((isAfter(slot2.from, slot1.from) || isEqual(slot2.from, slot1.from)) && - isBefore(slot2.from, slot1.to)) - ); - }; - - for (const bookedSlot of bookedSlots) { - for (const availabilityDay of availability) { - availabilityDay.slots = availabilityDay.slots.filter((slot) => { - return !isOverlap(slot, bookedSlot); - }); - } + if (bookedSlots.length === 0) { + return availability; } - // Filter out days without slots - return availability.filter((day) => day.slots.length > 0); + return availability.map((avail) => ({ + ...avail, + slots: avail.slots.filter((slot) => { + const slotInterval = { + start: slot.from, + end: slot.to, + }; + + const isSlotOverlapping = bookedSlots.some((booked) => { + return areIntervalsOverlapping(slotInterval, booked, { + inclusive: true, + }); + }); + return !isSlotOverlapping; + }), + })); }; diff --git a/src/library/jest/helpers/shipping.ts b/src/library/jest/helpers/shipping.ts index 071dfc09..cc1d14d1 100644 --- a/src/library/jest/helpers/shipping.ts +++ b/src/library/jest/helpers/shipping.ts @@ -34,7 +34,7 @@ export const createShipping = (filter: Partial) => { name: faker.company.buzzPhrase(), fullAddress: faker.location.streetAddress(), }; - shipping.duration = { + shipping.duration = filter.duration || { text: "2 hours 59 mins", value: 179.31666666666666, }; From 8c4489d1b7caa8b90f15ee2d00b60a68bed47a3b Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Thu, 29 Feb 2024 06:45:04 +0100 Subject: [PATCH 6/6] refactor(range.ts): rename start and end parameters to startDate and endDate fix(range.ts): update variable names to match parameter names for clarity fix(range.ts): correct comparison operators for date ranges fix(range.ts): update index names in order schema refactor(get-orders.spec.ts): use line item properties for start and end dates fix(get-orders.ts): update parameter names for start and end dates fix(get-orders.ts): correct comparison operators for date ranges fix(get-orders.ts): ensure correct field names are used for date comparisons fix(remove-booked-slots.spec.ts): update variable names for clarity refactor(order.ts): import addMinutes function for generating random date range --- .../customer/services/order/range.ts | 24 ++++++++++------- src/functions/order/order.schema.ts | 27 ------------------- .../services/availability/get-orders.spec.ts | 27 ++++++++++++++----- .../user/services/availability/get-orders.ts | 17 +++++++----- .../availability/remove-booked-slots.spec.ts | 8 +++--- src/library/jest/helpers/order.ts | 8 +++--- 6 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/functions/customer/services/order/range.ts b/src/functions/customer/services/order/range.ts index bd1bd0c2..10fdcd0c 100644 --- a/src/functions/customer/services/order/range.ts +++ b/src/functions/customer/services/order/range.ts @@ -33,11 +33,11 @@ export type CustomerOrderServiceRangeAggregate = Omit< export const CustomerOrderServiceRange = async ({ customerId, - start, - end, + start: startDate, + end: endDate, }: CustomerOrderServiceRangeProps) => { - const startDate = new Date(start); - const endDate = new Date(end); + const start = new Date(startDate); + const end = new Date(endDate); return OrderModel.aggregate([ { @@ -54,15 +54,17 @@ export const CustomerOrderServiceRange = async ({ ], }, { - $and: [ + $or: [ { "line_items.properties.from": { - $gte: startDate, + $gte: start, + $lte: end, }, }, { "line_items.properties.to": { - $lte: endDate, + $gte: start, + $lte: end, }, }, ], @@ -85,15 +87,17 @@ export const CustomerOrderServiceRange = async ({ ], }, { - $and: [ + $or: [ { "line_items.properties.from": { - $gte: startDate, + $gte: start, + $lte: end, }, }, { "line_items.properties.to": { - $lte: endDate, + $gte: start, + $lte: end, }, }, ], diff --git a/src/functions/order/order.schema.ts b/src/functions/order/order.schema.ts index 1673682e..2d7a9991 100644 --- a/src/functions/order/order.schema.ts +++ b/src/functions/order/order.schema.ts @@ -336,30 +336,3 @@ export const OrdreMongooseSchema = new Schema({ shipping_address: AddressSchema, shipping_lines: [ShippingLineSchema], }); - -// orders/get-line-item -OrdreMongooseSchema.index( - { "customer.id": 1, "line_items.id": 1 }, - { unique: false } -); -OrdreMongooseSchema.index( - { "line_items.properties.customerId": 1, "line_items.id": 1 }, - { unique: false } -); - -// orders/get -OrdreMongooseSchema.index({ "customer.id": 1, id: 1 }, { unique: false }); -OrdreMongooseSchema.index( - { "line_items.properties.customerId": 1, id: 1 }, - { unique: false } -); - -// order/list -OrdreMongooseSchema.index( - { "customer.id": 1, "line_items.properties.from": 1 }, - { unique: false } -); -OrdreMongooseSchema.index( - { "line_items.properties.customerId": 1, "line_items.properties.from": 1 }, - { unique: false } -); diff --git a/src/functions/user/services/availability/get-orders.spec.ts b/src/functions/user/services/availability/get-orders.spec.ts index 26097d50..ee05e0b2 100644 --- a/src/functions/user/services/availability/get-orders.spec.ts +++ b/src/functions/user/services/availability/get-orders.spec.ts @@ -1,4 +1,4 @@ -import { addDays } from "date-fns"; +import { isWithinInterval } from "date-fns"; import { OrderModel } from "~/functions/order/order.models"; import { getOrderObject } from "~/library/jest/helpers/order"; import { UserAvailabilityServiceGetOrders } from "./get-orders"; @@ -8,11 +8,12 @@ require("~/library/jest/mongoose/mongodb.jest"); describe("CustomerOrderServiceGet", () => { it("should return booked lineItems from booking", async () => { const customerId = 1; - const dumbData = getOrderObject({ customerId, lineItemsTotal: 50 }); + const dumbData = getOrderObject({ customerId, lineItemsTotal: 10 }); await OrderModel.create(dumbData); - const start = new Date(); - const end = addDays(new Date(), 7); + const start = dumbData.line_items[0].properties!.from; + const end = + dumbData.line_items[dumbData.line_items.length - 1].properties!.to; const orders = await UserAvailabilityServiceGetOrders({ customerId, @@ -21,8 +22,22 @@ describe("CustomerOrderServiceGet", () => { }); orders.forEach((order) => { - expect(order.start.getTime()).toBeGreaterThanOrEqual(start.getTime()); - expect(order.end.getTime()).toBeLessThanOrEqual(end.getTime()); + const isStartWithinInterval = isWithinInterval(order.start, { + start: start, + end: end, + }); + const isEndWithinInterval = isWithinInterval(order.end, { + start: start, + end: end, + }); + + console.log( + order.start, + isStartWithinInterval, + order.end, + isEndWithinInterval + ); + expect(isStartWithinInterval || isEndWithinInterval).toBeTruthy(); }); }); }); diff --git a/src/functions/user/services/availability/get-orders.ts b/src/functions/user/services/availability/get-orders.ts index 4818f757..3f20cfc2 100644 --- a/src/functions/user/services/availability/get-orders.ts +++ b/src/functions/user/services/availability/get-orders.ts @@ -13,8 +13,8 @@ export type UserAvailabilityServiceGetOrdersAggregate = { export const UserAvailabilityServiceGetOrders = async ({ customerId, - start: $gte, - end: $lte, + start, + end, }: UserAvailabilityServiceGetOrdersProps) => { return OrderModel.aggregate([ { @@ -34,12 +34,14 @@ export const UserAvailabilityServiceGetOrders = async ({ $or: [ { "line_items.properties.from": { - $gte, + $gte: start, + $lte: end, }, }, { "line_items.properties.to": { - $lte, + $gte: start, + $lte: end, }, }, ], @@ -65,12 +67,14 @@ export const UserAvailabilityServiceGetOrders = async ({ $or: [ { "line_items.properties.from": { - $gte, + $gte: start, + $lte: end, }, }, { "line_items.properties.to": { - $lte, + $gte: start, + $lte: end, }, }, ], @@ -204,6 +208,7 @@ export const UserAvailabilityServiceGetOrders = async ({ id: "$_id", start: 1, end: 1, + line_items: 1, }, }, ]); diff --git a/src/library/availability/remove-booked-slots.spec.ts b/src/library/availability/remove-booked-slots.spec.ts index 5dfb79c2..0507da66 100644 --- a/src/library/availability/remove-booked-slots.spec.ts +++ b/src/library/availability/remove-booked-slots.spec.ts @@ -32,12 +32,12 @@ describe("removeBookedSlots", () => { const bookedSlots = [ { - from: new Date("2023-05-15T10:00:00.000Z"), - to: new Date("2023-05-15T11:00:00.000Z"), + start: new Date("2023-05-15T10:00:00.000Z"), + end: new Date("2023-05-15T11:00:00.000Z"), }, { - from: new Date("2023-05-15T17:00:00.000Z"), - to: new Date("2023-05-15T18:00:00.000Z"), + start: new Date("2023-05-15T17:00:00.000Z"), + end: new Date("2023-05-15T18:00:00.000Z"), }, ]; diff --git a/src/library/jest/helpers/order.ts b/src/library/jest/helpers/order.ts index c7a9ab25..2e4a4d88 100644 --- a/src/library/jest/helpers/order.ts +++ b/src/library/jest/helpers/order.ts @@ -1,4 +1,5 @@ import { faker } from "@faker-js/faker"; +import { addMinutes } from "date-fns"; import { Order } from "~/functions/order/order.types"; export const getOrderObject = ({ @@ -29,7 +30,7 @@ export const getOrderObject = ({ properties: { ...generateRandomDateRange(), customerId, - groupId: "12", + groupId: faker.number.int({ min: 1, max: 1000000000 }).toString(), locationId: "1", }, quantity: 1, @@ -274,8 +275,9 @@ const generateRandomDateRange = () => { const randomDate = faker.date.between({ from, to }); // Add a random duration between 30 and 60 minutes to the random date - const endDate = new Date( - randomDate.getTime() + faker.number.int({ min: 30, max: 60 }) * 60000 + const endDate = addMinutes( + randomDate, + faker.number.int({ min: 30, max: 60 }) ); return {