Refactor: claim service to TS + refactoring (#1287)
This commit is contained in:
1 parent
63bf684e66
commit
78bd61abe1
9 files changed
+965
-777
No files matched your search
@@ -14,6 +14,12 @@ import { MedusaError } from "medusa-core-utils"
|
|||||||
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
|
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
|
||||||
import { AddressPayload } from "../../../../types/common"
|
import { AddressPayload } from "../../../../types/common"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
import {
|
||||||
|
ClaimItemReason,
|
||||||
|
ClaimItemReasonValue,
|
||||||
|
ClaimTypeValue,
|
||||||
|
} from "../../../../types/claim"
|
||||||
|
import { ClaimType } from "../../../../models"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /order/{id}/claims
|
* @oas [post] /order/{id}/claims
|
||||||
@@ -332,26 +338,10 @@ export default async (req, res) => {
|
|||||||
res.status(idempotencyKey.response_code).json(idempotencyKey.response_body)
|
res.status(idempotencyKey.response_code).json(idempotencyKey.response_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClaimTypeEnum {
|
|
||||||
replace = "replace",
|
|
||||||
refund = "refund",
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClaimType = `${ClaimTypeEnum}`
|
|
||||||
|
|
||||||
enum ClaimItemReasonEnum {
|
|
||||||
missing_item = "missing_item",
|
|
||||||
wrong_item = "wrong_item",
|
|
||||||
production_failure = "production_failure",
|
|
||||||
other = "other",
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClaimItemReasonType = `${ClaimItemReasonEnum}`
|
|
||||||
|
|
||||||
export class AdminPostOrdersOrderClaimsReq {
|
export class AdminPostOrdersOrderClaimsReq {
|
||||||
@IsEnum(ClaimTypeEnum)
|
@IsEnum(ClaimType)
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
type: ClaimType
|
type: ClaimTypeValue
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@@ -432,9 +422,9 @@ class Item {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
note?: string
|
note?: string
|
||||||
|
|
||||||
@IsEnum(ClaimItemReasonEnum)
|
@IsEnum(ClaimItemReason)
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
reason?: ClaimItemReasonType
|
reason?: ClaimItemReasonValue
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default async (req, res) => {
|
|||||||
export class AdminPostOrdersOrderClaimsClaimFulfillmentsReq {
|
export class AdminPostOrdersOrderClaimsClaimFulfillmentsReq {
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: object
|
metadata?: Record<string, unknown>
|
||||||
|
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export class AdminPostOrdersOrderClaimsClaimReq {
|
|||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: object
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShippingMethod {
|
class ShippingMethod {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class ShippingMethod {
|
|||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
claim_order_id: string
|
claim_order_id: string | null
|
||||||
|
|
||||||
@ManyToOne(() => ClaimOrder)
|
@ManyToOne(() => ClaimOrder)
|
||||||
@JoinColumn({ name: "claim_order_id" })
|
@JoinColumn({ name: "claim_order_id" })
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ describe("ClaimService", () => {
|
|||||||
create: (d) => ({ id: "claim_134", ...d }),
|
create: (d) => ({ id: "claim_134", ...d }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const lineItemRepository = MockRepository({
|
||||||
|
create: (d) => ({ id: "claim_item_134", ...d }),
|
||||||
|
})
|
||||||
|
|
||||||
const taxProviderService = {
|
const taxProviderService = {
|
||||||
createTaxLines: jest.fn(),
|
createTaxLines: jest.fn(),
|
||||||
withTransaction: function () {
|
withTransaction: function () {
|
||||||
@@ -103,6 +107,7 @@ describe("ClaimService", () => {
|
|||||||
const claimService = new ClaimService({
|
const claimService = new ClaimService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
claimRepository: claimRepo,
|
claimRepository: claimRepo,
|
||||||
|
lineItemRepository: lineItemRepository,
|
||||||
taxProviderService,
|
taxProviderService,
|
||||||
totalsService,
|
totalsService,
|
||||||
returnService,
|
returnService,
|
||||||
|
|||||||
@@ -974,7 +974,6 @@ describe("SwapService", () => {
|
|||||||
|
|
||||||
const swapService = new SwapService({
|
const swapService = new SwapService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
eventBusService,
|
|
||||||
swapRepository: swapRepo,
|
swapRepository: swapRepo,
|
||||||
paymentProviderService,
|
paymentProviderService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
|
|||||||
@@ -1,753 +0,0 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
|
|
||||||
class ClaimService extends BaseService {
|
|
||||||
static Events = {
|
|
||||||
CREATED: "claim.created",
|
|
||||||
UPDATED: "claim.updated",
|
|
||||||
CANCELED: "claim.canceled",
|
|
||||||
FULFILLMENT_CREATED: "claim.fulfillment_created",
|
|
||||||
SHIPMENT_CREATED: "claim.shipment_created",
|
|
||||||
REFUND_PROCESSED: "claim.refund_processed",
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
manager,
|
|
||||||
addressRepository,
|
|
||||||
claimItemService,
|
|
||||||
claimRepository,
|
|
||||||
eventBusService,
|
|
||||||
fulfillmentProviderService,
|
|
||||||
fulfillmentService,
|
|
||||||
inventoryService,
|
|
||||||
lineItemService,
|
|
||||||
paymentProviderService,
|
|
||||||
regionService,
|
|
||||||
returnService,
|
|
||||||
shippingOptionService,
|
|
||||||
taxProviderService,
|
|
||||||
totalsService,
|
|
||||||
}) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
/** @private @constant {EntityManager} */
|
|
||||||
this.manager_ = manager
|
|
||||||
|
|
||||||
this.addressRepo_ = addressRepository
|
|
||||||
this.claimItemService_ = claimItemService
|
|
||||||
this.claimRepository_ = claimRepository
|
|
||||||
this.eventBus_ = eventBusService
|
|
||||||
this.fulfillmentProviderService_ = fulfillmentProviderService
|
|
||||||
this.fulfillmentService_ = fulfillmentService
|
|
||||||
this.inventoryService_ = inventoryService
|
|
||||||
this.lineItemService_ = lineItemService
|
|
||||||
this.paymentProviderService_ = paymentProviderService
|
|
||||||
this.regionService_ = regionService
|
|
||||||
this.returnService_ = returnService
|
|
||||||
this.shippingOptionService_ = shippingOptionService
|
|
||||||
this.taxProviderService_ = taxProviderService
|
|
||||||
this.totalsService_ = totalsService
|
|
||||||
}
|
|
||||||
|
|
||||||
withTransaction(manager) {
|
|
||||||
if (!manager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new ClaimService({
|
|
||||||
manager,
|
|
||||||
addressRepository: this.addressRepo_,
|
|
||||||
claimItemService: this.claimItemService_,
|
|
||||||
claimRepository: this.claimRepository_,
|
|
||||||
eventBusService: this.eventBus_,
|
|
||||||
fulfillmentProviderService: this.fulfillmentProviderService_,
|
|
||||||
fulfillmentService: this.fulfillmentService_,
|
|
||||||
inventoryService: this.inventoryService_,
|
|
||||||
lineItemService: this.lineItemService_,
|
|
||||||
paymentProviderService: this.paymentProviderService_,
|
|
||||||
regionService: this.regionService_,
|
|
||||||
returnService: this.returnService_,
|
|
||||||
shippingOptionService: this.shippingOptionService_,
|
|
||||||
totalsService: this.totalsService_,
|
|
||||||
taxProviderService: this.taxProviderService_,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = manager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
update(id, data) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
const claim = await this.retrieve(id, { relations: ["shipping_methods"] })
|
|
||||||
|
|
||||||
if (claim.canceled_at) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Canceled claim cannot be updated"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { claim_items, shipping_methods, metadata, no_notification } = data
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
claim.metadata = this.setMetadata_(claim, metadata)
|
|
||||||
await claimRepo.save(claim)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shipping_methods) {
|
|
||||||
for (const m of claim.shipping_methods) {
|
|
||||||
await this.shippingOptionService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.updateShippingMethod(m.id, {
|
|
||||||
claim_order_id: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const method of shipping_methods) {
|
|
||||||
if (method.id) {
|
|
||||||
await this.shippingOptionService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.updateShippingMethod(method.id, {
|
|
||||||
claim_order_id: claim.id,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.shippingOptionService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.createShippingMethod(method.option_id, method.data, {
|
|
||||||
claim_order_id: claim.id,
|
|
||||||
price: method.price,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (no_notification !== undefined) {
|
|
||||||
claim.no_notification = no_notification
|
|
||||||
await claimRepo.save(claim)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim_items) {
|
|
||||||
for (const i of claim_items) {
|
|
||||||
if (i.id) {
|
|
||||||
await this.claimItemService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.update(i.id, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(ClaimService.Events.UPDATED, {
|
|
||||||
id: claim.id,
|
|
||||||
no_notification: claim.no_notification,
|
|
||||||
})
|
|
||||||
|
|
||||||
return claim
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Claim on an Order. Claims consists of items that are claimed and
|
|
||||||
* optionally items to be sent as replacement for the claimed items. The
|
|
||||||
* shipping address that the new items will be shipped to
|
|
||||||
* @param {Object} data - the object containing all data required to create a claim
|
|
||||||
* @return {Object} created claim
|
|
||||||
*/
|
|
||||||
create(data) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
|
|
||||||
const {
|
|
||||||
type,
|
|
||||||
claim_items,
|
|
||||||
order,
|
|
||||||
return_shipping,
|
|
||||||
additional_items,
|
|
||||||
shipping_methods,
|
|
||||||
refund_amount,
|
|
||||||
shipping_address,
|
|
||||||
shipping_address_id,
|
|
||||||
no_notification,
|
|
||||||
...rest
|
|
||||||
} = data
|
|
||||||
|
|
||||||
for (const item of claim_items) {
|
|
||||||
const line = await this.lineItemService_.retrieve(item.item_id, {
|
|
||||||
relations: ["order", "swap", "claim_order", "tax_lines"],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
|
||||||
line.order?.canceled_at ||
|
|
||||||
line.swap?.canceled_at ||
|
|
||||||
line.claim_order?.canceled_at
|
|
||||||
) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`Cannot create a claim on a canceled item.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let addressId = shipping_address_id || order.shipping_address_id
|
|
||||||
if (shipping_address) {
|
|
||||||
const addressRepo = manager.getCustomRepository(this.addressRepo_)
|
|
||||||
const created = addressRepo.create(shipping_address)
|
|
||||||
const saved = await addressRepo.save(created)
|
|
||||||
addressId = saved.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type !== "refund" && type !== "replace") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`Claim type must be one of "refund" or "replace".`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "replace" && !additional_items?.length) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`Claims with type "replace" must have at least one additional item.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!claim_items?.length) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`Claims must have at least one claim item.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refund_amount && type !== "refund") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`Claim has type "${type}" but must be type "refund" to have a refund_amount.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let toRefund = refund_amount
|
|
||||||
if (type === "refund" && typeof refund_amount === "undefined") {
|
|
||||||
const lines = claim_items.map((ci) => {
|
|
||||||
const allOrderItems = order.items
|
|
||||||
|
|
||||||
if (order.swaps?.length) {
|
|
||||||
for (const swap of order.swaps) {
|
|
||||||
swap.additional_items.forEach((it) => {
|
|
||||||
if (
|
|
||||||
it.shipped_quantity ||
|
|
||||||
it.shipped_quantity === it.fulfilled_quantity
|
|
||||||
) {
|
|
||||||
allOrderItems.push(it)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.claims?.length) {
|
|
||||||
for (const claim of order.claims) {
|
|
||||||
claim.additional_items.forEach((it) => {
|
|
||||||
if (
|
|
||||||
it.shipped_quantity ||
|
|
||||||
it.shipped_quantity === it.fulfilled_quantity
|
|
||||||
) {
|
|
||||||
allOrderItems.push(it)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderItem = allOrderItems.find((oi) => oi.id === ci.item_id)
|
|
||||||
return {
|
|
||||||
...orderItem,
|
|
||||||
quantity: ci.quantity,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
toRefund = await this.totalsService_.getRefundTotal(order, lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
let newItems = []
|
|
||||||
if (typeof additional_items !== "undefined") {
|
|
||||||
for (const item of additional_items) {
|
|
||||||
await this.inventoryService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.confirmInventory(item.variant_id, item.quantity)
|
|
||||||
}
|
|
||||||
|
|
||||||
newItems = await Promise.all(
|
|
||||||
additional_items.map((i) =>
|
|
||||||
this.lineItemService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.generate(i.variant_id, order.region_id, i.quantity)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const newItem of newItems) {
|
|
||||||
await this.inventoryService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.adjustInventory(newItem.variant_id, -newItem.quantity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const evaluatedNoNotification =
|
|
||||||
no_notification !== undefined ? no_notification : order.no_notification
|
|
||||||
|
|
||||||
const created = claimRepo.create({
|
|
||||||
shipping_address_id: addressId,
|
|
||||||
payment_status: type === "refund" ? "not_refunded" : "na",
|
|
||||||
...rest,
|
|
||||||
refund_amount: toRefund,
|
|
||||||
type,
|
|
||||||
additional_items: newItems,
|
|
||||||
order_id: order.id,
|
|
||||||
no_notification: evaluatedNoNotification,
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await claimRepo.save(created)
|
|
||||||
|
|
||||||
if (result.additional_items && result.additional_items.length) {
|
|
||||||
const calcContext = this.totalsService_.getCalculationContext(order)
|
|
||||||
const lineItems = await this.lineItemService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.list({
|
|
||||||
id: result.additional_items.map((i) => i.id),
|
|
||||||
})
|
|
||||||
await this.taxProviderService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.createTaxLines(lineItems, calcContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shipping_methods) {
|
|
||||||
for (const method of shipping_methods) {
|
|
||||||
if (method.id) {
|
|
||||||
await this.shippingOptionService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.updateShippingMethod(method.id, {
|
|
||||||
claim_order_id: result.id,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.shippingOptionService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.createShippingMethod(method.option_id, method.data, {
|
|
||||||
claim_order_id: result.id,
|
|
||||||
price: method.price,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ci of claim_items) {
|
|
||||||
await this.claimItemService_.withTransaction(manager).create({
|
|
||||||
...ci,
|
|
||||||
claim_order_id: result.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (return_shipping) {
|
|
||||||
await this.returnService_.withTransaction(manager).create({
|
|
||||||
order_id: order.id,
|
|
||||||
claim_order_id: result.id,
|
|
||||||
items: claim_items.map((ci) => ({
|
|
||||||
item_id: ci.item_id,
|
|
||||||
quantity: ci.quantity,
|
|
||||||
metadata: ci.metadata,
|
|
||||||
})),
|
|
||||||
shipping_method: return_shipping,
|
|
||||||
no_notification: evaluatedNoNotification,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(ClaimService.Events.CREATED, {
|
|
||||||
id: result.id,
|
|
||||||
no_notification: result.no_notification,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {string} id - the object containing all data required to create a claim
|
|
||||||
* @param {Object} config - config object
|
|
||||||
* @param {Object | undefined} config.metadata - config metadata
|
|
||||||
* @param {boolean|undefined} config.no_notification - config no notification
|
|
||||||
* @return {Claim} created claim
|
|
||||||
*/
|
|
||||||
createFulfillment(
|
|
||||||
id,
|
|
||||||
config = {
|
|
||||||
metadata: {},
|
|
||||||
no_notification: undefined,
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const { metadata, no_notification } = config
|
|
||||||
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const claim = await this.retrieve(id, {
|
|
||||||
relations: [
|
|
||||||
"additional_items",
|
|
||||||
"additional_items.tax_lines",
|
|
||||||
"shipping_methods",
|
|
||||||
"shipping_methods.tax_lines",
|
|
||||||
"shipping_address",
|
|
||||||
"order",
|
|
||||||
"order.billing_address",
|
|
||||||
"order.discounts",
|
|
||||||
"order.discounts.rule",
|
|
||||||
"order.payments",
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (claim.canceled_at) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Canceled claim cannot be fulfilled"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = claim.order
|
|
||||||
|
|
||||||
if (
|
|
||||||
claim.fulfillment_status !== "not_fulfilled" &&
|
|
||||||
claim.fulfillment_status !== "canceled"
|
|
||||||
) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"The claim has already been fulfilled."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim.type !== "replace") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`Claims with the type "${claim.type}" can not be fulfilled.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!claim.shipping_methods?.length) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Cannot fulfill a claim without a shipping method."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const evaluatedNoNotification =
|
|
||||||
no_notification !== undefined ? no_notification : claim.no_notification
|
|
||||||
|
|
||||||
const fulfillments = await this.fulfillmentService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.createFulfillment(
|
|
||||||
{
|
|
||||||
...claim,
|
|
||||||
email: order.email,
|
|
||||||
payments: order.payments,
|
|
||||||
discounts: order.discounts,
|
|
||||||
currency_code: order.currency_code,
|
|
||||||
tax_rate: order.tax_rate,
|
|
||||||
region_id: order.region_id,
|
|
||||||
display_id: order.display_id,
|
|
||||||
billing_address: order.billing_address,
|
|
||||||
items: claim.additional_items,
|
|
||||||
shipping_methods: claim.shipping_methods,
|
|
||||||
is_claim: true,
|
|
||||||
no_notification: evaluatedNoNotification,
|
|
||||||
},
|
|
||||||
claim.additional_items.map((i) => ({
|
|
||||||
item_id: i.id,
|
|
||||||
quantity: i.quantity,
|
|
||||||
})),
|
|
||||||
{ claim_order_id: id, metadata }
|
|
||||||
)
|
|
||||||
|
|
||||||
let successfullyFulfilled = []
|
|
||||||
for (const f of fulfillments) {
|
|
||||||
successfullyFulfilled = successfullyFulfilled.concat(f.items)
|
|
||||||
}
|
|
||||||
|
|
||||||
claim.fulfillment_status = "fulfilled"
|
|
||||||
|
|
||||||
for (const item of claim.additional_items) {
|
|
||||||
const fulfillmentItem = successfullyFulfilled.find(
|
|
||||||
(f) => item.id === f.item_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (fulfillmentItem) {
|
|
||||||
const fulfilledQuantity =
|
|
||||||
(item.fulfilled_quantity || 0) + fulfillmentItem.quantity
|
|
||||||
|
|
||||||
// Update the fulfilled quantity
|
|
||||||
await this.lineItemService_.withTransaction(manager).update(item.id, {
|
|
||||||
fulfilled_quantity: fulfilledQuantity,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (item.quantity !== fulfilledQuantity) {
|
|
||||||
claim.fulfillment_status = "requires_action"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (item.quantity !== item.fulfilled_quantity) {
|
|
||||||
claim.fulfillment_status = "requires_action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
const result = await claimRepo.save(claim)
|
|
||||||
|
|
||||||
for (const fulfillment of fulfillments) {
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(ClaimService.Events.FULFILLMENT_CREATED, {
|
|
||||||
id: id,
|
|
||||||
fulfillment_id: fulfillment.id,
|
|
||||||
no_notification: claim.no_notification,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async cancelFulfillment(fulfillmentId) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const canceled = await this.fulfillmentService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.cancelFulfillment(fulfillmentId)
|
|
||||||
|
|
||||||
if (!canceled.claim_order_id) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`Fufillment not related to a claim`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const claim = await this.retrieve(canceled.claim_order_id)
|
|
||||||
|
|
||||||
claim.fulfillment_status = "canceled"
|
|
||||||
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
const updated = await claimRepo.save(claim)
|
|
||||||
return updated
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async processRefund(id) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const claim = await this.retrieve(id, {
|
|
||||||
relations: ["order", "order.payments"],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (claim.canceled_at) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Canceled claim cannot be processed"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim.type !== "refund") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`Claim must have type "refund" to create a refund.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim.refund_amount) {
|
|
||||||
await this.paymentProviderService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.refundPayment(claim.order.payments, claim.refund_amount, "claim")
|
|
||||||
}
|
|
||||||
|
|
||||||
claim.payment_status = "refunded"
|
|
||||||
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
const result = await claimRepo.save(claim)
|
|
||||||
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(ClaimService.Events.REFUND_PROCESSED, {
|
|
||||||
id,
|
|
||||||
no_notification: result.no_notification,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async createShipment(
|
|
||||||
id,
|
|
||||||
fulfillmentId,
|
|
||||||
trackingLinks,
|
|
||||||
config = {
|
|
||||||
metadata: {},
|
|
||||||
no_notification: undefined,
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const { metadata, no_notification } = config
|
|
||||||
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const claim = await this.retrieve(id, {
|
|
||||||
relations: ["additional_items"],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (claim.canceled_at) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Canceled claim cannot be fulfilled as shipped"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const evaluatedNoNotification =
|
|
||||||
no_notification !== undefined ? no_notification : claim.no_notification
|
|
||||||
|
|
||||||
const shipment = await this.fulfillmentService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.createShipment(fulfillmentId, trackingLinks, {
|
|
||||||
metadata,
|
|
||||||
no_notification: evaluatedNoNotification,
|
|
||||||
})
|
|
||||||
|
|
||||||
claim.fulfillment_status = "shipped"
|
|
||||||
|
|
||||||
for (const i of claim.additional_items) {
|
|
||||||
const shipped = shipment.items.find((si) => si.item_id === i.id)
|
|
||||||
if (shipped) {
|
|
||||||
const shippedQty = (i.shipped_quantity || 0) + shipped.quantity
|
|
||||||
await this.lineItemService_.withTransaction(manager).update(i.id, {
|
|
||||||
shipped_quantity: shippedQty,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (shippedQty !== i.quantity) {
|
|
||||||
claim.fulfillment_status = "partially_shipped"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (i.shipped_quantity !== i.quantity) {
|
|
||||||
claim.fulfillment_status = "partially_shipped"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
const result = await claimRepo.save(claim)
|
|
||||||
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(ClaimService.Events.SHIPMENT_CREATED, {
|
|
||||||
id,
|
|
||||||
fulfillment_id: shipment.id,
|
|
||||||
no_notification: evaluatedNoNotification,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async cancel(id) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const claim = await this.retrieve(id, {
|
|
||||||
relations: ["return_order", "fulfillments", "order", "order.refunds"],
|
|
||||||
})
|
|
||||||
if (claim.refund_amount) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Claim with a refund cannot be canceled"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim.fulfillments) {
|
|
||||||
for (const f of claim.fulfillments) {
|
|
||||||
if (!f.canceled_at) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"All fulfillments must be canceled before the claim can be canceled"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim.return_order && claim.return_order.status !== "canceled") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"Return must be canceled before the claim can be canceled"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
claim.fulfillment_status = "canceled"
|
|
||||||
claim.canceled_at = new Date()
|
|
||||||
|
|
||||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
|
||||||
const result = await claimRepo.save(claim)
|
|
||||||
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(ClaimService.Events.CANCELED, {
|
|
||||||
id: result.id,
|
|
||||||
no_notification: result.no_notification,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} selector - the query object for find
|
|
||||||
* @param {Object} config - the config object containing query settings
|
|
||||||
* @return {Promise} the result of the find operation
|
|
||||||
*/
|
|
||||||
async list(
|
|
||||||
selector,
|
|
||||||
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
|
|
||||||
) {
|
|
||||||
const claimRepo = this.manager_.getCustomRepository(this.claimRepository_)
|
|
||||||
const query = this.buildQuery_(selector, config)
|
|
||||||
return claimRepo.find(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an order by id.
|
|
||||||
* @param {string} claimId - id of order to retrieve
|
|
||||||
* @param {Object} config - the config object containing query settings
|
|
||||||
* @return {Promise<Order>} the order document
|
|
||||||
*/
|
|
||||||
async retrieve(claimId, config = {}) {
|
|
||||||
const claimRepo = this.manager_.getCustomRepository(this.claimRepository_)
|
|
||||||
const validatedId = this.validateId_(claimId)
|
|
||||||
|
|
||||||
const query = this.buildQuery_({ id: validatedId }, config)
|
|
||||||
const claim = await claimRepo.findOne(query)
|
|
||||||
|
|
||||||
if (!claim) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Claim with ${claimId} was not found`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return claim
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dedicated method to delete metadata for an order.
|
|
||||||
* @param {string} orderId - the order to delete metadata from.
|
|
||||||
* @param {string} key - key for metadata field
|
|
||||||
* @return {Promise} resolves to the updated result.
|
|
||||||
*/
|
|
||||||
async deleteMetadata(orderId, key) {
|
|
||||||
const validatedId = this.validateId_(orderId)
|
|
||||||
|
|
||||||
if (typeof key !== "string") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
|
||||||
"Key type is invalid. Metadata keys must be strings"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyPath = `metadata.${key}`
|
|
||||||
return this.orderModel_
|
|
||||||
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
|
||||||
.catch((err) => {
|
|
||||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ClaimService
|
|
||||||
@@ -0,0 +1,858 @@
|
|||||||
|
import ClaimItemService from "./claim-item"
|
||||||
|
import EventBusService from "./event-bus"
|
||||||
|
import FulfillmentProviderService from "./fulfillment-provider"
|
||||||
|
import FulfillmentService from "./fulfillment"
|
||||||
|
import InventoryService from "./inventory"
|
||||||
|
import LineItemService from "./line-item"
|
||||||
|
import PaymentProviderService from "./payment-provider"
|
||||||
|
import RegionService from "./region"
|
||||||
|
import ReturnService from "./return"
|
||||||
|
import ShippingOptionService from "./shipping-option"
|
||||||
|
import TaxProviderService from "./tax-provider"
|
||||||
|
import TotalsService from "./totals"
|
||||||
|
import { AddressRepository } from "../repositories/address"
|
||||||
|
import {
|
||||||
|
ClaimFulfillmentStatus,
|
||||||
|
ClaimOrder,
|
||||||
|
ClaimPaymentStatus,
|
||||||
|
ClaimType,
|
||||||
|
FulfillmentItem,
|
||||||
|
LineItem,
|
||||||
|
} from "../models"
|
||||||
|
import { ClaimRepository } from "../repositories/claim"
|
||||||
|
import { DeepPartial, EntityManager } from "typeorm"
|
||||||
|
import { LineItemRepository } from "../repositories/line-item"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
import { FindConfig } from "../types/common"
|
||||||
|
import { CreateClaimInput, UpdateClaimInput } from "../types/claim"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
addressRepository: typeof AddressRepository
|
||||||
|
shippingMethodRepository: typeof ShippingMethodRepository
|
||||||
|
lineItemRepository: typeof LineItemRepository
|
||||||
|
claimRepository: typeof ClaimRepository
|
||||||
|
claimItemService: ClaimItemService
|
||||||
|
eventBusService: EventBusService
|
||||||
|
fulfillmentProviderService: FulfillmentProviderService
|
||||||
|
fulfillmentService: FulfillmentService
|
||||||
|
inventoryService: InventoryService
|
||||||
|
lineItemService: LineItemService
|
||||||
|
paymentProviderService: PaymentProviderService
|
||||||
|
regionService: RegionService
|
||||||
|
returnService: ReturnService
|
||||||
|
shippingOptionService: ShippingOptionService
|
||||||
|
taxProviderService: TaxProviderService
|
||||||
|
totalsService: TotalsService
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ClaimService extends TransactionBaseService<
|
||||||
|
ClaimService,
|
||||||
|
InjectedDependencies
|
||||||
|
> {
|
||||||
|
static readonly Events = {
|
||||||
|
CREATED: "claim.created",
|
||||||
|
UPDATED: "claim.updated",
|
||||||
|
CANCELED: "claim.canceled",
|
||||||
|
FULFILLMENT_CREATED: "claim.fulfillment_created",
|
||||||
|
SHIPMENT_CREATED: "claim.shipment_created",
|
||||||
|
REFUND_PROCESSED: "claim.refund_processed",
|
||||||
|
}
|
||||||
|
|
||||||
|
protected manager_: EntityManager
|
||||||
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
|
||||||
|
protected readonly addressRepository_: typeof AddressRepository
|
||||||
|
protected readonly claimRepository_: typeof ClaimRepository
|
||||||
|
protected readonly shippingMethodRepository_: typeof ShippingMethodRepository
|
||||||
|
protected readonly lineItemRepository_: typeof LineItemRepository
|
||||||
|
protected readonly claimItemService_: ClaimItemService
|
||||||
|
protected readonly eventBus_: EventBusService
|
||||||
|
protected readonly fulfillmentProviderService_: FulfillmentProviderService
|
||||||
|
protected readonly fulfillmentService_: FulfillmentService
|
||||||
|
protected readonly inventoryService_: InventoryService
|
||||||
|
protected readonly lineItemService_: LineItemService
|
||||||
|
protected readonly paymentProviderService_: PaymentProviderService
|
||||||
|
protected readonly regionService_: RegionService
|
||||||
|
protected readonly returnService_: ReturnService
|
||||||
|
protected readonly shippingOptionService_: ShippingOptionService
|
||||||
|
protected readonly taxProviderService_: TaxProviderService
|
||||||
|
protected readonly totalsService_: TotalsService
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
addressRepository,
|
||||||
|
claimRepository,
|
||||||
|
shippingMethodRepository,
|
||||||
|
lineItemRepository,
|
||||||
|
claimItemService,
|
||||||
|
eventBusService,
|
||||||
|
fulfillmentProviderService,
|
||||||
|
fulfillmentService,
|
||||||
|
inventoryService,
|
||||||
|
lineItemService,
|
||||||
|
paymentProviderService,
|
||||||
|
regionService,
|
||||||
|
returnService,
|
||||||
|
shippingOptionService,
|
||||||
|
taxProviderService,
|
||||||
|
totalsService,
|
||||||
|
}: InjectedDependencies) {
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
|
this.manager_ = manager
|
||||||
|
|
||||||
|
this.addressRepository_ = addressRepository
|
||||||
|
this.claimRepository_ = claimRepository
|
||||||
|
this.shippingMethodRepository_ = shippingMethodRepository
|
||||||
|
this.lineItemRepository_ = lineItemRepository
|
||||||
|
this.claimItemService_ = claimItemService
|
||||||
|
this.eventBus_ = eventBusService
|
||||||
|
this.fulfillmentProviderService_ = fulfillmentProviderService
|
||||||
|
this.fulfillmentService_ = fulfillmentService
|
||||||
|
this.inventoryService_ = inventoryService
|
||||||
|
this.lineItemService_ = lineItemService
|
||||||
|
this.paymentProviderService_ = paymentProviderService
|
||||||
|
this.regionService_ = regionService
|
||||||
|
this.returnService_ = returnService
|
||||||
|
this.shippingOptionService_ = shippingOptionService
|
||||||
|
this.taxProviderService_ = taxProviderService
|
||||||
|
this.totalsService_ = totalsService
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, data: UpdateClaimInput): Promise<ClaimOrder> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
const claim = await this.retrieve(id, {
|
||||||
|
relations: ["shipping_methods"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (claim.canceled_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Canceled claim cannot be updated"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { claim_items, shipping_methods, metadata, no_notification } =
|
||||||
|
data
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
claim.metadata = setMetadata(claim, metadata)
|
||||||
|
await claimRepo.save(claim)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shipping_methods) {
|
||||||
|
for (const m of claim.shipping_methods) {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.updateShippingMethod(m.id, {
|
||||||
|
claim_order_id: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const method of shipping_methods) {
|
||||||
|
if (method.id) {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.updateShippingMethod(method.id, {
|
||||||
|
claim_order_id: claim.id,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.createShippingMethod(
|
||||||
|
method.option_id as string,
|
||||||
|
(method as any).data,
|
||||||
|
{
|
||||||
|
claim_order_id: claim.id,
|
||||||
|
price: method.price,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (no_notification !== undefined) {
|
||||||
|
claim.no_notification = no_notification
|
||||||
|
await claimRepo.save(claim)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim_items) {
|
||||||
|
for (const i of claim_items) {
|
||||||
|
if (i.id) {
|
||||||
|
await this.claimItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.update(i.id, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(ClaimService.Events.UPDATED, {
|
||||||
|
id: claim.id,
|
||||||
|
no_notification: claim.no_notification,
|
||||||
|
})
|
||||||
|
|
||||||
|
return claim
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Claim on an Order. Claims consists of items that are claimed and
|
||||||
|
* optionally items to be sent as replacement for the claimed items. The
|
||||||
|
* shipping address that the new items will be shipped to
|
||||||
|
* @param data - the object containing all data required to create a claim
|
||||||
|
* @return created claim
|
||||||
|
*/
|
||||||
|
async create(data: CreateClaimInput): Promise<ClaimOrder> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
claim_items,
|
||||||
|
order,
|
||||||
|
return_shipping,
|
||||||
|
additional_items,
|
||||||
|
shipping_methods,
|
||||||
|
refund_amount,
|
||||||
|
shipping_address,
|
||||||
|
shipping_address_id,
|
||||||
|
no_notification,
|
||||||
|
...rest
|
||||||
|
} = data
|
||||||
|
|
||||||
|
for (const item of claim_items) {
|
||||||
|
const line = await this.lineItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.retrieve(item.item_id, {
|
||||||
|
relations: ["order", "swap", "claim_order", "tax_lines"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
line.order?.canceled_at ||
|
||||||
|
line.swap?.canceled_at ||
|
||||||
|
line.claim_order?.canceled_at
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Cannot create a claim on a canceled item.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addressId = shipping_address_id || order.shipping_address_id
|
||||||
|
if (shipping_address) {
|
||||||
|
const addressRepo = transactionManager.getCustomRepository(
|
||||||
|
this.addressRepository_
|
||||||
|
)
|
||||||
|
const created = addressRepo.create(shipping_address)
|
||||||
|
const saved = await addressRepo.save(created)
|
||||||
|
addressId = saved.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type !== ClaimType.REFUND && type !== ClaimType.REPLACE) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Claim type must be one of "refund" or "replace".`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === ClaimType.REPLACE && !additional_items?.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Claims with type "replace" must have at least one additional item.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!claim_items?.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Claims must have at least one claim item.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refund_amount && type !== ClaimType.REFUND) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Claim has type "${type}" but must be type "refund" to have a refund_amount.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let toRefund = refund_amount
|
||||||
|
if (type === ClaimType.REFUND && typeof refund_amount === "undefined") {
|
||||||
|
const lines = claim_items.map((ci) => {
|
||||||
|
const allOrderItems = order.items
|
||||||
|
|
||||||
|
if (order.swaps?.length) {
|
||||||
|
for (const swap of order.swaps) {
|
||||||
|
swap.additional_items.forEach((it) => {
|
||||||
|
if (
|
||||||
|
it.shipped_quantity ||
|
||||||
|
it.shipped_quantity === it.fulfilled_quantity
|
||||||
|
) {
|
||||||
|
allOrderItems.push(it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.claims?.length) {
|
||||||
|
for (const claim of order.claims) {
|
||||||
|
claim.additional_items.forEach((it) => {
|
||||||
|
if (
|
||||||
|
it.shipped_quantity ||
|
||||||
|
it.shipped_quantity === it.fulfilled_quantity
|
||||||
|
) {
|
||||||
|
allOrderItems.push(it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderItem = allOrderItems.find((oi) => oi.id === ci.item_id)
|
||||||
|
return {
|
||||||
|
...orderItem,
|
||||||
|
quantity: ci.quantity,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
toRefund = await this.totalsService_.getRefundTotal(
|
||||||
|
order,
|
||||||
|
lines as LineItem[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let newItems: LineItem[] = []
|
||||||
|
if (typeof additional_items !== "undefined") {
|
||||||
|
for (const item of additional_items) {
|
||||||
|
await this.inventoryService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.confirmInventory(item.variant_id, item.quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
newItems = await Promise.all(
|
||||||
|
additional_items.map((i) =>
|
||||||
|
this.lineItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.generate(i.variant_id, order.region_id, i.quantity)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const newItem of newItems) {
|
||||||
|
await this.inventoryService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.adjustInventory(newItem.variant_id, -newItem.quantity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluatedNoNotification =
|
||||||
|
no_notification !== undefined
|
||||||
|
? no_notification
|
||||||
|
: order.no_notification
|
||||||
|
|
||||||
|
const created = claimRepo.create({
|
||||||
|
shipping_address_id: addressId,
|
||||||
|
payment_status: type === ClaimType.REFUND ? "not_refunded" : "na",
|
||||||
|
refund_amount: toRefund,
|
||||||
|
type,
|
||||||
|
additional_items: newItems,
|
||||||
|
order_id: order.id,
|
||||||
|
no_notification: evaluatedNoNotification,
|
||||||
|
...rest,
|
||||||
|
} as DeepPartial<ClaimOrder>)
|
||||||
|
|
||||||
|
const result: ClaimOrder = await claimRepo.save(created)
|
||||||
|
|
||||||
|
if (result.additional_items && result.additional_items.length) {
|
||||||
|
const calcContext = this.totalsService_.getCalculationContext(order)
|
||||||
|
const lineItems = await this.lineItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.list({
|
||||||
|
id: result.additional_items.map((i) => i.id),
|
||||||
|
})
|
||||||
|
await this.taxProviderService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.createTaxLines(lineItems, calcContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shipping_methods) {
|
||||||
|
for (const method of shipping_methods) {
|
||||||
|
if (method.id) {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.updateShippingMethod(method.id, {
|
||||||
|
claim_order_id: result.id,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.createShippingMethod(
|
||||||
|
method.option_id as string,
|
||||||
|
(method as any).data,
|
||||||
|
{
|
||||||
|
claim_order_id: result.id,
|
||||||
|
price: method.price,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ci of claim_items) {
|
||||||
|
await this.claimItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.create({
|
||||||
|
...ci,
|
||||||
|
claim_order_id: result.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (return_shipping) {
|
||||||
|
await this.returnService_.withTransaction(transactionManager).create({
|
||||||
|
order_id: order.id,
|
||||||
|
claim_order_id: result.id,
|
||||||
|
items: claim_items.map((ci) => ({
|
||||||
|
item_id: ci.item_id,
|
||||||
|
quantity: ci.quantity,
|
||||||
|
metadata: (ci as any).metadata,
|
||||||
|
})),
|
||||||
|
shipping_method: return_shipping,
|
||||||
|
no_notification: evaluatedNoNotification,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(ClaimService.Events.CREATED, {
|
||||||
|
id: result.id,
|
||||||
|
no_notification: result.no_notification,
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id - the object containing all data required to create a claim
|
||||||
|
* @param config - config object
|
||||||
|
* @param config.metadata - config metadata
|
||||||
|
* @param config.no_notification - config no notification
|
||||||
|
* @return created claim
|
||||||
|
*/
|
||||||
|
async createFulfillment(
|
||||||
|
id: string,
|
||||||
|
config: {
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
no_notification?: boolean
|
||||||
|
} = {
|
||||||
|
metadata: {},
|
||||||
|
}
|
||||||
|
): Promise<ClaimOrder> {
|
||||||
|
const { metadata, no_notification } = config
|
||||||
|
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claim = await this.retrieve(id, {
|
||||||
|
relations: [
|
||||||
|
"additional_items",
|
||||||
|
"additional_items.tax_lines",
|
||||||
|
"shipping_methods",
|
||||||
|
"shipping_methods.tax_lines",
|
||||||
|
"shipping_address",
|
||||||
|
"order",
|
||||||
|
"order.billing_address",
|
||||||
|
"order.discounts",
|
||||||
|
"order.discounts.rule",
|
||||||
|
"order.payments",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (claim.canceled_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Canceled claim cannot be fulfilled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = claim.order
|
||||||
|
|
||||||
|
if (
|
||||||
|
claim.fulfillment_status !== "not_fulfilled" &&
|
||||||
|
claim.fulfillment_status !== "canceled"
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"The claim has already been fulfilled."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim.type !== "replace") {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Claims with the type "${claim.type}" can not be fulfilled.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!claim.shipping_methods?.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Cannot fulfill a claim without a shipping method."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluatedNoNotification =
|
||||||
|
no_notification !== undefined
|
||||||
|
? no_notification
|
||||||
|
: claim.no_notification
|
||||||
|
|
||||||
|
const fulfillments = await this.fulfillmentService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.createFulfillment(
|
||||||
|
{
|
||||||
|
...claim,
|
||||||
|
email: order.email,
|
||||||
|
payments: order.payments,
|
||||||
|
discounts: order.discounts,
|
||||||
|
currency_code: order.currency_code,
|
||||||
|
tax_rate: order.tax_rate,
|
||||||
|
region_id: order.region_id,
|
||||||
|
display_id: order.display_id,
|
||||||
|
billing_address: order.billing_address,
|
||||||
|
items: claim.additional_items,
|
||||||
|
shipping_methods: claim.shipping_methods,
|
||||||
|
is_claim: true,
|
||||||
|
no_notification: evaluatedNoNotification,
|
||||||
|
},
|
||||||
|
claim.additional_items.map((i) => ({
|
||||||
|
item_id: i.id,
|
||||||
|
quantity: i.quantity,
|
||||||
|
})),
|
||||||
|
{ claim_order_id: id, metadata }
|
||||||
|
)
|
||||||
|
|
||||||
|
let successfullyFulfilledItems: FulfillmentItem[] = []
|
||||||
|
for (const fulfillment of fulfillments) {
|
||||||
|
successfullyFulfilledItems = successfullyFulfilledItems.concat(
|
||||||
|
fulfillment.items
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.FULFILLED
|
||||||
|
|
||||||
|
for (const item of claim.additional_items) {
|
||||||
|
const fulfillmentItem = successfullyFulfilledItems.find(
|
||||||
|
(successfullyFulfilledItem) => {
|
||||||
|
return successfullyFulfilledItem.item_id === item.id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (fulfillmentItem) {
|
||||||
|
const fulfilledQuantity =
|
||||||
|
(item.fulfilled_quantity || 0) + fulfillmentItem.quantity
|
||||||
|
|
||||||
|
// Update the fulfilled quantity
|
||||||
|
await this.lineItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.update(item.id, {
|
||||||
|
fulfilled_quantity: fulfilledQuantity,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (item.quantity !== fulfilledQuantity) {
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.REQUIRES_ACTION
|
||||||
|
}
|
||||||
|
} else if (item.quantity !== item.fulfilled_quantity) {
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.REQUIRES_ACTION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
const claimOrder = await claimRepo.save(claim)
|
||||||
|
|
||||||
|
for (const fulfillment of fulfillments) {
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(ClaimService.Events.FULFILLMENT_CREATED, {
|
||||||
|
id: id,
|
||||||
|
fulfillment_id: fulfillment.id,
|
||||||
|
no_notification: claim.no_notification,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return claimOrder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelFulfillment(fulfillmentId: string): Promise<ClaimOrder> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const canceled = await this.fulfillmentService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.cancelFulfillment(fulfillmentId)
|
||||||
|
|
||||||
|
if (!canceled.claim_order_id) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Fufillment not related to a claim`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const claim = await this.retrieve(canceled.claim_order_id)
|
||||||
|
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.CANCELED
|
||||||
|
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
return claimRepo.save(claim)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async processRefund(id: string): Promise<ClaimOrder> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claim = await this.retrieve(id, {
|
||||||
|
relations: ["order", "order.payments"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (claim.canceled_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Canceled claim cannot be processed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim.type !== "refund") {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Claim must have type "refund" to create a refund.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim.refund_amount) {
|
||||||
|
await this.paymentProviderService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.refundPayment(claim.order.payments, claim.refund_amount, "claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
claim.payment_status = ClaimPaymentStatus.REFUNDED
|
||||||
|
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
const claimOrder = await claimRepo.save(claim)
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(ClaimService.Events.REFUND_PROCESSED, {
|
||||||
|
id,
|
||||||
|
no_notification: claimOrder.no_notification,
|
||||||
|
})
|
||||||
|
|
||||||
|
return claimOrder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createShipment(
|
||||||
|
id: string,
|
||||||
|
fulfillmentId: string,
|
||||||
|
trackingLinks: { tracking_number: string }[] = [],
|
||||||
|
config = {
|
||||||
|
metadata: {},
|
||||||
|
no_notification: undefined,
|
||||||
|
}
|
||||||
|
): Promise<ClaimOrder> {
|
||||||
|
const { metadata, no_notification } = config
|
||||||
|
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claim = await this.retrieve(id, {
|
||||||
|
relations: ["additional_items"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (claim.canceled_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Canceled claim cannot be fulfilled as shipped"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const evaluatedNoNotification =
|
||||||
|
no_notification !== undefined
|
||||||
|
? no_notification
|
||||||
|
: claim.no_notification
|
||||||
|
|
||||||
|
const shipment = await this.fulfillmentService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.createShipment(fulfillmentId, trackingLinks, {
|
||||||
|
metadata,
|
||||||
|
no_notification: evaluatedNoNotification,
|
||||||
|
})
|
||||||
|
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.SHIPPED
|
||||||
|
|
||||||
|
for (const additionalItem of claim.additional_items) {
|
||||||
|
const shipped = shipment.items.find(
|
||||||
|
(si) => si.item_id === additionalItem.id
|
||||||
|
)
|
||||||
|
if (shipped) {
|
||||||
|
const shippedQty =
|
||||||
|
(additionalItem.shipped_quantity || 0) + shipped.quantity
|
||||||
|
await this.lineItemService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.update(additionalItem.id, {
|
||||||
|
shipped_quantity: shippedQty,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shippedQty !== additionalItem.quantity) {
|
||||||
|
claim.fulfillment_status =
|
||||||
|
ClaimFulfillmentStatus.PARTIALLY_SHIPPED
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
additionalItem.shipped_quantity !== additionalItem.quantity
|
||||||
|
) {
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.PARTIALLY_SHIPPED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
const claimOrder = await claimRepo.save(claim)
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(ClaimService.Events.SHIPMENT_CREATED, {
|
||||||
|
id,
|
||||||
|
fulfillment_id: shipment.id,
|
||||||
|
no_notification: evaluatedNoNotification,
|
||||||
|
})
|
||||||
|
|
||||||
|
return claimOrder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancel(id: string): Promise<ClaimOrder> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claim = await this.retrieve(id, {
|
||||||
|
relations: ["return_order", "fulfillments", "order", "order.refunds"],
|
||||||
|
})
|
||||||
|
if (claim.refund_amount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Claim with a refund cannot be canceled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim.fulfillments) {
|
||||||
|
for (const f of claim.fulfillments) {
|
||||||
|
if (!f.canceled_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"All fulfillments must be canceled before the claim can be canceled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim.return_order && claim.return_order.status !== "canceled") {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Return must be canceled before the claim can be canceled"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
claim.fulfillment_status = ClaimFulfillmentStatus.CANCELED
|
||||||
|
claim.canceled_at = new Date()
|
||||||
|
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
const claimOrder = await claimRepo.save(claim)
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.emit(ClaimService.Events.CANCELED, {
|
||||||
|
id: claimOrder.id,
|
||||||
|
no_notification: claimOrder.no_notification,
|
||||||
|
})
|
||||||
|
|
||||||
|
return claimOrder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param selector - the query object for find
|
||||||
|
* @param config - the config object containing query settings
|
||||||
|
* @return the result of the find operation
|
||||||
|
*/
|
||||||
|
async list(
|
||||||
|
selector,
|
||||||
|
config: FindConfig<ClaimOrder> = {
|
||||||
|
skip: 0,
|
||||||
|
take: 50,
|
||||||
|
order: { created_at: "DESC" },
|
||||||
|
}
|
||||||
|
): Promise<ClaimOrder[]> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
const query = buildQuery<ClaimOrder>(selector, config)
|
||||||
|
return await claimRepo.find(query)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an order by id.
|
||||||
|
* @param id - id of the claim order to retrieve
|
||||||
|
* @param config - the config object containing query settings
|
||||||
|
* @return the order document
|
||||||
|
*/
|
||||||
|
async retrieve(
|
||||||
|
id: string,
|
||||||
|
config: FindConfig<ClaimOrder> = {}
|
||||||
|
): Promise<ClaimOrder> {
|
||||||
|
return await this.atomicPhase_(
|
||||||
|
async (transactionManager: EntityManager) => {
|
||||||
|
const claimRepo = transactionManager.getCustomRepository(
|
||||||
|
this.claimRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = buildQuery<ClaimOrder>({ id }, config)
|
||||||
|
const claim = await claimRepo.findOne(query)
|
||||||
|
|
||||||
|
if (!claim) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Claim with ${id} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return claim
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { ClaimType, Order } from "../models"
|
||||||
|
import { AddressPayload } from "./common"
|
||||||
|
|
||||||
|
export type ClaimTypeValue = `${ClaimType}`
|
||||||
|
|
||||||
|
export enum ClaimItemReason {
|
||||||
|
missing_item = "missing_item",
|
||||||
|
wrong_item = "wrong_item",
|
||||||
|
production_failure = "production_failure",
|
||||||
|
other = "other",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClaimItemReasonValue = `${ClaimItemReason}`
|
||||||
|
|
||||||
|
/* CREATE INPUT */
|
||||||
|
|
||||||
|
export type CreateClaimInput = {
|
||||||
|
type: ClaimTypeValue
|
||||||
|
claim_items: CreateClaimItemInput[]
|
||||||
|
return_shipping?: CreateClaimReturnShippingInput
|
||||||
|
additional_items?: CreateClaimItemAdditionalItemInput[]
|
||||||
|
shipping_methods?: CreateClaimShippingMethodInput[]
|
||||||
|
refund_amount?: number
|
||||||
|
shipping_address?: AddressPayload
|
||||||
|
no_notification?: boolean
|
||||||
|
metadata?: object
|
||||||
|
order: Order
|
||||||
|
claim_order_id?: string
|
||||||
|
shipping_address_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateClaimReturnShippingInput = {
|
||||||
|
option_id?: string
|
||||||
|
price?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateClaimShippingMethodInput = {
|
||||||
|
id?: string
|
||||||
|
option_id?: string
|
||||||
|
price?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateClaimItemInput = {
|
||||||
|
item_id: string
|
||||||
|
quantity: number
|
||||||
|
note?: string
|
||||||
|
reason?: ClaimItemReasonValue
|
||||||
|
tags?: string[]
|
||||||
|
images?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateClaimItemAdditionalItemInput = {
|
||||||
|
variant_id: string
|
||||||
|
quantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UPDATE INPUT */
|
||||||
|
|
||||||
|
export type UpdateClaimInput = {
|
||||||
|
claim_items?: UpdateClaimItemInput[]
|
||||||
|
shipping_methods?: UpdateClaimShippingMethodInput[]
|
||||||
|
no_notification?: boolean
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateClaimShippingMethodInput = {
|
||||||
|
id?: string
|
||||||
|
option_id?: string
|
||||||
|
price?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateClaimItemInput = {
|
||||||
|
id: string
|
||||||
|
note?: string
|
||||||
|
reason?: string
|
||||||
|
images: UpdateClaimItemImageInput[]
|
||||||
|
tags: UpdateClaimItemTagInput[]
|
||||||
|
metadata?: object
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateClaimItemImageInput = {
|
||||||
|
id?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateClaimItemTagInput = {
|
||||||
|
id?: string
|
||||||
|
value?: string
|
||||||
|
}
|
||||||
Reference in new issue
Block a user