Add پس کرایه (freight collect) on shipping cost groups.

When enabled, checkout shipping amount is 0 while customers still see the پس کرایه label.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-02 22:37:28 +03:30
co-authored by Cursor
parent 7f6fa3672d
commit 41d3f8f4eb
4 changed files with 42 additions and 7 deletions
@@ -0,0 +1,4 @@
-- Freight-collect (پس کرایه): shipping is free at checkout but labeled for the customer
ALTER TABLE shipping_cost_groups
ADD COLUMN IF NOT EXISTS pas_kraye BOOLEAN NOT NULL DEFAULT FALSE;
+1
View File
@@ -1268,6 +1268,7 @@ model ShippingCostGroup {
sortOrder Int @default(0) @map("sort_order") sortOrder Int @default(0) @map("sort_order")
isActive Boolean @default(true) @map("is_active") isActive Boolean @default(true) @map("is_active")
isDefault Boolean @default(false) @map("is_default") isDefault Boolean @default(false) @map("is_default")
pasKraye Boolean @default(false) @map("pas_kraye")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade, onUpdate: NoAction) business Business @relation(fields: [businessId], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@ -80,6 +80,10 @@ export class CreateShippingCostGroupDto {
@Min(0) @Min(0)
defaultAmount!: number; defaultAmount!: number;
@IsOptional()
@IsBoolean()
pasKraye?: boolean;
@IsOptional() @IsOptional()
@IsArray() @IsArray()
@IsString({ each: true }) @IsString({ each: true })
@@ -136,6 +140,10 @@ export class UpdateShippingCostGroupDto {
@Min(0) @Min(0)
defaultAmount?: number; defaultAmount?: number;
@IsOptional()
@IsBoolean()
pasKraye?: boolean;
@IsOptional() @IsOptional()
@IsArray() @IsArray()
@IsString({ each: true }) @IsString({ each: true })
+25 -3
View File
@@ -120,6 +120,8 @@ export class ShippingCostGroupsService {
dto.provinceExceptions ?? [], dto.provinceExceptions ?? [],
); );
const isDefault = dto.isDefault === true; const isDefault = dto.isDefault === true;
const pasKraye = dto.pasKraye === true;
const defaultAmount = pasKraye ? 0 : dto.defaultAmount;
const created = await this.prisma.$transaction(async (tx) => { const created = await this.prisma.$transaction(async (tx) => {
if (isDefault) { if (isDefault) {
@@ -136,7 +138,8 @@ export class ShippingCostGroupsService {
nameEn: dto.nameEn.trim(), nameEn: dto.nameEn.trim(),
description: dto.description?.trim() || null, description: dto.description?.trim() || null,
methods, methods,
defaultAmount: dto.defaultAmount, defaultAmount,
pasKraye,
sortOrder: dto.sortOrder ?? 0, sortOrder: dto.sortOrder ?? 0,
isActive: dto.isActive ?? true, isActive: dto.isActive ?? true,
isDefault, isDefault,
@@ -191,7 +194,13 @@ export class ShippingCostGroupsService {
if (dto.methods !== undefined) { if (dto.methods !== undefined) {
data.methods = this.normalizeMethods(dto.methods); data.methods = this.normalizeMethods(dto.methods);
} }
if (dto.defaultAmount !== undefined) { if (dto.pasKraye !== undefined) {
data.pasKraye = dto.pasKraye;
if (dto.pasKraye) {
data.defaultAmount = 0;
}
}
if (dto.defaultAmount !== undefined && dto.pasKraye !== true) {
data.defaultAmount = dto.defaultAmount; data.defaultAmount = dto.defaultAmount;
} }
if (dto.sortOrder !== undefined) data.sortOrder = dto.sortOrder; if (dto.sortOrder !== undefined) data.sortOrder = dto.sortOrder;
@@ -319,6 +328,7 @@ export class ShippingCostGroupsService {
methodId: string | null; methodId: string | null;
methodLabel: string | null; methodLabel: string | null;
amount: number; amount: number;
pasKraye: boolean;
itemNames: string[]; itemNames: string[];
}>; }>;
}> { }> {
@@ -340,6 +350,7 @@ export class ShippingCostGroupsService {
methodId: null, methodId: null,
methodLabel: null, methodLabel: null,
amount: 0, amount: 0,
pasKraye: false,
itemNames: input.cartItems.map((item) => item.productName), itemNames: input.cartItems.map((item) => item.productName),
}, },
], ],
@@ -357,6 +368,7 @@ export class ShippingCostGroupsService {
methodId: null, methodId: null,
methodLabel: null, methodLabel: null,
amount: 0, amount: 0,
pasKraye: false,
itemNames: input.cartItems.map((item) => item.productName), itemNames: input.cartItems.map((item) => item.productName),
}, },
], ],
@@ -381,6 +393,7 @@ export class ShippingCostGroupsService {
methodId: null, methodId: null,
methodLabel: null, methodLabel: null,
amount, amount,
pasKraye: false,
itemNames: input.cartItems.map((item) => item.productName), itemNames: input.cartItems.map((item) => item.productName),
}, },
], ],
@@ -402,6 +415,7 @@ export class ShippingCostGroupsService {
title: string; title: string;
methods: ShippingMethodId[]; methods: ShippingMethodId[];
amount: number; amount: number;
pasKraye: boolean;
itemNames: string[]; itemNames: string[];
isDefault: boolean; isDefault: boolean;
} }
@@ -419,17 +433,21 @@ export class ShippingCostGroupsService {
if (existing) { if (existing) {
existing.itemNames.push(item.productName); existing.itemNames.push(item.productName);
} else { } else {
const pasKraye = group.pasKraye === true;
buckets.set(group.id, { buckets.set(group.id, {
groupId: group.id, groupId: group.id,
title: group.isDefault title: group.isDefault
? STORE_SHIPPING_TITLE ? STORE_SHIPPING_TITLE
: group.nameFa || group.nameEn, : group.nameFa || group.nameEn,
methods: group.methods, methods: group.methods,
amount: this.resolveProvinceAmount( amount: pasKraye
? 0
: this.resolveProvinceAmount(
group.defaultAmount, group.defaultAmount,
group.provinceExceptions, group.provinceExceptions,
provinceId, provinceId,
), ),
pasKraye,
itemNames: [item.productName], itemNames: [item.productName],
isDefault: group.isDefault, isDefault: group.isDefault,
}); });
@@ -442,6 +460,7 @@ export class ShippingCostGroupsService {
title: STORE_SHIPPING_TITLE, title: STORE_SHIPPING_TITLE,
methods: [], methods: [],
amount: 0, amount: 0,
pasKraye: false,
itemNames: ungroupedNames, itemNames: ungroupedNames,
isDefault: true, isDefault: true,
}); });
@@ -459,6 +478,7 @@ export class ShippingCostGroupsService {
methodId, methodId,
methodLabel: methodId ? METHOD_LABELS_FA[methodId] : null, methodLabel: methodId ? METHOD_LABELS_FA[methodId] : null,
amount: bucket.amount, amount: bucket.amount,
pasKraye: bucket.pasKraye,
itemNames: bucket.itemNames, itemNames: bucket.itemNames,
}; };
}); });
@@ -506,6 +526,7 @@ export class ShippingCostGroupsService {
nameEn: string; nameEn: string;
methods: ShippingMethodId[]; methods: ShippingMethodId[];
defaultAmount: number; defaultAmount: number;
pasKraye: boolean;
provinceExceptions?: Array<{ provinceId: string; amount: number }>; provinceExceptions?: Array<{ provinceId: string; amount: number }>;
}>, }>,
categoryIds: string[], categoryIds: string[],
@@ -545,6 +566,7 @@ export class ShippingCostGroupsService {
sortOrder: group.sortOrder, sortOrder: group.sortOrder,
isActive: group.isActive, isActive: group.isActive,
isDefault: group.isDefault, isDefault: group.isDefault,
pasKraye: group.pasKraye,
categoryIds: group.categories.map((row) => row.categoryId.toString()), categoryIds: group.categories.map((row) => row.categoryId.toString()),
categories: group.categories.map((row) => ({ categories: group.categories.map((row) => ({
id: row.category.id.toString(), id: row.category.id.toString(),