Add isUsed on store items with API list filters.
Persists used/second-hand status per listing and supports isUsed=true on business and public store-item queries. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
c6b7629c77
commit
94e9a8ed1c
@@ -0,0 +1,6 @@
|
||||
-- Used / second-hand flag on store listings (product-level).
|
||||
ALTER TABLE store_items
|
||||
ADD COLUMN IF NOT EXISTS is_used BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_store_items_business_is_used
|
||||
ON store_items (business_id, is_used);
|
||||
@@ -1141,6 +1141,7 @@ model StoreItem {
|
||||
businessId BigInt @map("business_id")
|
||||
productId BigInt @map("product_id")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
isUsed Boolean @default(false) @map("is_used")
|
||||
sortOrder Int @default(0) @map("sort_order")
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
@@ -1152,6 +1153,7 @@ model StoreItem {
|
||||
@@unique([businessId, productId], map: "store_items_business_product_unique")
|
||||
@@index([businessId], map: "idx_store_items_business_id")
|
||||
@@index([productId], map: "idx_store_items_product_id")
|
||||
@@index([businessId, isUsed], map: "idx_store_items_business_is_used")
|
||||
@@map("store_items")
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ export class BatchCreateStoreItemsDto {
|
||||
@MinLength(1)
|
||||
productId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
@IsBoolean()
|
||||
isUsed?: boolean;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateStoreItemVariantDto)
|
||||
@@ -79,6 +84,11 @@ export class ListStoreItemsDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
@IsBoolean()
|
||||
isUsed?: boolean;
|
||||
}
|
||||
|
||||
export class ListPublicStoreItemsDto {
|
||||
@@ -120,6 +130,11 @@ export class ListPublicStoreItemsDto {
|
||||
@IsBoolean()
|
||||
isFestival?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
@IsBoolean()
|
||||
isUsed?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@@ -209,6 +224,11 @@ export class SyncProductStoreItemVariantsDto {
|
||||
@MinLength(1)
|
||||
productId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
@IsBoolean()
|
||||
isUsed?: boolean;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => SyncStoreItemVariantRowDto)
|
||||
|
||||
@@ -71,23 +71,44 @@ export class StoreItemsService {
|
||||
const name = query.name?.trim();
|
||||
const where: Prisma.StoreItemVariantWhereInput = {
|
||||
businessId,
|
||||
...(name
|
||||
...(query.isUsed
|
||||
? {
|
||||
storeItem: {
|
||||
product: {
|
||||
OR: [
|
||||
{ title: { contains: name, mode: 'insensitive' } },
|
||||
{
|
||||
content: {
|
||||
path: ['nameFa'],
|
||||
string_contains: name,
|
||||
isUsed: true,
|
||||
...(name
|
||||
? {
|
||||
product: {
|
||||
OR: [
|
||||
{ title: { contains: name, mode: 'insensitive' } },
|
||||
{
|
||||
content: {
|
||||
path: ['nameFa'],
|
||||
string_contains: name,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
: name
|
||||
? {
|
||||
storeItem: {
|
||||
product: {
|
||||
OR: [
|
||||
{ title: { contains: name, mode: 'insensitive' } },
|
||||
{
|
||||
content: {
|
||||
path: ['nameFa'],
|
||||
string_contains: name,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
@@ -467,7 +488,9 @@ export class StoreItemsService {
|
||||
}
|
||||
|
||||
const created = await this.prisma.$transaction(async (tx) => {
|
||||
const storeItem = await this.ensureStoreItem(businessId, productId, tx);
|
||||
const storeItem = await this.ensureStoreItem(businessId, productId, tx, {
|
||||
isUsed: dto.isUsed,
|
||||
});
|
||||
const baseSortOrder = await tx.storeItemVariant.count({
|
||||
where: { storeItemId: storeItem.id },
|
||||
});
|
||||
@@ -719,7 +742,9 @@ export class StoreItemsService {
|
||||
}
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
const storeItem = await this.ensureStoreItem(businessId, productId, tx);
|
||||
const storeItem = await this.ensureStoreItem(businessId, productId, tx, {
|
||||
isUsed: dto.isUsed,
|
||||
});
|
||||
const baseSortOrder = await tx.storeItemVariant.count({
|
||||
where: { storeItemId: storeItem.id },
|
||||
});
|
||||
@@ -947,17 +972,28 @@ export class StoreItemsService {
|
||||
businessId: bigint,
|
||||
productId: bigint,
|
||||
tx: Prisma.TransactionClient = this.prisma,
|
||||
options?: { isUsed?: boolean },
|
||||
) {
|
||||
const existing = await tx.storeItem.findUnique({
|
||||
where: { businessId_productId: { businessId, productId } },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
if (options?.isUsed !== undefined && existing.isUsed !== options.isUsed) {
|
||||
return tx.storeItem.update({
|
||||
where: { id: existing.id },
|
||||
data: { isUsed: options.isUsed },
|
||||
});
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
return tx.storeItem.create({
|
||||
data: { businessId, productId },
|
||||
data: {
|
||||
businessId,
|
||||
productId,
|
||||
isUsed: options?.isUsed ?? false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1126,6 +1162,7 @@ export class StoreItemsService {
|
||||
stockQuantity: variant.stockQuantity,
|
||||
rewardPoints: variant.rewardPoints,
|
||||
isFestival: variant.isFestival,
|
||||
isUsed: variant.storeItem.isUsed,
|
||||
sortOrder: variant.sortOrder,
|
||||
createdAt: variant.createdAt,
|
||||
updatedAt: variant.updatedAt,
|
||||
@@ -1257,6 +1294,7 @@ export class StoreItemsService {
|
||||
isActive: true,
|
||||
storeItem: {
|
||||
isActive: true,
|
||||
...(query.isUsed ? { isUsed: true } : {}),
|
||||
product: {
|
||||
status: ContentStatus.published,
|
||||
...(query.brandId ? { brandId: BigInt(query.brandId) } : {}),
|
||||
|
||||
@@ -473,6 +473,7 @@ export class StoreSpecialsService {
|
||||
productNameFa: (content.nameFa as string | null | undefined) ?? '',
|
||||
productImage,
|
||||
isActive: storeItem.isActive,
|
||||
isUsed: storeItem.isUsed,
|
||||
sortOrder: storeItem.sortOrder,
|
||||
variants: storeItem.variants.map((variant) => {
|
||||
const selections = variant.selections.map((selection) => ({
|
||||
|
||||
Reference in New Issue
Block a user