feat: add generated code

This commit is contained in:
aid-orchestrator
2026-05-12 09:31:32 +00:00
parent 62b4b052a3
commit 12179f2160
51 changed files with 1143 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
enum MovementKind {
Receipt
Issue
Transfer
Adjustment
}
model ProductType {
id String @id @default(uuid())
name String @unique
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("product_types")
}
model Product {
id String @id @default(uuid())
name String
productType ProductType @relation(fields: [productTypeId], references: [id])
productTypeId String
parent Product? @relation("ProductHierarchy", fields: [parentId], references: [id])
parentId String?
children Product[] @relation("ProductHierarchy")
stockStatus StockStatus?
lines StockMovementLine[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("products")
}
model StockStatus {
product Product @relation(fields: [productId], references: [id])
productId String @id @unique
quantity Decimal @default(0)
inStock Boolean @default(false)
updatedAt DateTime @default(now()) @updatedAt
@@map("stock_statuses")
}
model StockMovement {
id String @id @default(uuid())
documentDate DateTime @db.Date
kind MovementKind
lines StockMovementLine[]
comment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("stock_movements")
}
model StockMovementLine {
id String @id @default(uuid())
product Product @relation(fields: [productId], references: [id])
productId String
quantity Decimal
inStock Boolean
movement StockMovement @relation(fields: [movementId], references: [id])
movementId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([movementId, productId])
@@map("stock_movement_lines")
}

19
backend/src/app.module.ts Normal file
View File

@@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaService } from './prisma/prisma.service';
import { ProductTypeModule } from './product-type/product-type.module';
import { ProductModule } from './product/product.module';
import { StockStatusModule } from './stock-status/stock-status.module';
import { StockMovementModule } from './stock-movement/stock-movement.module';
@Module({
imports: [
AuthModule,
ProductTypeModule,
ProductModule,
StockStatusModule,
StockMovementModule,
],
providers: [PrismaService],
})
export class AppModule {}

View File

@@ -0,0 +1,9 @@
import { IsString, IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateProductTypeDto {
@ApiProperty({ description: 'Наименование типа товара' })
@IsString()
@IsNotEmpty()
name: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateProductTypeDto } from './create-product-type.dto';
export class UpdateProductTypeDto extends PartialType(CreateProductTypeDto) {}

View File

@@ -0,0 +1,54 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ProductTypeService } from './product-type.service';
import { CreateProductTypeDto } from './dto/create-product-type.dto';
import { UpdateProductTypeDto } from './dto/update-product-type.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
const parseJson = <T>(value?: string): T | undefined => {
if (!value) return undefined;
try { return JSON.parse(value) as T; } catch { return undefined; }
};
@ApiTags('product-type')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('product-types')
export class ProductTypeController {
constructor(private readonly productTypeService: ProductTypeService) {}
@Get()
findAll(
@Query('skip') skip?: string,
@Query('take') take?: string,
@Query('orderBy') orderBy?: string,
@Query('where') where?: string,
) {
return this.productTypeService.findAll({
skip: skip ? Number(skip) : 0,
take: take ? Number(take) : 25,
orderBy: parseJson(orderBy),
where: parseJson(where),
});
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.productTypeService.findOne({ id });
}
@Post()
create(@Body() dto: CreateProductTypeDto) {
return this.productTypeService.create(dto);
}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateProductTypeDto) {
return this.productTypeService.update({ where: { id }, data: dto });
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.productTypeService.remove({ id });
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ProductTypeService } from './product-type.service';
import { ProductTypeController } from './product-type.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
controllers: [ProductTypeController],
providers: [ProductTypeService, PrismaService],
})
export class ProductTypeModule {}

View File

@@ -0,0 +1,41 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { ProductType, Prisma } from '@prisma/client';
@Injectable()
export class ProductTypeService {
constructor(private prisma: PrismaService) {}
async findAll(params: {
skip?: number;
take?: number;
where?: Prisma.ProductTypeWhereInput;
orderBy?: Prisma.ProductTypeOrderByWithRelationInput;
}): Promise<{ data: ProductType[]; total: number }> {
const { skip, take, where, orderBy } = params;
const [data, total] = await this.prisma.$transaction([
this.prisma.productType.findMany({ skip, take, where, orderBy }),
this.prisma.productType.count({ where }),
]);
return { data, total };
}
async findOne(where: Prisma.ProductTypeWhereUniqueInput): Promise<ProductType | null> {
return this.prisma.productType.findUnique({ where });
}
async create(data: Prisma.ProductTypeCreateInput): Promise<ProductType> {
return this.prisma.productType.create({ data });
}
async update(params: {
where: Prisma.ProductTypeWhereUniqueInput;
data: Prisma.ProductTypeUpdateInput;
}): Promise<ProductType> {
return this.prisma.productType.update(params);
}
async remove(where: Prisma.ProductTypeWhereUniqueInput): Promise<ProductType> {
return this.prisma.productType.delete({ where });
}
}

View File

@@ -0,0 +1,19 @@
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateProductDto {
@ApiProperty({ description: 'Наименование товара' })
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty({ description: 'ID типа товара' })
@IsString()
@IsNotEmpty()
productTypeId: string;
@ApiProperty({ description: 'ID родительского товара (для иерархии)', required: false })
@IsString()
@IsOptional()
parentId?: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';
export class UpdateProductDto extends PartialType(CreateProductDto) {}

View File

@@ -0,0 +1,54 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ProductService } from './product.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
const parseJson = <T>(value?: string): T | undefined => {
if (!value) return undefined;
try { return JSON.parse(value) as T; } catch { return undefined; }
};
@ApiTags('product')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get()
findAll(
@Query('skip') skip?: string,
@Query('take') take?: string,
@Query('orderBy') orderBy?: string,
@Query('where') where?: string,
) {
return this.productService.findAll({
skip: skip ? Number(skip) : 0,
take: take ? Number(take) : 25,
orderBy: parseJson(orderBy),
where: parseJson(where),
});
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.productService.findOne({ id });
}
@Post()
create(@Body() dto: CreateProductDto) {
return this.productService.create(dto);
}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
return this.productService.update({ where: { id }, data: dto });
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.productService.remove({ id });
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';
import { ProductController } from './product.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
controllers: [ProductController],
providers: [ProductService, PrismaService],
})
export class ProductModule {}

View File

@@ -0,0 +1,74 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Product, Prisma } from '@prisma/client';
@Injectable()
export class ProductService {
constructor(private prisma: PrismaService) {}
async findAll(params: {
skip?: number;
take?: number;
where?: Prisma.ProductWhereInput;
orderBy?: Prisma.ProductOrderByWithRelationInput;
}): Promise<{ data: Product[]; total: number }> {
const { skip, take, where, orderBy } = params;
const [data, total] = await this.prisma.$transaction([
this.prisma.product.findMany({
skip,
take,
where,
orderBy,
include: {
productType: true,
parent: true,
},
}),
this.prisma.product.count({ where }),
]);
return { data, total };
}
async findOne(where: Prisma.ProductWhereUniqueInput): Promise<Product | null> {
return this.prisma.product.findUnique({
where,
include: {
productType: true,
parent: true,
},
});
}
async create(data: Prisma.ProductCreateInput): Promise<Product> {
return this.prisma.product.create({
data,
include: {
productType: true,
parent: true,
},
});
}
async update(params: {
where: Prisma.ProductWhereUniqueInput;
data: Prisma.ProductUpdateInput;
}): Promise<Product> {
return this.prisma.product.update({
...params,
include: {
productType: true,
parent: true,
},
});
}
async remove(where: Prisma.ProductWhereUniqueInput): Promise<Product> {
return this.prisma.product.delete({
where,
include: {
productType: true,
parent: true,
},
});
}
}

View File

@@ -0,0 +1,19 @@
import { IsString, IsNotEmpty, IsNumber, IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
export class CreateStockMovementLineDto {
@ApiProperty({ description: 'ID товара' })
@IsString()
@IsNotEmpty()
productId: string;
@ApiProperty({ description: 'Количество' })
@IsNumber()
@Type(() => Number)
quantity: number;
@ApiProperty({ description: 'Новый признак наличия после проведения' })
@IsBoolean()
inStock: boolean;
}

View File

@@ -0,0 +1,25 @@
import { IsString, IsNotEmpty, IsEnum, IsArray, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
import { MovementKind } from '@prisma/client';
import { CreateStockMovementLineDto } from './create-stock-movement-line.dto';
export class CreateStockMovementDto {
@ApiProperty({ description: 'Дата документа' })
@Type(() => Date)
documentDate: Date;
@ApiProperty({ enum: MovementKind, enumName: 'MovementKind', description: 'Вид движения' })
@IsEnum(MovementKind)
kind: MovementKind;
@ApiProperty({ type: [CreateStockMovementLineDto], description: 'Строки документа' })
@IsArray()
@Type(() => CreateStockMovementLineDto)
lines: CreateStockMovementLineDto[];
@ApiProperty({ description: 'Комментарий к документу', required: false })
@IsString()
@IsOptional()
comment?: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateStockMovementLineDto } from './create-stock-movement-line.dto';
export class UpdateStockMovementLineDto extends PartialType(CreateStockMovementLineDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateStockMovementDto } from './create-stock-movement.dto';
export class UpdateStockMovementDto extends PartialType(CreateStockMovementDto) {}

View File

@@ -0,0 +1,54 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { StockMovementService } from './stock-movement.service';
import { CreateStockMovementDto } from './dto/create-stock-movement.dto';
import { UpdateStockMovementDto } from './dto/update-stock-movement.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
const parseJson = <T>(value?: string): T | undefined => {
if (!value) return undefined;
try { return JSON.parse(value) as T; } catch { return undefined; }
};
@ApiTags('stock-movement')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('stock-movements')
export class StockMovementController {
constructor(private readonly stockMovementService: StockMovementService) {}
@Get()
findAll(
@Query('skip') skip?: string,
@Query('take') take?: string,
@Query('orderBy') orderBy?: string,
@Query('where') where?: string,
) {
return this.stockMovementService.findAll({
skip: skip ? Number(skip) : 0,
take: take ? Number(take) : 25,
orderBy: parseJson(orderBy),
where: parseJson(where),
});
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.stockMovementService.findOne({ id });
}
@Post()
create(@Body() dto: CreateStockMovementDto) {
return this.stockMovementService.create(dto);
}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateStockMovementDto) {
return this.stockMovementService.update({ where: { id }, data: dto });
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.stockMovementService.remove({ id });
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { StockMovementService } from './stock-movement.service';
import { StockMovementController } from './stock-movement.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
controllers: [StockMovementController],
providers: [StockMovementService, PrismaService],
})
export class StockMovementModule {}

View File

@@ -0,0 +1,89 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { StockMovement, Prisma } from '@prisma/client';
@Injectable()
export class StockMovementService {
constructor(private prisma: PrismaService) {}
async findAll(params: {
skip?: number;
take?: number;
where?: Prisma.StockMovementWhereInput;
orderBy?: Prisma.StockMovementOrderByWithRelationInput;
}): Promise<{ data: StockMovement[]; total: number }> {
const { skip, take, where, orderBy } = params;
const [data, total] = await this.prisma.$transaction([
this.prisma.stockMovement.findMany({
skip,
take,
where,
orderBy,
include: {
lines: {
include: {
product: true,
},
},
},
}),
this.prisma.stockMovement.count({ where }),
]);
return { data, total };
}
async findOne(where: Prisma.StockMovementWhereUniqueInput): Promise<StockMovement | null> {
return this.prisma.stockMovement.findUnique({
where,
include: {
lines: {
include: {
product: true,
},
},
},
});
}
async create(data: Prisma.StockMovementCreateInput): Promise<StockMovement> {
return this.prisma.stockMovement.create({
data,
include: {
lines: {
include: {
product: true,
},
},
},
});
}
async update(params: {
where: Prisma.StockMovementWhereUniqueInput;
data: Prisma.StockMovementUpdateInput;
}): Promise<StockMovement> {
return this.prisma.stockMovement.update({
...params,
include: {
lines: {
include: {
product: true,
},
},
},
});
}
async remove(where: Prisma.StockMovementWhereUniqueInput): Promise<StockMovement> {
return this.prisma.stockMovement.delete({
where,
include: {
lines: {
include: {
product: true,
},
},
},
});
}
}

View File

@@ -0,0 +1,25 @@
import { IsString, IsNotEmpty, IsNumber, IsBoolean, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
export class CreateStockStatusDto {
@ApiProperty({ description: 'ID товара' })
@IsString()
@IsNotEmpty()
productId: string;
@ApiProperty({ description: 'Текущее количество на складе', required: false })
@IsNumber()
@IsOptional()
@Type(() => Number)
quantity?: number;
@ApiProperty({ description: 'Признак наличия товара', required: false })
@IsBoolean()
@IsOptional()
inStock?: boolean;
@ApiProperty({ description: 'Дата последнего обновления', required: false })
@IsOptional()
updatedAt?: Date;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateStockStatusDto } from './create-stock-status.dto';
export class UpdateStockStatusDto extends PartialType(CreateStockStatusDto) {}

View File

@@ -0,0 +1,54 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { StockStatusService } from './stock-status.service';
import { CreateStockStatusDto } from './dto/create-stock-status.dto';
import { UpdateStockStatusDto } from './dto/update-stock-status.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
const parseJson = <T>(value?: string): T | undefined => {
if (!value) return undefined;
try { return JSON.parse(value) as T; } catch { return undefined; }
};
@ApiTags('stock-status')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('stock-statuses')
export class StockStatusController {
constructor(private readonly stockStatusService: StockStatusService) {}
@Get()
findAll(
@Query('skip') skip?: string,
@Query('take') take?: string,
@Query('orderBy') orderBy?: string,
@Query('where') where?: string,
) {
return this.stockStatusService.findAll({
skip: skip ? Number(skip) : 0,
take: take ? Number(take) : 25,
orderBy: parseJson(orderBy),
where: parseJson(where),
});
}
@Get(':productId')
findOne(@Param('productId') productId: string) {
return this.stockStatusService.findOne({ productId });
}
@Post()
create(@Body() dto: CreateStockStatusDto) {
return this.stockStatusService.create(dto);
}
@Patch(':productId')
update(@Param('productId') productId: string, @Body() dto: UpdateStockStatusDto) {
return this.stockStatusService.update({ where: { productId }, data: dto });
}
@Delete(':productId')
remove(@Param('productId') productId: string) {
return this.stockStatusService.remove({ productId });
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { StockStatusService } from './stock-status.service';
import { StockStatusController } from './stock-status.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
controllers: [StockStatusController],
providers: [StockStatusService, PrismaService],
})
export class StockStatusModule {}

View File

@@ -0,0 +1,69 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { StockStatus, Prisma } from '@prisma/client';
@Injectable()
export class StockStatusService {
constructor(private prisma: PrismaService) {}
async findAll(params: {
skip?: number;
take?: number;
where?: Prisma.StockStatusWhereInput;
orderBy?: Prisma.StockStatusOrderByWithRelationInput;
}): Promise<{ data: StockStatus[]; total: number }> {
const { skip, take, where, orderBy } = params;
const [data, total] = await this.prisma.$transaction([
this.prisma.stockStatus.findMany({
skip,
take,
where,
orderBy,
include: {
product: true,
},
}),
this.prisma.stockStatus.count({ where }),
]);
return { data, total };
}
async findOne(where: Prisma.StockStatusWhereUniqueInput): Promise<StockStatus | null> {
return this.prisma.stockStatus.findUnique({
where,
include: {
product: true,
},
});
}
async create(data: Prisma.StockStatusCreateInput): Promise<StockStatus> {
return this.prisma.stockStatus.create({
data,
include: {
product: true,
},
});
}
async update(params: {
where: Prisma.StockStatusWhereUniqueInput;
data: Prisma.StockStatusUpdateInput;
}): Promise<StockStatus> {
return this.prisma.stockStatus.update({
...params,
include: {
product: true,
},
});
}
async remove(where: Prisma.StockStatusWhereUniqueInput): Promise<StockStatus> {
return this.prisma.stockStatus.delete({
where,
include: {
product: true,
},
});
}
}