Ask for organization business field at signup, including jewelry.

Store Didar-style industry on the org so registration, settings, and Ask Mento share the same catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-03 16:45:59 +03:30
co-authored by Cursor
parent ce94658fcd
commit d2f66d11fc
14 changed files with 286 additions and 8 deletions
+11 -1
View File
@@ -11,6 +11,7 @@ import {
noteAccessWhere,
ownerWhere,
} from '../common/record-scope';
import { BUSINESS_FIELD_LABELS, isBusinessField } from '@mento/shared';
import { AiService } from './ai.service';
type CatalogItem = AskMentoSourceDto;
@@ -101,7 +102,11 @@ Rules:
if (!person) throw new NotFoundException('شخص پیدا نشد');
}
const [notes, activities, deals, memories] = await Promise.all([
const [org, notes, activities, deals, memories] = await Promise.all([
this.prisma.organization.findUnique({
where: { id: actor.organizationId },
select: { name: true, businessField: true },
}),
this.prisma.note.findMany({
where: {
organizationId: actor.organizationId,
@@ -167,8 +172,13 @@ Rules:
]);
const catalog: CatalogItem[] = [];
const field =
org?.businessField && isBusinessField(org.businessField)
? BUSINESS_FIELD_LABELS[org.businessField]
: 'نامشخص';
const lines: string[] = [
`Today: ${new Date().toISOString()}`,
`Organization: ${org?.name ?? '—'} | business field: ${field}`,
personId ? `Focus personId: ${personId}` : 'Scope: current user records',
];
+1
View File
@@ -45,6 +45,7 @@ export class AuthService {
data: {
name: dto.organizationName,
slug,
businessField: dto.businessField,
users: {
create: {
email: dto.email.toLowerCase(),
+10 -1
View File
@@ -1,4 +1,10 @@
import { IsEmail, IsString, MinLength } from 'class-validator';
import {
IsEmail,
IsIn,
IsString,
MinLength,
} from 'class-validator';
import { BUSINESS_FIELDS } from '@mento/shared';
export class LoginDto {
@IsEmail()
@@ -24,4 +30,7 @@ export class RegisterOrganizationDto {
@IsString()
@MinLength(6)
password!: string;
@IsIn([...BUSINESS_FIELDS])
businessField!: (typeof BUSINESS_FIELDS)[number];
}
+14
View File
@@ -1,6 +1,7 @@
import {
ArrayMinSize,
IsArray,
IsIn,
IsNumber,
IsOptional,
IsString,
@@ -10,6 +11,7 @@ import {
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { BUSINESS_FIELDS } from '@mento/shared';
export class ExchangeRateInputDto {
@IsString()
@@ -42,3 +44,15 @@ export class UpdateOrgCurrenciesDto {
@Type(() => ExchangeRateInputDto)
rates?: ExchangeRateInputDto[];
}
export class UpdateOrganizationDto {
@IsOptional()
@IsString()
@MinLength(2)
@MaxLength(120)
name?: string;
@IsOptional()
@IsIn([...BUSINESS_FIELDS])
businessField?: (typeof BUSINESS_FIELDS)[number];
}
@@ -3,7 +3,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { CurrentUser } from '../auth/current-user.decorator';
import { UpdateOrgCurrenciesDto } from './dto';
import { UpdateOrgCurrenciesDto, UpdateOrganizationDto } from './dto';
import { OrganizationsService } from './organizations.service';
@Controller('organizations')
@@ -21,6 +21,15 @@ export class OrganizationsController {
return this.organizationsService.catalog();
}
@Patch('current')
@Roles('owner', 'admin')
updateCurrent(
@CurrentUser() user: { organizationId: string },
@Body() dto: UpdateOrganizationDto,
) {
return this.organizationsService.updateProfile(user.organizationId, dto);
}
@Patch('current/currencies')
@Roles('owner', 'admin')
updateCurrencies(
@@ -7,7 +7,7 @@ import type {
OrganizationDto,
UpdateOrgCurrenciesRequest,
} from '@mento/shared';
import { CURRENCY_CATALOG } from '@mento/shared';
import { CURRENCY_CATALOG, isBusinessField } from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { isKnownCurrency } from '../common/currency';
@@ -29,6 +29,27 @@ export class OrganizationsService {
return this.toDto(org);
}
async updateProfile(
organizationId: string,
dto: { name?: string; businessField?: string },
): Promise<OrganizationDto> {
if (dto.businessField && !isBusinessField(dto.businessField)) {
throw new BadRequestException('زمینه فعالیت معتبر نیست');
}
const name = dto.name?.trim();
if (dto.name !== undefined && (!name || name.length < 2)) {
throw new BadRequestException('نام سازمان معتبر نیست');
}
await this.prisma.organization.update({
where: { id: organizationId },
data: {
...(name ? { name } : {}),
...(dto.businessField ? { businessField: dto.businessField } : {}),
},
});
return this.getCurrent(organizationId);
}
async updateCurrencies(
organizationId: string,
dto: UpdateOrgCurrenciesRequest,
@@ -130,6 +151,7 @@ export class OrganizationsService {
id: string;
name: string;
slug: string;
businessField: string | null;
baseCurrency: string;
enabledCurrencies: string[];
exchangeRates: Array<{
@@ -142,6 +164,10 @@ export class OrganizationsService {
id: org.id,
name: org.name,
slug: org.slug,
businessField:
org.businessField && isBusinessField(org.businessField)
? org.businessField
: null,
baseCurrency: org.baseCurrency,
enabledCurrencies: org.enabledCurrencies,
exchangeRates: org.exchangeRates.map((r) => ({