Feat/nested return reasons (#418)
* api endpoints for nested return_reasons * add nested return reasons to database * add parent reason to update * integration tests * add children relation * integration tests for nested returns and failing doubly nesting returns * add delete-route and nested relations * delete return reason route * doubly nested return reason creation check and deletion * nested return reasons migration * list only parent reasons * removed null filter * remove empty migration * add return reason filter to get list of categories with children * removed console log * corrected delete route * return reason testing * return reasons query * listREasonsFromIDs * create return testing * listReasonsFromIds * return reason tests * failing if returnreason has child on return * integration tests * cascading deletes on return reasons * more elegant checking for children of return reasons when creating a return * remove console.log * pr adjust Co-authored-by: Philip Korsholm <phko@MacBook-Pro.localdomain>
This commit is contained in:
co-authored by
Philip Korsholm
parent
4db860f9ab
commit
42cdfde6d9
@@ -39,6 +39,7 @@ export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
value: Validator.string().required(),
|
||||
label: Validator.string().required(),
|
||||
parent_return_reason_id: Validator.string().optional(),
|
||||
description: Validator.string()
|
||||
.optional()
|
||||
.allow(""),
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @oas [delete] /return-reason/{id}
|
||||
* operationId: "DeleteReturnReason"
|
||||
* summary: "Delete a return reason"
|
||||
* description: "Deletes a return reason."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the return reason
|
||||
* tags:
|
||||
* - Return Reason
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: The id of the deleted return reason
|
||||
* object:
|
||||
* type: string
|
||||
* description: The type of the object that was deleted.
|
||||
* deleted:
|
||||
* type: boolean
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const returnReasonService = req.scope.resolve("returnReasonService")
|
||||
await returnReasonService.delete(id)
|
||||
|
||||
res.json({
|
||||
id: id,
|
||||
object: "return_reason",
|
||||
deleted: true,
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,11 @@ export default app => {
|
||||
*/
|
||||
route.post("/:id", middlewares.wrap(require("./update-reason").default))
|
||||
|
||||
/**
|
||||
* Delete a reason
|
||||
*/
|
||||
route.delete("/:id", middlewares.wrap(require("./delete-reason").default))
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -33,10 +38,14 @@ export const defaultFields = [
|
||||
"id",
|
||||
"value",
|
||||
"label",
|
||||
"parent_return_reason_id",
|
||||
"description",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const defaultRelations = []
|
||||
export const defaultRelations = [
|
||||
"parent_return_reason",
|
||||
"return_reason_children",
|
||||
]
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async (req, res) => {
|
||||
try {
|
||||
const returnReasonService = req.scope.resolve("returnReasonService")
|
||||
|
||||
const query = {}
|
||||
const query = { parent_return_reason_id: null }
|
||||
const data = await returnReasonService.list(query, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
|
||||
@@ -42,6 +42,7 @@ export default async (req, res) => {
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
label: Validator.string().optional(),
|
||||
parent_return_reason_id: Validator.string().optional(),
|
||||
description: Validator.string()
|
||||
.optional()
|
||||
.allow(""),
|
||||
|
||||
@@ -23,10 +23,14 @@ export const defaultFields = [
|
||||
"id",
|
||||
"value",
|
||||
"label",
|
||||
"parent_return_reason_id",
|
||||
"description",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const defaultRelations = []
|
||||
export const defaultRelations = [
|
||||
"parent_return_reason",
|
||||
"return_reason_children",
|
||||
]
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async (req, res) => {
|
||||
try {
|
||||
const returnReasonService = req.scope.resolve("returnReasonService")
|
||||
|
||||
const query = {}
|
||||
const query = { parent_return_reason_id: null}
|
||||
const data = await returnReasonService.list(query, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class nestedReturnReasons1631800727788 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return_reason" ADD "parent_return_reason_id" character varying`
|
||||
|
||||
)
|
||||
await queryRunner.query(`ALTER TABLE "return_reason" ADD CONSTRAINT "FK_2250c5d9e975987ab212f61a657" FOREIGN KEY ("parent_return_reason_id") REFERENCES "return_reason"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return_reason" DROP COLUMN "parent_return_reason_id"`
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
PrimaryColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
|
||||
@@ -26,6 +29,21 @@ export class ReturnReason {
|
||||
@Column({ nullable: true })
|
||||
description: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
parent_return_reason_id: string
|
||||
|
||||
@ManyToOne(() => ReturnReason, {cascade: ['soft-remove']}
|
||||
)
|
||||
@JoinColumn({ name: "parent_return_reason_id" })
|
||||
parent_return_reason: ReturnReason
|
||||
|
||||
@OneToMany(
|
||||
() => ReturnReason,
|
||||
return_reason => return_reason.parent_return_reason,
|
||||
{ cascade: ["insert", 'soft-remove'] }
|
||||
)
|
||||
return_reason_children: ReturnReason[]
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { In } from "typeorm"
|
||||
|
||||
class ReturnReasonService extends BaseService {
|
||||
constructor({ manager, returnReasonRepository }) {
|
||||
@@ -32,6 +32,17 @@ class ReturnReasonService extends BaseService {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
|
||||
if (data.parent_return_reason_id && data.parent_return_reason_id !== "") {
|
||||
const parentReason = await this.retrieve(data.parent_return_reason_id)
|
||||
|
||||
if (parentReason.parent_return_reason_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Doubly nested return reasons is not supported"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const created = rrRepo.create(data)
|
||||
|
||||
const result = await rrRepo.save(created)
|
||||
@@ -44,14 +55,20 @@ class ReturnReasonService extends BaseService {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
const reason = await this.retrieve(id)
|
||||
|
||||
if ("description" in data) {
|
||||
const { description, label, parent_return_reason_id } = data
|
||||
|
||||
if (description) {
|
||||
reason.description = data.description
|
||||
}
|
||||
|
||||
if ("label" in data) {
|
||||
if (label) {
|
||||
reason.label = data.label
|
||||
}
|
||||
|
||||
if (parent_return_reason_id) {
|
||||
reason.parent_return_reason_id = parent_return_reason_id
|
||||
}
|
||||
|
||||
await rrRepo.save(reason)
|
||||
|
||||
return reason
|
||||
@@ -92,6 +109,25 @@ class ReturnReasonService extends BaseService {
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
async delete(returnReasonId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
|
||||
// We include the relation 'return_reason_children' to enable cascading deletes of return reasons if a parent is removed
|
||||
const reason = await this.retrieve(returnReasonId, {
|
||||
relations: ["return_reason_children"],
|
||||
})
|
||||
|
||||
if (!reason) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await rrRepo.softRemove(reason)
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ReturnReasonService
|
||||
|
||||
@@ -374,6 +374,18 @@ class ReturnService extends BaseService {
|
||||
refund_amount: Math.floor(toRefund),
|
||||
}
|
||||
|
||||
const returnReasons = await this.returnReasonService_.list(
|
||||
{ id: [...returnLines.map(rl => rl.reason_id)] },
|
||||
{ relations: ["return_reason_children"] }
|
||||
)
|
||||
|
||||
if (returnReasons.some(rr => rr.return_reason_children?.length > 0)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Cannot apply return reason category"
|
||||
)
|
||||
}
|
||||
|
||||
const rItemRepo = manager.getCustomRepository(this.returnItemRepository_)
|
||||
returnObject.items = returnLines.map(i =>
|
||||
rItemRepo.create({
|
||||
|
||||
Reference in New Issue
Block a user