Store a customer address list for physical catalog requests.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "CustomerAddress" (
|
||||
"id" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"label" TEXT,
|
||||
"text" TEXT NOT NULL,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "CustomerAddress_customerId_idx" ON "CustomerAddress"("customerId");
|
||||
|
||||
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -276,6 +276,7 @@ model Customer {
|
||||
stockNotifyRequests StockNotifyRequest[]
|
||||
productSuggestions ProductSuggestion[]
|
||||
customerBrands CustomerBrand[]
|
||||
addresses CustomerAddress[]
|
||||
|
||||
@@index([companyId])
|
||||
@@index([appRole])
|
||||
@@ -284,6 +285,19 @@ model Customer {
|
||||
@@index([levelDefId])
|
||||
}
|
||||
|
||||
model CustomerAddress {
|
||||
id String @id @default(cuid())
|
||||
customerId String
|
||||
label String?
|
||||
text String
|
||||
isDefault Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([customerId])
|
||||
}
|
||||
|
||||
model AdminUser {
|
||||
id String @id @default(cuid())
|
||||
fullName String
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "CustomerAddress" (
|
||||
"id" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"label" TEXT,
|
||||
"text" TEXT NOT NULL,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "CustomerAddress_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "CustomerAddress_customerId_idx" ON "CustomerAddress"("customerId");
|
||||
|
||||
ALTER TABLE "CustomerAddress" ADD CONSTRAINT "CustomerAddress_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -276,6 +276,7 @@ model Customer {
|
||||
stockNotifyRequests StockNotifyRequest[]
|
||||
productSuggestions ProductSuggestion[]
|
||||
customerBrands CustomerBrand[]
|
||||
addresses CustomerAddress[]
|
||||
|
||||
@@index([companyId])
|
||||
@@index([appRole])
|
||||
@@ -284,6 +285,19 @@ model Customer {
|
||||
@@index([levelDefId])
|
||||
}
|
||||
|
||||
model CustomerAddress {
|
||||
id String @id @default(cuid())
|
||||
customerId String
|
||||
label String?
|
||||
text String
|
||||
isDefault Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([customerId])
|
||||
}
|
||||
|
||||
model AdminUser {
|
||||
id String @id @default(cuid())
|
||||
fullName String
|
||||
|
||||
+12
-3
File diff suppressed because one or more lines are too long
+306
-4
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
|
||||
import { AuthType } from '../common/decorators/auth.decorators';
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
|
||||
import { AddressesService } from './addresses.service';
|
||||
import { CreateAddressDto } from './dto/address.dto';
|
||||
|
||||
@Controller('addresses')
|
||||
@AuthType('customer')
|
||||
export class AddressesController {
|
||||
constructor(private readonly addresses: AddressesService) {}
|
||||
|
||||
@Get()
|
||||
list(@CurrentUser() user: AuthPrincipal) {
|
||||
return this.addresses.list(user.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(@CurrentUser() user: AuthPrincipal, @Body() dto: CreateAddressDto) {
|
||||
return this.addresses.create(user.id, dto.text, dto.label);
|
||||
}
|
||||
|
||||
@Post(':id/default')
|
||||
makeDefault(@CurrentUser() user: AuthPrincipal, @Param('id') id: string) {
|
||||
return this.addresses.makeDefault(user.id, id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@CurrentUser() user: AuthPrincipal, @Param('id') id: string) {
|
||||
return this.addresses.remove(user.id, id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AddressesController } from './addresses.controller';
|
||||
import { AddressesService } from './addresses.service';
|
||||
|
||||
@Module({
|
||||
controllers: [AddressesController],
|
||||
providers: [AddressesService],
|
||||
})
|
||||
export class AddressesModule {}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class AddressesService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(customerId: string) {
|
||||
const rows = await this.prisma.customerAddress.findMany({
|
||||
where: { customerId },
|
||||
orderBy: [{ isDefault: 'desc' }, { createdAt: 'asc' }],
|
||||
});
|
||||
if (rows.length) return rows.map(mapAddress);
|
||||
|
||||
const legacy = await this.legacyAddress(customerId);
|
||||
if (!legacy) return [];
|
||||
const created = await this.prisma.customerAddress.create({
|
||||
data: { customerId, text: legacy, isDefault: true },
|
||||
});
|
||||
return [mapAddress(created)];
|
||||
}
|
||||
|
||||
async create(customerId: string, text: string, label?: string) {
|
||||
const trimmed = text.trim();
|
||||
const count = await this.prisma.customerAddress.count({ where: { customerId } });
|
||||
const row = await this.prisma.customerAddress.create({
|
||||
data: {
|
||||
customerId,
|
||||
text: trimmed,
|
||||
label: label?.trim() || null,
|
||||
isDefault: count === 0,
|
||||
},
|
||||
});
|
||||
if (row.isDefault) await this.syncLegacy(customerId, trimmed);
|
||||
return mapAddress(row);
|
||||
}
|
||||
|
||||
async makeDefault(customerId: string, id: string) {
|
||||
const row = await this.own(customerId, id);
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.customerAddress.updateMany({
|
||||
where: { customerId },
|
||||
data: { isDefault: false },
|
||||
}),
|
||||
this.prisma.customerAddress.update({
|
||||
where: { id: row.id },
|
||||
data: { isDefault: true },
|
||||
}),
|
||||
]);
|
||||
await this.syncLegacy(customerId, row.text);
|
||||
return this.list(customerId);
|
||||
}
|
||||
|
||||
async remove(customerId: string, id: string) {
|
||||
const row = await this.own(customerId, id);
|
||||
await this.prisma.customerAddress.delete({ where: { id: row.id } });
|
||||
if (row.isDefault) {
|
||||
const next = await this.prisma.customerAddress.findFirst({
|
||||
where: { customerId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
if (next) {
|
||||
await this.prisma.customerAddress.update({
|
||||
where: { id: next.id },
|
||||
data: { isDefault: true },
|
||||
});
|
||||
await this.syncLegacy(customerId, next.text);
|
||||
} else {
|
||||
await this.syncLegacy(customerId, null);
|
||||
}
|
||||
}
|
||||
return this.list(customerId);
|
||||
}
|
||||
|
||||
private async own(customerId: string, id: string) {
|
||||
const row = await this.prisma.customerAddress.findFirst({
|
||||
where: { id, customerId },
|
||||
});
|
||||
if (!row) throw new NotFoundException('آدرس پیدا نشد');
|
||||
return row;
|
||||
}
|
||||
|
||||
private async legacyAddress(customerId: string): Promise<string | null> {
|
||||
try {
|
||||
const customer = await this.prisma.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
select: { address: true },
|
||||
});
|
||||
return customer?.address?.trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async syncLegacy(customerId: string, text: string | null) {
|
||||
try {
|
||||
await this.prisma.customer.update({
|
||||
where: { id: customerId },
|
||||
data: { address: text },
|
||||
});
|
||||
} catch {
|
||||
/* ستون قدیمی آدرس روی بعضی دیتابیسها هنوز نیست */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mapAddress(row: {
|
||||
id: string;
|
||||
label: string | null;
|
||||
text: string;
|
||||
isDefault: boolean;
|
||||
}) {
|
||||
return {
|
||||
id: row.id,
|
||||
label: row.label,
|
||||
text: row.text,
|
||||
isDefault: row.isDefault,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
export class CreateAddressDto {
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(500)
|
||||
text!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
label?: string;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import { LevelsModule } from './levels/levels.module';
|
||||
import { SuggestionsModule } from './suggestions/suggestions.module';
|
||||
import { StockNotifyModule } from './stock-notify/stock-notify.module';
|
||||
import { BannersModule } from './banners/banners.module';
|
||||
import { AddressesModule } from './addresses/addresses.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -52,6 +53,7 @@ import { BannersModule } from './banners/banners.module';
|
||||
SuggestionsModule,
|
||||
StockNotifyModule,
|
||||
BannersModule,
|
||||
AddressesModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user