PR Review fixes

This commit is contained in:
olivermrbl
2021-06-07 15:23:41 +02:00
parent e79b8f6494
commit c0f2d629ab
18 changed files with 195 additions and 91 deletions
@@ -75,7 +75,7 @@ describe("/admin/draft-orders", () => {
const payload = { const payload = {
email: "oli@test.dk", email: "oli@test.dk",
shipping_address_id: "oli-shipping", shipping_address: "oli-shipping",
items: [ items: [
{ {
variant_id: "test-variant", variant_id: "test-variant",
@@ -109,7 +109,7 @@ describe("/admin/draft-orders", () => {
const payload = { const payload = {
email: "oli@test.dk", email: "oli@test.dk",
shipping_address_id: "oli-shipping", shipping_address: "oli-shipping",
items: [ items: [
{ {
variant_id: "test-variant", variant_id: "test-variant",
@@ -148,7 +148,7 @@ describe("/admin/draft-orders", () => {
const payload = { const payload = {
email: "oli@test.dk", email: "oli@test.dk",
shipping_address_id: "oli-shipping", shipping_address: "oli-shipping",
discounts: [{ code: "TEST" }], discounts: [{ code: "TEST" }],
items: [ items: [
{ {
@@ -302,6 +302,7 @@ describe("/admin/draft-orders", () => {
); );
// expect draft order to be complete // expect draft order to be complete
expect(updatedDraftOrder.data.draft_order.status).toEqual("completed"); expect(updatedDraftOrder.data.draft_order.status).toEqual("completed");
expect(updatedDraftOrder.data.draft_order.completed_at).not.toEqual(null);
}); });
}); });
describe("GET /admin/draft-orders", () => { describe("GET /admin/draft-orders", () => {
@@ -67,7 +67,7 @@ describe("/admin/orders", () => {
await manager.query(`DELETE FROM "user"`); await manager.query(`DELETE FROM "user"`);
}); });
it("creates a cart", async () => { it("gets orders", async () => {
const api = useApi(); const api = useApi();
const response = await api const response = await api
@@ -20,6 +20,7 @@ describe("/store/carts", () => {
await manager.query(`DELETE FROM "shipping_method"`); await manager.query(`DELETE FROM "shipping_method"`);
await manager.query(`DELETE FROM "shipping_option"`); await manager.query(`DELETE FROM "shipping_option"`);
await manager.query(`DELETE FROM "cart"`); await manager.query(`DELETE FROM "cart"`);
await manager.query(`DELETE FROM "address"`);
await manager.query(`DELETE FROM "customer"`); await manager.query(`DELETE FROM "customer"`);
await manager.query( await manager.query(
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'` `UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
@@ -143,6 +144,41 @@ describe("/store/carts", () => {
expect(response.status).toEqual(200); expect(response.status).toEqual(200);
}); });
it("updates address using string id", async () => {
const api = useApi();
const response = await api.post("/store/carts/test-cart", {
billing_address: "test-general-address",
shipping_address: "test-general-address",
});
expect(response.data.cart.shipping_address_id).toEqual(
"test-general-address"
);
expect(response.data.cart.billing_address_id).toEqual(
"test-general-address"
);
expect(response.status).toEqual(200);
});
it("updates address", async () => {
const api = useApi();
const response = await api.post("/store/carts/test-cart", {
shipping_address: {
first_name: "clark",
last_name: "kent",
address_1: "5th avenue",
city: "nyc",
country_code: "us",
postal_code: "something",
},
});
expect(response.data.cart.shipping_address.first_name).toEqual("clark");
expect(response.status).toEqual(200);
});
it("adds free shipping to cart then removes it again", async () => { it("adds free shipping to cart then removes it again", async () => {
const api = useApi(); const api = useApi();
@@ -45,7 +45,9 @@ describe("/store/carts (draft-orders)", () => {
await manager.query(`DELETE FROM "product"`); await manager.query(`DELETE FROM "product"`);
await manager.query(`DELETE FROM "shipping_method"`); await manager.query(`DELETE FROM "shipping_method"`);
await manager.query(`DELETE FROM "shipping_option"`); await manager.query(`DELETE FROM "shipping_option"`);
await manager.query(`UPDATE "discount" SET rule_id=NULL`);
await manager.query(`DELETE FROM "discount"`); await manager.query(`DELETE FROM "discount"`);
await manager.query(`DELETE FROM "discount_rule"`);
await manager.query(`DELETE FROM "payment_provider"`); await manager.query(`DELETE FROM "payment_provider"`);
await manager.query(`DELETE FROM "payment_session"`); await manager.query(`DELETE FROM "payment_session"`);
await manager.query(`UPDATE "payment" SET order_id=NULL`); await manager.query(`UPDATE "payment" SET order_id=NULL`);
@@ -64,6 +66,7 @@ describe("/store/carts (draft-orders)", () => {
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'de'` `UPDATE "country" SET region_id=NULL WHERE iso_2 = 'de'`
); );
await manager.query(`DELETE FROM "region"`); await manager.query(`DELETE FROM "region"`);
await manager.query(`DELETE FROM "user"`);
}); });
it("completes a cart for a draft order thereby creating an order for the draft order", async () => { it("completes a cart for a draft order thereby creating an order for the draft order", async () => {
@@ -7,6 +7,7 @@ const {
ShippingProfile, ShippingProfile,
ShippingOption, ShippingOption,
ShippingMethod, ShippingMethod,
Address,
} = require("@medusajs/medusa"); } = require("@medusajs/medusa");
module.exports = async (connection, data = {}) => { module.exports = async (connection, data = {}) => {
@@ -16,6 +17,12 @@ module.exports = async (connection, data = {}) => {
type: "default", type: "default",
}); });
await manager.insert(Address, {
id: "test-general-address",
first_name: "superman",
country_code: "us",
});
const r = manager.create(Region, { const r = manager.create(Region, {
id: "test-region", id: "test-region",
name: "Test Region", name: "Test Region",
@@ -12,6 +12,7 @@ const {
DraftOrder, DraftOrder,
Discount, Discount,
DiscountRule, DiscountRule,
Payment,
} = require("@medusajs/medusa"); } = require("@medusajs/medusa");
module.exports = async (connection, data = {}) => { module.exports = async (connection, data = {}) => {
@@ -189,6 +190,19 @@ module.exports = async (connection, data = {}) => {
metadata: { draft_order_id: "test-draft-order" }, metadata: { draft_order_id: "test-draft-order" },
}); });
const pay = manager.create(Payment, {
id: "test-payment",
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test-pay",
data: {},
});
await manager.save(pay);
c.payment = pay;
await manager.save(c); await manager.save(c);
await manager.insert(PaymentSession, { await manager.insert(PaymentSession, {
@@ -197,12 +211,12 @@ module.exports = async (connection, data = {}) => {
provider_id: "test-pay", provider_id: "test-pay",
is_selected: true, is_selected: true,
data: {}, data: {},
status: "pending", status: "authorized",
}); });
const draftOrder = manager.create(DraftOrder, { const draftOrder = manager.create(DraftOrder, {
id: "test-draft-order", id: "test-draft-order",
status: "awaiting", status: "open",
display_id: 4, display_id: 4,
cart_id: "test-cart", cart_id: "test-cart",
customer_id: "oli-test", customer_id: "oli-test",
+22 -12
View File
@@ -2,19 +2,29 @@ import Joi from "joi"
Joi.objectId = require("joi-objectid")(Joi) Joi.objectId = require("joi-objectid")(Joi)
// if address is a string, we assume that it is an id
Joi.address = () => { Joi.address = () => {
return Joi.object().keys({ return Joi.alternatives().try(
first_name: Joi.string().required(), Joi.string(),
last_name: Joi.string().required(), Joi.object().keys({
address_1: Joi.string().required(), first_name: Joi.string().required(),
address_2: Joi.string().allow(null), last_name: Joi.string().required(),
city: Joi.string().required(), address_1: Joi.string().required(),
country_code: Joi.string().required(), address_2: Joi.string()
province: Joi.string().allow(null), .allow(null, "")
postal_code: Joi.string().required(), .optional(),
phone: Joi.string().optional(), city: Joi.string().required(),
metadata: Joi.object().allow(null), country_code: Joi.string().required(),
}) province: Joi.string()
.allow(null, "")
.optional(),
postal_code: Joi.string().required(),
phone: Joi.string().optional(),
metadata: Joi.object()
.allow(null, {})
.optional(),
})
)
} }
Joi.dateFilter = () => { Joi.dateFilter = () => {
@@ -25,12 +25,6 @@ import { defaultFields, defaultRelations } from "."
* description: "The Address to be used for shipping." * description: "The Address to be used for shipping."
* anyOf: * anyOf:
* - $ref: "#/components/schemas/address" * - $ref: "#/components/schemas/address"
* billing_address_id:
* description: The id of an existing billing Address
* type: string
* shipping_address_id:
* description: The id of an existing shipping Address
* type: string
* items: * items:
* description: The Line Items that have been received. * description: The Line Items that have been received.
* type: array * type: array
@@ -98,15 +92,13 @@ import { defaultFields, defaultRelations } from "."
export default async (req, res) => { export default async (req, res) => {
const schema = Validator.object().keys({ const schema = Validator.object().keys({
status: Validator.string() status: Validator.string()
.valid("open", "awaiting", "completed") .valid("open", "completed")
.optional(), .optional(),
email: Validator.string() email: Validator.string()
.email() .email()
.required(), .required(),
billing_address: Validator.address().optional(), billing_address: Validator.address().optional(),
shipping_address: Validator.address().optional(), shipping_address: Validator.address().optional(),
billing_address_id: Validator.string().optional(),
shipping_address_id: Validator.string().optional(),
items: Validator.array() items: Validator.array()
.items({ .items({
variant_id: Validator.string() variant_id: Validator.string()
@@ -66,10 +66,7 @@ export default async (req, res) => {
.withTransaction(manager) .withTransaction(manager)
.retrieve(id, { select: defaultFields, relations: ["cart"] }) .retrieve(id, { select: defaultFields, relations: ["cart"] })
if ( if (draftOrder.status === "completed") {
draftOrder.status === "completed" ||
draftOrder.status === "awaiting"
) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders" "You are only allowed to update open draft orders"
@@ -35,10 +35,7 @@ export default async (req, res) => {
.withTransaction(manager) .withTransaction(manager)
.retrieve(id, { select: defaultFields }) .retrieve(id, { select: defaultFields })
if ( if (draftOrder.status === "completed") {
draftOrder.status === "completed" ||
draftOrder.status === "awaiting"
) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders" "You are only allowed to update open draft orders"
@@ -67,11 +67,6 @@ export default async (req, res) => {
code: Validator.string(), code: Validator.string(),
}) })
.optional(), .optional(),
gift_cards: Validator.array()
.items({
code: Validator.string(),
})
.optional(),
customer_id: Validator.string().optional(), customer_id: Validator.string().optional(),
}) })
@@ -86,7 +81,7 @@ export default async (req, res) => {
const draftOrder = await draftOrderService.retrieve(id) const draftOrder = await draftOrderService.retrieve(id)
if (draftOrder.status === "completed" || draftOrder.status === "awaiting") { if (draftOrder.status === "completed") {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders" "You are only allowed to update open draft orders"
@@ -64,10 +64,7 @@ export default async (req, res) => {
relations: ["cart", "cart.items"], relations: ["cart", "cart.items"],
}) })
if ( if (draftOrder.status === "completed") {
draftOrder.status === "completed" ||
draftOrder.status === "awaiting"
) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"You are only allowed to update open draft orders" "You are only allowed to update open draft orders"
@@ -75,8 +75,8 @@ export default async (req, res) => {
email: Validator.string() email: Validator.string()
.email() .email()
.optional(), .optional(),
billing_address: Validator.object().optional(), billing_address: Validator.address().optional(),
shipping_address: Validator.object().optional(), shipping_address: Validator.address().optional(),
gift_cards: Validator.array() gift_cards: Validator.array()
.items({ .items({
code: Validator.string(), code: Validator.string(),
@@ -0,0 +1,28 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class draftOrderCompletedAt1623063141210 implements MigrationInterface {
name = 'draftOrderCompletedAt1623063141210'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "draft_order" ADD "completed_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
await queryRunner.query(`ALTER TYPE "public"."draft_order_status_enum" RENAME TO "draft_order_status_enum_old"`);
await queryRunner.query(`CREATE TYPE "draft_order_status_enum" AS ENUM('open', 'completed')`);
await queryRunner.query(`ALTER TABLE "draft_order" ALTER COLUMN "status" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "draft_order" ALTER COLUMN "status" TYPE "draft_order_status_enum" USING "status"::"text"::"draft_order_status_enum"`);
await queryRunner.query(`ALTER TABLE "draft_order" ALTER COLUMN "status" SET DEFAULT 'open'`);
await queryRunner.query(`DROP TYPE "draft_order_status_enum_old"`);
await queryRunner.query(`COMMENT ON COLUMN "draft_order"."status" IS NULL`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`COMMENT ON COLUMN "draft_order"."status" IS NULL`);
await queryRunner.query(`CREATE TYPE "draft_order_status_enum_old" AS ENUM('open', 'awaiting', 'completed')`);
await queryRunner.query(`ALTER TABLE "draft_order" ALTER COLUMN "status" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "draft_order" ALTER COLUMN "status" TYPE "draft_order_status_enum_old" USING "status"::"text"::"draft_order_status_enum_old"`);
await queryRunner.query(`ALTER TABLE "draft_order" ALTER COLUMN "status" SET DEFAULT 'open'`);
await queryRunner.query(`DROP TYPE "draft_order_status_enum"`);
await queryRunner.query(`ALTER TYPE "draft_order_status_enum_old" RENAME TO "draft_order_status_enum"`);
await queryRunner.query(`ALTER TABLE "draft_order" DROP COLUMN "completed_at"`);
}
}
+6 -2
View File
@@ -17,7 +17,6 @@ import { Order } from "./order"
enum DraftOrderStatus { enum DraftOrderStatus {
OPEN = "open", OPEN = "open",
AWAITING = "awaiting",
COMPLETED = "completed", COMPLETED = "completed",
} }
@@ -59,6 +58,9 @@ export class DraftOrder {
@UpdateDateColumn({ type: "timestamptz" }) @UpdateDateColumn({ type: "timestamptz" })
updated_at: Date updated_at: Date
@UpdateDateColumn({ type: "timestamptz" })
completed_at: Date
@Column({ type: "jsonb", nullable: true }) @Column({ type: "jsonb", nullable: true })
metadata: any metadata: any
@@ -85,7 +87,6 @@ export class DraftOrder {
* type: string * type: string
* enum: * enum:
* - open * - open
* - awaiting
* - completed * - completed
* display_id: * display_id:
* type: string * type: string
@@ -111,6 +112,9 @@ export class DraftOrder {
* deleted_at: * deleted_at:
* type: string * type: string
* format: date-time * format: date-time
* completed_at:
* type: string
* format: date-time
* metadata: * metadata:
* type: object * type: object
* idempotency_key: * idempotency_key:
+59 -40
View File
@@ -306,7 +306,12 @@ class CartService extends BaseService {
const regCountries = region.countries.map(({ iso_2 }) => iso_2) const regCountries = region.countries.map(({ iso_2 }) => iso_2)
if (!data.shipping_address && !data.shipping_address_id) { if (data.shipping_address && typeof data.shipping_address === `string`) {
const addr = await addressRepo.findOne(data.shipping_address)
data.shipping_address = addr
}
if (!data.shipping_address) {
if (region.countries.length === 1) { if (region.countries.length === 1) {
// Preselect the country if the region only has 1 // Preselect the country if the region only has 1
// and create address entity // and create address entity
@@ -315,22 +320,11 @@ class CartService extends BaseService {
}) })
} }
} else { } else {
if (data.shipping_address) { if (!regCountries.includes(data.shipping_address.country_code)) {
if (!regCountries.includes(data.shipping_address.country_code)) { throw new MedusaError(
throw new MedusaError( MedusaError.Types.NOT_ALLOWED,
MedusaError.Types.NOT_ALLOWED, "Shipping country not in region"
"Shipping country not in region" )
)
}
}
if (data.shipping_address_id) {
const addr = await addressRepo.findOne(data.shipping_address_id)
if (!regCountries.includes(addr.country_code)) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Shipping country not in region"
)
}
} }
} }
@@ -782,20 +776,32 @@ class CartService extends BaseService {
* @param {object} address - the value to set the billing address to * @param {object} address - the value to set the billing address to
* @return {Promise} the result of the update operation * @return {Promise} the result of the update operation
*/ */
async updateBillingAddress_(cart, address, addrRepo) { async updateBillingAddress_(cart, addressOrId, addrRepo) {
address.country_code = address.country_code.toLowerCase() if (typeof addressOrId === `string`) {
if (cart.billing_address_id) { addressOrId = await addrRepo.findOne({
const addr = await addrRepo.findOne({ where: { id: addressOrId },
where: { id: cart.billing_address_id },
}) })
}
await addrRepo.save({ ...addr, ...address }) addressOrId.country_code = addressOrId.country_code.toLowerCase()
if (addressOrId.id) {
cart.billing_address_id = addressOrId.id
cart.billing_address = addressOrId
} else { } else {
const created = addrRepo.create({ if (cart.billing_address_id) {
...address, const addr = await addrRepo.findOne({
}) where: { id: cart.billing_address_id },
})
cart.billing_address = created await addrRepo.save({ ...addr, ...addressOrId })
} else {
const created = addrRepo.create({
...addressOrId,
})
cart.billing_address = created
}
} }
} }
@@ -805,11 +811,19 @@ class CartService extends BaseService {
* @param {object} address - the value to set the shipping address to * @param {object} address - the value to set the shipping address to
* @return {Promise} the result of the update operation * @return {Promise} the result of the update operation
*/ */
async updateShippingAddress_(cart, address, addrRepo) { async updateShippingAddress_(cart, addressOrId, addrRepo) {
address.country_code = address.country_code.toLowerCase() if (typeof addressOrId === `string`) {
addressOrId = await addrRepo.findOne({
where: { id: addressOrId },
})
}
addressOrId.country_code = addressOrId.country_code.toLowerCase()
if ( if (
!cart.region.countries.find(({ iso_2 }) => address.country_code === iso_2) !cart.region.countries.find(
({ iso_2 }) => addressOrId.country_code === iso_2
)
) { ) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
@@ -817,18 +831,23 @@ class CartService extends BaseService {
) )
} }
if (cart.shipping_address_id) { if (addressOrId.id) {
const addr = await addrRepo.findOne({ cart.shipping_address = addressOrId
where: { id: cart.shipping_address_id }, cart.shipping_address_id = addressOrId.id
})
await addrRepo.save({ ...addr, ...address })
} else { } else {
const created = addrRepo.create({ if (cart.shipping_address_id) {
...address, const addr = await addrRepo.findOne({
}) where: { id: cart.shipping_address_id },
})
cart.shipping_address = created await addrRepo.save({ ...addr, ...addressOrId })
} else {
const created = addrRepo.create({
...addressOrId,
})
cart.shipping_address = created
}
} }
} }
+1 -1
View File
@@ -226,7 +226,6 @@ class DraftOrderService extends BaseService {
/** /**
* Creates a draft order. * Creates a draft order.
* @param {object} data - data to create draft order from * @param {object} data - data to create draft order from
* @param {boolean} shippingRequired - needs shipping flag
* @return {Promise<DraftOrder>} the created draft order * @return {Promise<DraftOrder>} the created draft order
*/ */
async create(data) { async create(data) {
@@ -348,6 +347,7 @@ class DraftOrderService extends BaseService {
const draftOrder = await this.retrieve(doId) const draftOrder = await this.retrieve(doId)
draftOrder.status = "completed" draftOrder.status = "completed"
draftOrder.completed_at = new Date()
draftOrder.order_id = orderId draftOrder.order_id = orderId
await draftOrderRepo.save(draftOrder) await draftOrderRepo.save(draftOrder)
+5 -1
View File
@@ -97,8 +97,12 @@ class LineItemService extends BaseService {
const region = await this.regionService_.retrieve(regionId) const region = await this.regionService_.retrieve(regionId)
let price let price
let shouldMerge = true
if (config.unit_price && typeof config.unit_price !== `undefined`) { if (config.unit_price && typeof config.unit_price !== `undefined`) {
// if custom unit_price, we ensure positive values
// and we choose to not merge the items
shouldMerge = false
if (config.unit_price < 0) { if (config.unit_price < 0) {
price = 0 price = 0
} else { } else {
@@ -121,7 +125,7 @@ class LineItemService extends BaseService {
allow_discounts: !variant.product.is_giftcard, allow_discounts: !variant.product.is_giftcard,
is_giftcard: variant.product.is_giftcard, is_giftcard: variant.product.is_giftcard,
metadata: config?.metadata || {}, metadata: config?.metadata || {},
should_merge: true, should_merge: shouldMerge,
} }
return toCreate return toCreate