fix(pricing,dashboard): update min_quantity/max_quantity to decimal in price model (#14045)

This commit is contained in:
Nicolas Gorga
2025-12-16 13:38:53 -03:00
committed by GitHub
parent bee86bf1d5
commit b5edbb9940
9 changed files with 110 additions and 48 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/dashboard": patch
"@medusajs/pricing": patch
---
fix(pricing,dashboard): update min_quantity/max_quantity to decimal in price model

View File

@@ -487,6 +487,31 @@ medusaIntegrationTestRunner({
}),
])
// Update item with decimal quantity
result = (
await api.post(
`/admin/order-edits/${orderId}/items/item/${item.id}`,
{
quantity: 2.5,
unit_price: 30,
},
adminHeaders
)
).data.order_preview
expect(result.summary.current_order_total).toEqual(111.4)
expect(result.summary.original_order_total).toEqual(60)
const decimalUpdatedItem = result.items.find((i) => i.id === item.id)
expect(decimalUpdatedItem.actions[3]).toEqual(
expect.objectContaining({
details: expect.objectContaining({
quantity: 2.5,
unit_price: 30,
quantity_diff: 0.5,
}),
})
)
// Remove the item by setting the quantity to 0
result = (
await api.post(
@@ -542,7 +567,7 @@ medusaIntegrationTestRunner({
)
).data.order_changes
expect(result[0].actions).toHaveLength(5)
expect(result[0].actions).toHaveLength(6)
expect(result[0].status).toEqual("confirmed")
expect(result[0].confirmed_by).toEqual(expect.stringContaining("user_"))
})

View File

@@ -196,7 +196,7 @@ medusaIntegrationTestRunner({
name: "Test shipping option",
price_type: "flat",
prices: expect.arrayContaining([
{
expect.objectContaining({
id: expect.any(String),
amount: 1000,
currency_code: "usd",
@@ -215,8 +215,8 @@ medusaIntegrationTestRunner({
updated_at: expect.any(String),
deleted_at: null,
price_rules: [],
},
{
}),
expect.objectContaining({
id: expect.any(String),
amount: 500,
currency_code: "usd",
@@ -246,7 +246,7 @@ medusaIntegrationTestRunner({
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
},
}),
]),
provider_id: "manual_test-provider",
provider: expect.objectContaining({

View File

@@ -122,7 +122,7 @@ medusaIntegrationTestRunner({
"customer.groups.id": [customerGroup.id],
},
prices: [
{
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 5000,
@@ -143,7 +143,7 @@ medusaIntegrationTestRunner({
value: "5000",
precision: 20,
}),
},
}),
],
},
])
@@ -289,7 +289,7 @@ medusaIntegrationTestRunner({
"customer.groups.id": [customerGroup.id],
},
prices: [
{
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 5000,
@@ -310,7 +310,7 @@ medusaIntegrationTestRunner({
rules: {
region_id: region.id,
},
},
}),
],
})
)

View File

@@ -180,6 +180,7 @@ function OrderEditItem({ item, currencyCode, orderId }: OrderEditItemProps) {
<Input
className="bg-ui-bg-base txt-small w-[67px] rounded-lg [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
type="number"
step="any"
disabled={item.detail.fulfilled_quantity === item.quantity}
min={item.detail.fulfilled_quantity}
defaultValue={item.quantity}

View File

@@ -134,7 +134,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_deleted_at\" ON \"price_list\" (deleted_at) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_deleted_at\" ON \"price_list\" (\"deleted_at\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_list_id_status_starts_at_ends_at",
@@ -143,7 +143,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_id_status_starts_at_ends_at\" ON \"price_list\" (id, status, starts_at, ends_at) WHERE deleted_at IS NULL AND status = 'active'"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_id_status_starts_at_ends_at\" ON \"price_list\" (\"id\", \"status\", \"starts_at\", \"ends_at\") WHERE deleted_at IS NULL AND status = 'active'"
},
{
"keyName": "price_list_pkey",
@@ -241,7 +241,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_price_list_id\" ON \"price_list_rule\" (price_list_id) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_price_list_id\" ON \"price_list_rule\" (\"price_list_id\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_list_rule_deleted_at",
@@ -250,7 +250,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_deleted_at\" ON \"price_list_rule\" (deleted_at) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_deleted_at\" ON \"price_list_rule\" (\"deleted_at\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_list_rule_attribute",
@@ -259,7 +259,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_attribute\" ON \"price_list_rule\" (attribute) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_attribute\" ON \"price_list_rule\" (\"attribute\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_list_rule_value",
@@ -268,7 +268,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_value\" ON \"price_list_rule\" USING gin (value) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_list_rule_value\" ON \"price_list_rule\" USING gin (\"value\") WHERE deleted_at IS NULL"
},
{
"keyName": "price_list_rule_pkey",
@@ -381,7 +381,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_preference_deleted_at\" ON \"price_preference\" (deleted_at) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_preference_deleted_at\" ON \"price_preference\" (\"deleted_at\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_preference_attribute_value",
@@ -390,7 +390,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_price_preference_attribute_value\" ON \"price_preference\" (attribute, value) WHERE deleted_at IS NULL"
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_price_preference_attribute_value\" ON \"price_preference\" (\"attribute\", \"value\") WHERE deleted_at IS NULL"
},
{
"keyName": "price_preference_pkey",
@@ -461,7 +461,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_set_deleted_at\" ON \"price_set\" (deleted_at) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_set_deleted_at\" ON \"price_set\" (\"deleted_at\") WHERE deleted_at IS NULL"
},
{
"keyName": "price_set_pkey",
@@ -518,21 +518,21 @@
},
"min_quantity": {
"name": "min_quantity",
"type": "integer",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "integer"
"mappedType": "decimal"
},
"max_quantity": {
"name": "max_quantity",
"type": "integer",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "integer"
"mappedType": "decimal"
},
"rules_count": {
"name": "rules_count",
@@ -571,6 +571,24 @@
"nullable": false,
"mappedType": "json"
},
"raw_min_quantity": {
"name": "raw_min_quantity",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"raw_max_quantity": {
"name": "raw_max_quantity",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
@@ -614,7 +632,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_price_set_id\" ON \"price\" (price_set_id) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_price_set_id\" ON \"price\" (\"price_set_id\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_price_list_id",
@@ -623,7 +641,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_price_list_id\" ON \"price\" (price_list_id) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_price_list_id\" ON \"price\" (\"price_list_id\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_deleted_at",
@@ -632,7 +650,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_deleted_at\" ON \"price\" (deleted_at) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_deleted_at\" ON \"price\" (\"deleted_at\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_currency_code",
@@ -641,16 +659,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_currency_code\" ON \"price\" (currency_code) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_currency_code_price_set_id_min_quantity",
"columnNames": [],
"composite": false,
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_currency_code_price_set_id_min_quantity\" ON \"price\" (currency_code, price_set_id, min_quantity) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_currency_code\" ON \"price\" (\"currency_code\") WHERE deleted_at IS NULL"
},
{
"keyName": "price_pkey",
@@ -802,7 +811,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_price_id\" ON \"price_rule\" (price_id) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_price_id\" ON \"price_rule\" (\"price_id\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_rule_deleted_at",
@@ -811,7 +820,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_deleted_at\" ON \"price_rule\" (deleted_at) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_deleted_at\" ON \"price_rule\" (\"deleted_at\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_rule_price_id_attribute_operator_unique",
@@ -820,7 +829,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_price_rule_price_id_attribute_operator_unique\" ON \"price_rule\" (price_id, attribute, operator) WHERE deleted_at IS NULL"
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_price_rule_price_id_attribute_operator_unique\" ON \"price_rule\" (\"price_id\", \"attribute\", \"operator\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_rule_attribute",
@@ -829,7 +838,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute\" ON \"price_rule\" (attribute) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute\" ON \"price_rule\" (\"attribute\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_rule_attribute_value",
@@ -838,7 +847,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute_value\" ON \"price_rule\" (attribute, value) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute_value\" ON \"price_rule\" (\"attribute\", \"value\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_rule_operator_value",
@@ -847,7 +856,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_operator_value\" ON \"price_rule\" (operator, value) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_operator_value\" ON \"price_rule\" (\"operator\", \"value\") WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_price_rule_attribute_value_price_id",
@@ -856,7 +865,7 @@
"constraint": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute_value_price_id\" ON \"price_rule\" (attribute, value, price_id) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_price_rule_attribute_value_price_id\" ON \"price_rule\" (\"attribute\", \"value\", \"price_id\") WHERE deleted_at IS NULL"
},
{
"keyName": "price_rule_pkey",

View File

@@ -0,0 +1,21 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20251112192723 extends Migration {
override async up(): Promise<void> {
this.addSql(`drop index if exists "IDX_price_currency_code_price_set_id_min_quantity";`);
this.addSql(`alter table if exists "price" add column if not exists "raw_min_quantity" jsonb null, add column if not exists "raw_max_quantity" jsonb null;`);
this.addSql(`alter table if exists "price" alter column "min_quantity" type numeric using ("min_quantity"::numeric);`);
this.addSql(`alter table if exists "price" alter column "max_quantity" type numeric using ("max_quantity"::numeric);`);
}
override async down(): Promise<void> {
this.addSql(`alter table if exists "price" drop column if exists "raw_min_quantity", drop column if exists "raw_max_quantity";`);
this.addSql(`alter table if exists "price" alter column "min_quantity" type integer using ("min_quantity"::integer);`);
this.addSql(`alter table if exists "price" alter column "max_quantity" type integer using ("max_quantity"::integer);`);
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_price_currency_code_price_set_id_min_quantity" ON "price" (currency_code, price_set_id, min_quantity) WHERE deleted_at IS NULL;`);
}
}

View File

@@ -9,8 +9,8 @@ const Price = model
title: model.text().nullable(),
currency_code: model.text(),
amount: model.bigNumber(),
min_quantity: model.number().nullable(),
max_quantity: model.number().nullable(),
min_quantity: model.bigNumber().nullable(),
max_quantity: model.bigNumber().nullable(),
rules_count: model.number().default(0).nullable(),
price_set: model.belongsTo(() => PriceSet, {
mappedBy: "prices",

View File

@@ -535,16 +535,16 @@ export default class PricingModuleService
id: calculatedPrice?.id || null,
price_list_id: calculatedPrice?.price_list_id || null,
price_list_type: calculatedPrice?.price_list_type || null,
min_quantity: parseInt(calculatedPrice?.min_quantity || "") || null,
max_quantity: parseInt(calculatedPrice?.max_quantity || "") || null,
min_quantity: parseFloat(calculatedPrice?.min_quantity || "") || null,
max_quantity: parseFloat(calculatedPrice?.max_quantity || "") || null,
},
original_price: {
id: originalPrice?.id || null,
price_list_id: originalPrice?.price_list_id || null,
price_list_type: originalPrice?.price_list_type || null,
min_quantity: parseInt(originalPrice?.min_quantity || "") || null,
max_quantity: parseInt(originalPrice?.max_quantity || "") || null,
min_quantity: parseFloat(originalPrice?.min_quantity || "") || null,
max_quantity: parseFloat(originalPrice?.max_quantity || "") || null,
},
}