Initial Mento CRM: portal OTP, reports, SMS, and deploy for crm.montzar.ir.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import type {
|
||||
CreateProductRequest,
|
||||
ProductDto,
|
||||
UpdateProductRequest,
|
||||
} from '@mento/shared';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
type ProductRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
sku: string | null;
|
||||
description: string | null;
|
||||
unitPrice: Prisma.Decimal | null;
|
||||
currency: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ProductsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(
|
||||
organizationId: string,
|
||||
opts?: { activeOnly?: boolean; q?: string },
|
||||
): Promise<ProductDto[]> {
|
||||
const rows = await this.prisma.product.findMany({
|
||||
where: {
|
||||
organizationId,
|
||||
...(opts?.activeOnly ? { isActive: true } : {}),
|
||||
...(opts?.q?.trim()
|
||||
? {
|
||||
OR: [
|
||||
{ name: { contains: opts.q.trim(), mode: 'insensitive' } },
|
||||
{ sku: { contains: opts.q.trim(), mode: 'insensitive' } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
orderBy: [{ isActive: 'desc' }, { name: 'asc' }],
|
||||
});
|
||||
return rows.map((row) => this.toDto(row));
|
||||
}
|
||||
|
||||
async create(
|
||||
organizationId: string,
|
||||
dto: CreateProductRequest,
|
||||
): Promise<ProductDto> {
|
||||
const name = dto.name.trim();
|
||||
if (!name) throw new BadRequestException('نام محصول الزامی است');
|
||||
|
||||
const row = await this.prisma.product.create({
|
||||
data: {
|
||||
organizationId,
|
||||
name,
|
||||
sku: emptyToNull(dto.sku),
|
||||
description: emptyToNull(dto.description),
|
||||
unitPrice:
|
||||
dto.unitPrice == null || Number.isNaN(Number(dto.unitPrice))
|
||||
? null
|
||||
: new Prisma.Decimal(dto.unitPrice),
|
||||
currency: (dto.currency ?? 'IRR').trim() || 'IRR',
|
||||
isActive: dto.isActive ?? true,
|
||||
},
|
||||
});
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async update(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
dto: UpdateProductRequest,
|
||||
): Promise<ProductDto> {
|
||||
await this.ensureExists(organizationId, id);
|
||||
const data: Prisma.ProductUpdateInput = {};
|
||||
if (dto.name !== undefined) {
|
||||
const name = dto.name.trim();
|
||||
if (!name) throw new BadRequestException('نام محصول الزامی است');
|
||||
data.name = name;
|
||||
}
|
||||
if ('sku' in dto) data.sku = emptyToNull(dto.sku);
|
||||
if ('description' in dto) data.description = emptyToNull(dto.description);
|
||||
if ('unitPrice' in dto) {
|
||||
data.unitPrice =
|
||||
dto.unitPrice == null || Number.isNaN(Number(dto.unitPrice))
|
||||
? null
|
||||
: new Prisma.Decimal(dto.unitPrice);
|
||||
}
|
||||
if (dto.currency !== undefined) {
|
||||
data.currency = dto.currency.trim() || 'IRR';
|
||||
}
|
||||
if (dto.isActive !== undefined) data.isActive = dto.isActive;
|
||||
|
||||
const row = await this.prisma.product.update({ where: { id }, data });
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async remove(organizationId: string, id: string): Promise<void> {
|
||||
await this.ensureExists(organizationId, id);
|
||||
await this.prisma.product.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async ensureInOrg(organizationId: string, productId: string): Promise<void> {
|
||||
await this.ensureExists(organizationId, productId);
|
||||
}
|
||||
|
||||
private async ensureExists(organizationId: string, id: string) {
|
||||
const found = await this.prisma.product.findFirst({
|
||||
where: { id, organizationId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!found) throw new NotFoundException('محصول پیدا نشد');
|
||||
}
|
||||
|
||||
private toDto(row: ProductRow): ProductDto {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
sku: row.sku,
|
||||
description: row.description,
|
||||
unitPrice: row.unitPrice == null ? null : Number(row.unitPrice),
|
||||
currency: row.currency,
|
||||
isActive: row.isActive,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function emptyToNull(value?: string | null): string | null {
|
||||
if (value == null) return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length ? trimmed : null;
|
||||
}
|
||||
Reference in New Issue
Block a user