diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json
index df15ba7f77..c5e9498a8c 100644
--- a/packages/admin/dashboard/src/i18n/translations/$schema.json
+++ b/packages/admin/dashboard/src/i18n/translations/$schema.json
@@ -4806,6 +4806,9 @@
"markAsShipped": {
"type": "string"
},
+ "markAsPickedUp": {
+ "type": "string"
+ },
"markAsDelivered": {
"type": "string"
},
@@ -4915,13 +4918,17 @@
},
"fulfillmentDelivered": {
"type": "string"
+ },
+ "fulfillmentPickedUp": {
+ "type": "string"
}
},
"required": [
"created",
"canceled",
"fulfillmentShipped",
- "fulfillmentDelivered"
+ "fulfillmentDelivered",
+ "fulfillmentPickedUp"
],
"additionalProperties": false
},
@@ -4952,6 +4959,7 @@
"available",
"inStock",
"markAsShipped",
+ "markAsPickedUp",
"markAsDelivered",
"itemsToFulfillDesc",
"locationDescription",
@@ -5754,6 +5762,23 @@
],
"additionalProperties": false
},
+ "pickupOptions": {
+ "type": "object",
+ "properties": {
+ "edit": {
+ "type": "object",
+ "properties": {
+ "header": {
+ "type": "string"
+ }
+ },
+ "required": ["header"],
+ "additionalProperties": false
+ }
+ },
+ "required": ["edit"],
+ "additionalProperties": false
+ },
"shippingOptions": {
"type": "object",
"properties": {
@@ -5779,6 +5804,25 @@
"required": ["header", "hint", "label", "successToast"],
"additionalProperties": false
},
+ "pickup": {
+ "type": "object",
+ "properties": {
+ "header": {
+ "type": "string"
+ },
+ "hint": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ },
+ "successToast": {
+ "type": "string"
+ }
+ },
+ "required": ["header", "hint", "label", "successToast"],
+ "additionalProperties": false
+ },
"returns": {
"type": "object",
"properties": {
@@ -5815,7 +5859,7 @@
"type": "string"
}
},
- "required": ["shipping", "returns", "tabs", "action"],
+ "required": ["shipping", "pickup", "returns", "tabs", "action"],
"additionalProperties": false
},
"delete": {
@@ -5996,6 +6040,12 @@
"shipping_other": {
"type": "string"
},
+ "pickup_one": {
+ "type": "string"
+ },
+ "pickup_other": {
+ "type": "string"
+ },
"returns_one": {
"type": "string"
},
@@ -6006,6 +6056,8 @@
"required": [
"shipping_one",
"shipping_other",
+ "pickup_one",
+ "pickup_other",
"returns_one",
"returns_other"
],
@@ -6201,6 +6253,7 @@
"fulfillmentSets",
"sidebar",
"salesChannels",
+ "pickupOptions",
"shippingOptions",
"serviceZones"
],
diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json
index e0c44e373a..716c5fea25 100644
--- a/packages/admin/dashboard/src/i18n/translations/en.json
+++ b/packages/admin/dashboard/src/i18n/translations/en.json
@@ -1281,6 +1281,7 @@
"available": "Available",
"inStock": "In stock",
"markAsShipped": "Mark as shipped",
+ "markAsPickedUp": "Mark as picked up",
"markAsDelivered": "Mark as delivered",
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
"locationDescription": "Choose which location you want to fulfill items from.",
@@ -1310,7 +1311,8 @@
"created": "Fulfillment created successfully",
"canceled": "Fulfillment successfully canceled",
"fulfillmentShipped": "Cannot cancel an already shipped fulfillment",
- "fulfillmentDelivered": "Fulfillment marked as delivered successfully"
+ "fulfillmentDelivered": "Fulfillment marked as delivered successfully",
+ "fulfillmentPickedUp": "Fulfillment marked as picked up successfully"
},
"trackingLabel": "Tracking",
"shippingFromLabel": "Shipping from",
@@ -1520,6 +1522,11 @@
"action": "Connect sales channels",
"successToast": "Sales channels were successfully updated."
},
+ "pickupOptions": {
+ "edit": {
+ "header": "Edit Pickup Option"
+ }
+ },
"shippingOptions": {
"create": {
"shipping": {
@@ -1528,6 +1535,12 @@
"label": "Shipping options",
"successToast": "Shipping option {{name}} was successfully created."
},
+ "pickup": {
+ "header": "Create Pickup Option for {{zone}}",
+ "hint": "Create a new pickup option to define how products are picked up from this location.",
+ "label": "Pickup options",
+ "successToast": "Pickup option {{name}} was successfully created."
+ },
"returns": {
"header": "Create a Return Option for {{zone}}",
"hint": "Create a new return option to define how products are returned to this location.",
@@ -1591,6 +1604,8 @@
"count": {
"shipping_one": "{{count}} shipping option",
"shipping_other": "{{count}} shipping options",
+ "pickup_one": "{{count}} pickup option",
+ "pickup_other": "{{count}} pickup options",
"returns_one": "{{count}} return option",
"returns_other": "{{count}} return options"
},
diff --git a/packages/admin/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx b/packages/admin/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx
index 8927b8693e..6b6507f5cf 100644
--- a/packages/admin/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx
+++ b/packages/admin/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx
@@ -195,12 +195,14 @@ type ServiceZoneOptionsProps = {
zone: HttpTypes.AdminServiceZone
locationId: string
fulfillmentSetId: string
+ type: FulfillmentSetType
}
function ServiceZoneOptions({
zone,
locationId,
fulfillmentSetId,
+ type,
}: ServiceZoneOptionsProps) {
const { t } = useTranslation()
@@ -216,7 +218,7 @@ function ServiceZoneOptions({
- {t("stockLocations.shippingOptions.create.shipping.label")}
+ {t(`stockLocations.shippingOptions.create.${type}.label`)}
ยท
- {t("stockLocations.shippingOptions.fields.count.shipping", {
+ {t(`stockLocations.shippingOptions.fields.count.${type}`, {
count: shippingOptionsCount,
})}
@@ -427,6 +435,7 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
)}
@@ -570,6 +579,7 @@ function FulfillmentSet(props: FulfillmentSetProps) {
{fulfillmentSet?.service_zones.map((zone) => (
{
const { t } = useTranslation()
+ const isPickup = type === FulfillmentSetType.Pickup
+
const shippingProfiles = useComboboxData({
queryFn: (params) => sdk.admin.shippingProfile.list(params),
queryKey: ["shipping_profiles"],
@@ -63,7 +70,7 @@ export const CreateShippingOptionDetailsForm = ({
{t(
`stockLocations.shippingOptions.create.${
- isReturn ? "returns" : "shipping"
+ isPickup ? "pickup" : isReturn ? "returns" : "shipping"
}.header`,
{
zone: zone.name,
@@ -73,54 +80,56 @@ export const CreateShippingOptionDetailsForm = ({
{t(
`stockLocations.shippingOptions.create.${
- isReturn ? "returns" : "shipping"
+ isReturn ? "returns" : isPickup ? "pickup" : "shipping"
}.hint`
)}
-
{
- return (
-
-
- {t("stockLocations.shippingOptions.fields.priceType.label")}
-
-
-
-
-
-
-
-
-
- )
- }}
- />
+ {!isPickup && (
+ {
+ return (
+
+
+ {t("stockLocations.shippingOptions.fields.priceType.label")}
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+ )}
-
-
-
+ {!isPickup && (
+ <>
+
+
+ >
+ )}
)
diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx
index 07dc491ddf..902faf8e22 100644
--- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx
+++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx
@@ -12,7 +12,10 @@ import {
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useCreateShippingOptions } from "../../../../../hooks/api/shipping-options"
import { castNumber } from "../../../../../lib/cast-number"
-import { ShippingOptionPriceType } from "../../../common/constants"
+import {
+ FulfillmentSetType,
+ ShippingOptionPriceType,
+} from "../../../common/constants"
import { buildShippingOptionPriceRules } from "../../../common/utils/price-rule-helpers"
import { CreateShippingOptionDetailsForm } from "./create-shipping-option-details-form"
import { CreateShippingOptionsPricesForm } from "./create-shipping-options-prices-form"
@@ -31,12 +34,14 @@ type CreateShippingOptionFormProps = {
zone: HttpTypes.AdminServiceZone
locationId: string
isReturn?: boolean
+ type: FulfillmentSetType
}
export function CreateShippingOptionsForm({
zone,
isReturn,
locationId,
+ type,
}: CreateShippingOptionFormProps) {
const [activeTab, setActiveTab] = useState(Tab.DETAILS)
const [validDetails, setValidDetails] = useState(false)
@@ -309,13 +314,14 @@ export function CreateShippingOptionsForm({
form={form}
zone={zone}
isReturn={isReturn}
+ type={type}
locationId={locationId}
fulfillmentProviderOptions={fulfillmentProviderOptions || []}
selectedProviderId={selectedProviderId}
/>
-
+
diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-prices-form.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-prices-form.tsx
index 6d00a42c42..2840c094f3 100644
--- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-prices-form.tsx
+++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-prices-form.tsx
@@ -1,4 +1,4 @@
-import { useMemo, useState } from "react"
+import { useEffect, useMemo, useState } from "react"
import { UseFormReturn, useWatch } from "react-hook-form"
import { DataGrid } from "../../../../../components/data-grid"
@@ -12,18 +12,24 @@ import { useRegions } from "../../../../../hooks/api/regions"
import { useStore } from "../../../../../hooks/api/store"
import { ConditionalPriceForm } from "../../../common/components/conditional-price-form"
import { ShippingOptionPriceProvider } from "../../../common/components/shipping-option-price-provider"
-import { CONDITIONAL_PRICES_STACKED_MODAL_ID } from "../../../common/constants"
+import {
+ FulfillmentSetType,
+ CONDITIONAL_PRICES_STACKED_MODAL_ID,
+} from "../../../common/constants"
import { useShippingOptionPriceColumns } from "../../../common/hooks/use-shipping-option-price-columns"
import { ConditionalPriceInfo } from "../../../common/types"
import { CreateShippingOptionSchema } from "./schema"
type PricingPricesFormProps = {
form: UseFormReturn
+ type: FulfillmentSetType
}
export const CreateShippingOptionsPricesForm = ({
form,
+ type,
}: PricingPricesFormProps) => {
+ const isPickup = type === FulfillmentSetType.Pickup
const { getIsOpen, setIsOpen } = useStackedModal()
const [selectedPrice, setSelectedPrice] =
useState(null)
@@ -80,6 +86,25 @@ export const CreateShippingOptionsPricesForm = ({
[currencies, regions]
)
+ /**
+ * Prefill prices with 0 if createing a pickup (shipping) option
+ */
+ useEffect(() => {
+ if (!isLoading && isPickup) {
+ if (currencies.length > 0) {
+ currencies.forEach((currency) => {
+ form.setValue(`currency_prices.${currency}`, "0")
+ })
+ }
+
+ if (regions.length > 0) {
+ regions.forEach((region) => {
+ form.setValue(`region_prices.${region.id}`, "0")
+ })
+ }
+ }
+ }, [isLoading, isPickup])
+
if (isStoreError) {
throw storeError
}
diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx
index f878ac9d0d..c40d49a89b 100644
--- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx
+++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx
@@ -4,6 +4,7 @@ import { RouteFocusModal } from "../../../components/modals"
import { useStockLocation } from "../../../hooks/api/stock-locations"
import { CreateShippingOptionsForm } from "./components/create-shipping-options-form"
import { LOC_CREATE_SHIPPING_OPTION_FIELDS } from "./constants"
+import { FulfillmentSetType } from "../common/constants"
export function LocationServiceZoneShippingOptionCreate() {
const { location_id, fset_id, zone_id } = useParams()
@@ -15,9 +16,18 @@ export function LocationServiceZoneShippingOptionCreate() {
fields: LOC_CREATE_SHIPPING_OPTION_FIELDS,
})
- const zone = stock_location?.fulfillment_sets
- ?.find((f) => f.id === fset_id)
- ?.service_zones?.find((z) => z.id === zone_id)
+ const fulfillmentSet = stock_location?.fulfillment_sets?.find(
+ (f) => f.id === fset_id
+ )
+
+ if (!isPending && !isFetching && !fulfillmentSet) {
+ throw json(
+ { message: `Fulfillment set with ID ${fset_id} was not found` },
+ 404
+ )
+ }
+
+ const zone = fulfillmentSet?.service_zones?.find((z) => z.id === zone_id)
if (!isPending && !isFetching && !zone) {
throw json(
@@ -37,6 +47,7 @@ export function LocationServiceZoneShippingOptionCreate() {
zone={zone}
isReturn={isReturn}
locationId={location_id!}
+ type={fulfillmentSet!.type as FulfillmentSetType}
/>
)}
diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx
index bed53bbe99..c33fe0b6dd 100644
--- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx
+++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/components/edit-region-form/edit-shipping-option-form.tsx
@@ -15,11 +15,15 @@ import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client"
import { pick } from "../../../../../lib/common"
import { isOptionEnabledInStore } from "../../../../../lib/shipping-options"
-import { ShippingOptionPriceType } from "../../../common/constants"
+import {
+ FulfillmentSetType,
+ ShippingOptionPriceType,
+} from "../../../common/constants"
type EditShippingOptionFormProps = {
locationId: string
shippingOption: HttpTypes.AdminShippingOption
+ type: FulfillmentSetType
}
const EditShippingOptionSchema = zod.object({
@@ -32,10 +36,13 @@ const EditShippingOptionSchema = zod.object({
export const EditShippingOptionForm = ({
locationId,
shippingOption,
+ type,
}: EditShippingOptionFormProps) => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
+ const isPickup = type === FulfillmentSetType.Pickup
+
const shippingProfiles = useComboboxData({
queryFn: (params) => sdk.admin.shippingProfile.list(params),
queryKey: ["shipping_profiles"],
@@ -108,46 +115,48 @@ export const EditShippingOptionForm = ({
-
{
- return (
-
-
- {t(
- "stockLocations.shippingOptions.fields.priceType.label"
- )}
-
-
-
-
-
-
-
-
-
- )
- }}
- />
+ {!isPickup && (
+ {
+ return (
+
+
+ {t(
+ "stockLocations.shippingOptions.fields.priceType.label"
+ )}
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+ )}
-
-
-
+ {!isPickup && (
+ <>
+
+
+ >
+ )}
diff --git a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/location-service-zone-shipping-option-edit.tsx b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/location-service-zone-shipping-option-edit.tsx
index 5845c6a891..90c9bd1fce 100644
--- a/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/location-service-zone-shipping-option-edit.tsx
+++ b/packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-edit/location-service-zone-shipping-option-edit.tsx
@@ -5,6 +5,7 @@ import { json, useParams } from "react-router-dom"
import { RouteDrawer } from "../../../components/modals"
import { useShippingOptions } from "../../../hooks/api/shipping-options"
import { EditShippingOptionForm } from "./components/edit-region-form"
+import { FulfillmentSetType } from "../common/constants"
export const LocationServiceZoneShippingOptionEdit = () => {
const { t } = useTranslation()
@@ -14,6 +15,7 @@ export const LocationServiceZoneShippingOptionEdit = () => {
const { shipping_options, isPending, isFetching, isError, error } =
useShippingOptions({
id: so_id,
+ fields: "+service_zone.fulfillment_set.type",
})
const shippingOption = shipping_options?.find((so) => so.id === so_id)
@@ -29,15 +31,27 @@ export const LocationServiceZoneShippingOptionEdit = () => {
throw error
}
+ const isPickup =
+ shippingOption?.service_zone.fulfillment_set.type ===
+ FulfillmentSetType.Pickup
+
return (
- {t("stockLocations.shippingOptions.edit.header")}
+
+ {t(
+ `stockLocations.${isPickup ? "pickupOptions" : "shippingOptions"}.edit.header`
+ )}
+
{shippingOption && (
)}
diff --git a/packages/admin/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx b/packages/admin/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx
index 30e5d8f4f7..8e2054ef7a 100644
--- a/packages/admin/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx
+++ b/packages/admin/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx
@@ -30,6 +30,7 @@ import {
import { useStockLocation } from "../../../../../hooks/api/stock-locations"
import { formatProvider } from "../../../../../lib/format-provider"
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
+import { FulfillmentSetType } from "../../../../locations/common/constants"
type OrderFulfillmentSectionProps = {
order: AdminOrder
@@ -213,6 +214,10 @@ const Fulfillment = ({
const showLocation = !!fulfillment.location_id
+ const isPickUpFulfillment =
+ fulfillment.shipping_option?.service_zone.fulfillment_set.type ===
+ FulfillmentSetType.Pickup
+
const { stock_location, isError, error } = useStockLocation(
fulfillment.location_id!,
undefined,
@@ -222,7 +227,9 @@ const Fulfillment = ({
)
let statusText = fulfillment.requires_shipping
- ? "Awaiting shipping"
+ ? isPickUpFulfillment
+ ? "Awaiting pickup"
+ : "Awaiting shipping"
: "Awaiting delivery"
let statusColor: "blue" | "green" | "red" = "blue"
let statusTimestamp = fulfillment.created_at
@@ -251,7 +258,9 @@ const Fulfillment = ({
!fulfillment.canceled_at &&
!fulfillment.shipped_at &&
!fulfillment.delivered_at &&
- fulfillment.requires_shipping
+ fulfillment.requires_shipping &&
+ !isPickUpFulfillment
+
const showDeliveryButton =
!fulfillment.canceled_at && !fulfillment.delivered_at
@@ -267,7 +276,13 @@ const Fulfillment = ({
if (res) {
await markAsDelivered(undefined, {
onSuccess: () => {
- toast.success(t("orders.fulfillment.toast.fulfillmentDelivered"))
+ toast.success(
+ t(
+ isPickUpFulfillment
+ ? "orders.fulfillment.toast.fulfillmentPickedUp"
+ : "orders.fulfillment.toast.fulfillmentDelivered"
+ )
+ )
},
onError: (e) => {
toast.error(e.message)
@@ -431,7 +446,11 @@ const Fulfillment = ({
{showDeliveryButton && (
)}
diff --git a/packages/admin/dashboard/src/routes/orders/order-detail/constants.ts b/packages/admin/dashboard/src/routes/orders/order-detail/constants.ts
index 446ff7e218..a4a1519659 100644
--- a/packages/admin/dashboard/src/routes/orders/order-detail/constants.ts
+++ b/packages/admin/dashboard/src/routes/orders/order-detail/constants.ts
@@ -37,6 +37,7 @@ const DEFAULT_RELATIONS = [
"*promotion",
"*shipping_methods",
"*fulfillments",
+ "+fulfillments.shipping_option.service_zone.fulfillment_set.type",
"*fulfillments.items",
"*fulfillments.labels",
"*fulfillments.labels",
diff --git a/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart-with-pricing.ts b/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart-with-pricing.ts
index 1fc550029e..600138fd90 100644
--- a/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart-with-pricing.ts
+++ b/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart-with-pricing.ts
@@ -22,6 +22,8 @@ const COMMON_OPTIONS_FIELDS = [
"price_type",
"service_zone_id",
"service_zone.fulfillment_set_id",
+ "service_zone.fulfillment_set.type",
+ "service_zone.fulfillment_set.location.address.*",
"shipping_profile_id",
"provider_id",
"data",
diff --git a/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts b/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts
index 2a177673f6..255eda18c8 100644
--- a/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts
+++ b/packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart.ts
@@ -131,6 +131,8 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
"provider_id",
"data",
"service_zone.fulfillment_set_id",
+ "service_zone.fulfillment_set.type",
+ "service_zone.fulfillment_set.location.address.*",
"type.id",
"type.label",