diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..9070fbe --- /dev/null +++ b/backend/prisma/schema.prisma @@ -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") +} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts new file mode 100644 index 0000000..eadb42a --- /dev/null +++ b/backend/src/app.module.ts @@ -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 {} \ No newline at end of file diff --git a/backend/src/product-type/dto/create-product-type.dto.ts b/backend/src/product-type/dto/create-product-type.dto.ts new file mode 100644 index 0000000..5917693 --- /dev/null +++ b/backend/src/product-type/dto/create-product-type.dto.ts @@ -0,0 +1,9 @@ +import { IsString, IsNotEmpty } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateProductTypeDto { + @ApiProperty({ description: 'Наименование типа товара' }) + @IsString() + @IsNotEmpty() + name: string; +} \ No newline at end of file diff --git a/backend/src/product-type/dto/update-product-type.dto.ts b/backend/src/product-type/dto/update-product-type.dto.ts new file mode 100644 index 0000000..f31cda6 --- /dev/null +++ b/backend/src/product-type/dto/update-product-type.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateProductTypeDto } from './create-product-type.dto'; + +export class UpdateProductTypeDto extends PartialType(CreateProductTypeDto) {} \ No newline at end of file diff --git a/backend/src/product-type/product-type.controller.ts b/backend/src/product-type/product-type.controller.ts new file mode 100644 index 0000000..1a2d8a0 --- /dev/null +++ b/backend/src/product-type/product-type.controller.ts @@ -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 = (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 }); + } +} \ No newline at end of file diff --git a/backend/src/product-type/product-type.module.ts b/backend/src/product-type/product-type.module.ts new file mode 100644 index 0000000..a2b3564 --- /dev/null +++ b/backend/src/product-type/product-type.module.ts @@ -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 {} \ No newline at end of file diff --git a/backend/src/product-type/product-type.service.ts b/backend/src/product-type/product-type.service.ts new file mode 100644 index 0000000..2a0ebaf --- /dev/null +++ b/backend/src/product-type/product-type.service.ts @@ -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 { + return this.prisma.productType.findUnique({ where }); + } + + async create(data: Prisma.ProductTypeCreateInput): Promise { + return this.prisma.productType.create({ data }); + } + + async update(params: { + where: Prisma.ProductTypeWhereUniqueInput; + data: Prisma.ProductTypeUpdateInput; + }): Promise { + return this.prisma.productType.update(params); + } + + async remove(where: Prisma.ProductTypeWhereUniqueInput): Promise { + return this.prisma.productType.delete({ where }); + } +} \ No newline at end of file diff --git a/backend/src/product/dto/create-product.dto.ts b/backend/src/product/dto/create-product.dto.ts new file mode 100644 index 0000000..5ea5086 --- /dev/null +++ b/backend/src/product/dto/create-product.dto.ts @@ -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; +} \ No newline at end of file diff --git a/backend/src/product/dto/update-product.dto.ts b/backend/src/product/dto/update-product.dto.ts new file mode 100644 index 0000000..68a1e7c --- /dev/null +++ b/backend/src/product/dto/update-product.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateProductDto } from './create-product.dto'; + +export class UpdateProductDto extends PartialType(CreateProductDto) {} \ No newline at end of file diff --git a/backend/src/product/product.controller.ts b/backend/src/product/product.controller.ts new file mode 100644 index 0000000..b8ebb5f --- /dev/null +++ b/backend/src/product/product.controller.ts @@ -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 = (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 }); + } +} \ No newline at end of file diff --git a/backend/src/product/product.module.ts b/backend/src/product/product.module.ts new file mode 100644 index 0000000..93a16c6 --- /dev/null +++ b/backend/src/product/product.module.ts @@ -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 {} \ No newline at end of file diff --git a/backend/src/product/product.service.ts b/backend/src/product/product.service.ts new file mode 100644 index 0000000..b267b2f --- /dev/null +++ b/backend/src/product/product.service.ts @@ -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 { + return this.prisma.product.findUnique({ + where, + include: { + productType: true, + parent: true, + }, + }); + } + + async create(data: Prisma.ProductCreateInput): Promise { + return this.prisma.product.create({ + data, + include: { + productType: true, + parent: true, + }, + }); + } + + async update(params: { + where: Prisma.ProductWhereUniqueInput; + data: Prisma.ProductUpdateInput; + }): Promise { + return this.prisma.product.update({ + ...params, + include: { + productType: true, + parent: true, + }, + }); + } + + async remove(where: Prisma.ProductWhereUniqueInput): Promise { + return this.prisma.product.delete({ + where, + include: { + productType: true, + parent: true, + }, + }); + } +} \ No newline at end of file diff --git a/backend/src/stock-movement/dto/create-stock-movement-line.dto.ts b/backend/src/stock-movement/dto/create-stock-movement-line.dto.ts new file mode 100644 index 0000000..96c8f43 --- /dev/null +++ b/backend/src/stock-movement/dto/create-stock-movement-line.dto.ts @@ -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; +} \ No newline at end of file diff --git a/backend/src/stock-movement/dto/create-stock-movement.dto.ts b/backend/src/stock-movement/dto/create-stock-movement.dto.ts new file mode 100644 index 0000000..1e6b5c6 --- /dev/null +++ b/backend/src/stock-movement/dto/create-stock-movement.dto.ts @@ -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; +} \ No newline at end of file diff --git a/backend/src/stock-movement/dto/update-stock-movement-line.dto.ts b/backend/src/stock-movement/dto/update-stock-movement-line.dto.ts new file mode 100644 index 0000000..ee3ec3c --- /dev/null +++ b/backend/src/stock-movement/dto/update-stock-movement-line.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateStockMovementLineDto } from './create-stock-movement-line.dto'; + +export class UpdateStockMovementLineDto extends PartialType(CreateStockMovementLineDto) {} \ No newline at end of file diff --git a/backend/src/stock-movement/dto/update-stock-movement.dto.ts b/backend/src/stock-movement/dto/update-stock-movement.dto.ts new file mode 100644 index 0000000..f7ff2c4 --- /dev/null +++ b/backend/src/stock-movement/dto/update-stock-movement.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateStockMovementDto } from './create-stock-movement.dto'; + +export class UpdateStockMovementDto extends PartialType(CreateStockMovementDto) {} \ No newline at end of file diff --git a/backend/src/stock-movement/stock-movement.controller.ts b/backend/src/stock-movement/stock-movement.controller.ts new file mode 100644 index 0000000..e2d775f --- /dev/null +++ b/backend/src/stock-movement/stock-movement.controller.ts @@ -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 = (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 }); + } +} \ No newline at end of file diff --git a/backend/src/stock-movement/stock-movement.module.ts b/backend/src/stock-movement/stock-movement.module.ts new file mode 100644 index 0000000..fd27aad --- /dev/null +++ b/backend/src/stock-movement/stock-movement.module.ts @@ -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 {} \ No newline at end of file diff --git a/backend/src/stock-movement/stock-movement.service.ts b/backend/src/stock-movement/stock-movement.service.ts new file mode 100644 index 0000000..c791353 --- /dev/null +++ b/backend/src/stock-movement/stock-movement.service.ts @@ -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 { + return this.prisma.stockMovement.findUnique({ + where, + include: { + lines: { + include: { + product: true, + }, + }, + }, + }); + } + + async create(data: Prisma.StockMovementCreateInput): Promise { + return this.prisma.stockMovement.create({ + data, + include: { + lines: { + include: { + product: true, + }, + }, + }, + }); + } + + async update(params: { + where: Prisma.StockMovementWhereUniqueInput; + data: Prisma.StockMovementUpdateInput; + }): Promise { + return this.prisma.stockMovement.update({ + ...params, + include: { + lines: { + include: { + product: true, + }, + }, + }, + }); + } + + async remove(where: Prisma.StockMovementWhereUniqueInput): Promise { + return this.prisma.stockMovement.delete({ + where, + include: { + lines: { + include: { + product: true, + }, + }, + }, + }); + } +} \ No newline at end of file diff --git a/backend/src/stock-status/dto/create-stock-status.dto.ts b/backend/src/stock-status/dto/create-stock-status.dto.ts new file mode 100644 index 0000000..af58538 --- /dev/null +++ b/backend/src/stock-status/dto/create-stock-status.dto.ts @@ -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; +} \ No newline at end of file diff --git a/backend/src/stock-status/dto/update-stock-status.dto.ts b/backend/src/stock-status/dto/update-stock-status.dto.ts new file mode 100644 index 0000000..516f331 --- /dev/null +++ b/backend/src/stock-status/dto/update-stock-status.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateStockStatusDto } from './create-stock-status.dto'; + +export class UpdateStockStatusDto extends PartialType(CreateStockStatusDto) {} \ No newline at end of file diff --git a/backend/src/stock-status/stock-status.controller.ts b/backend/src/stock-status/stock-status.controller.ts new file mode 100644 index 0000000..ea8cbc3 --- /dev/null +++ b/backend/src/stock-status/stock-status.controller.ts @@ -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 = (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 }); + } +} \ No newline at end of file diff --git a/backend/src/stock-status/stock-status.module.ts b/backend/src/stock-status/stock-status.module.ts new file mode 100644 index 0000000..d9e8023 --- /dev/null +++ b/backend/src/stock-status/stock-status.module.ts @@ -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 {} \ No newline at end of file diff --git a/backend/src/stock-status/stock-status.service.ts b/backend/src/stock-status/stock-status.service.ts new file mode 100644 index 0000000..4ceaf1e --- /dev/null +++ b/backend/src/stock-status/stock-status.service.ts @@ -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 { + return this.prisma.stockStatus.findUnique({ + where, + include: { + product: true, + }, + }); + } + + async create(data: Prisma.StockStatusCreateInput): Promise { + return this.prisma.stockStatus.create({ + data, + include: { + product: true, + }, + }); + } + + async update(params: { + where: Prisma.StockStatusWhereUniqueInput; + data: Prisma.StockStatusUpdateInput; + }): Promise { + return this.prisma.stockStatus.update({ + ...params, + include: { + product: true, + }, + }); + } + + async remove(where: Prisma.StockStatusWhereUniqueInput): Promise { + return this.prisma.stockStatus.delete({ + where, + include: { + product: true, + }, + }); + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..bdb303c --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,60 @@ +import { Admin, Resource } from 'react-admin'; +import { dataProvider } from './dataProvider'; +import { authProvider } from './authProvider'; + +// ProductType resources +import { ProductTypeList, ProductTypeCreate, ProductTypeEdit, ProductTypeShow } from './resources/product-type'; + +// Product resources +import { ProductList, ProductCreate, ProductEdit, ProductShow } from './resources/product'; + +// StockStatus resources +import { StockStatusList, StockStatusCreate, StockStatusEdit, StockStatusShow } from './resources/stock-status'; + +// StockMovement resources +import { StockMovementList, StockMovementCreate, StockMovementEdit, StockMovementShow } from './resources/stock-movement'; + +// StockMovementLine resources +import { StockMovementLineList, StockMovementLineCreate, StockMovementLineEdit, StockMovementLineShow } from './resources/stock-movement-line'; + +const App = () => ( + + + + + + + +); + +export default App; \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..7a4e4db --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import { initKeycloak } from './authProvider'; + +// Initialize Keycloak before rendering the app +initKeycloak().then(() => { + ReactDOM.createRoot(document.getElementById('root')!).render( + + + + ); +}).catch((error) => { + console.error('Failed to initialize Keycloak:', error); +}); \ No newline at end of file diff --git a/frontend/src/resources/product-type/ProductTypeCreate.tsx b/frontend/src/resources/product-type/ProductTypeCreate.tsx new file mode 100644 index 0000000..7e5de0f --- /dev/null +++ b/frontend/src/resources/product-type/ProductTypeCreate.tsx @@ -0,0 +1,9 @@ +import { Create, SimpleForm, TextInput } from 'react-admin'; + +export const ProductTypeCreate = () => ( + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product-type/ProductTypeEdit.tsx b/frontend/src/resources/product-type/ProductTypeEdit.tsx new file mode 100644 index 0000000..ddc13ba --- /dev/null +++ b/frontend/src/resources/product-type/ProductTypeEdit.tsx @@ -0,0 +1,10 @@ +import { Edit, SimpleForm, TextInput } from 'react-admin'; + +export const ProductTypeEdit = () => ( + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product-type/ProductTypeList.tsx b/frontend/src/resources/product-type/ProductTypeList.tsx new file mode 100644 index 0000000..d5c724d --- /dev/null +++ b/frontend/src/resources/product-type/ProductTypeList.tsx @@ -0,0 +1,13 @@ +import { List, DataTable, TextField, DateField, EditButton } from 'react-admin'; + +export const ProductTypeList = () => ( + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product-type/ProductTypeShow.tsx b/frontend/src/resources/product-type/ProductTypeShow.tsx new file mode 100644 index 0000000..3e1de3f --- /dev/null +++ b/frontend/src/resources/product-type/ProductTypeShow.tsx @@ -0,0 +1,12 @@ +import { Show, SimpleShowLayout, TextField, DateField } from 'react-admin'; + +export const ProductTypeShow = () => ( + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product-type/index.ts b/frontend/src/resources/product-type/index.ts new file mode 100644 index 0000000..6b792bd --- /dev/null +++ b/frontend/src/resources/product-type/index.ts @@ -0,0 +1,4 @@ +export { ProductTypeList } from './ProductTypeList'; +export { ProductTypeEdit } from './ProductTypeEdit'; +export { ProductTypeCreate } from './ProductTypeCreate'; +export { ProductTypeShow } from './ProductTypeShow'; \ No newline at end of file diff --git a/frontend/src/resources/product/ProductCreate.tsx b/frontend/src/resources/product/ProductCreate.tsx new file mode 100644 index 0000000..7fdf452 --- /dev/null +++ b/frontend/src/resources/product/ProductCreate.tsx @@ -0,0 +1,15 @@ +import { Create, SimpleForm, TextInput, ReferenceInput, SelectInput } from 'react-admin'; + +export const ProductCreate = () => ( + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product/ProductEdit.tsx b/frontend/src/resources/product/ProductEdit.tsx new file mode 100644 index 0000000..9bff72f --- /dev/null +++ b/frontend/src/resources/product/ProductEdit.tsx @@ -0,0 +1,16 @@ +import { Edit, SimpleForm, TextInput, ReferenceInput, SelectInput } from 'react-admin'; + +export const ProductEdit = () => ( + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product/ProductList.tsx b/frontend/src/resources/product/ProductList.tsx new file mode 100644 index 0000000..5223810 --- /dev/null +++ b/frontend/src/resources/product/ProductList.tsx @@ -0,0 +1,19 @@ +import { List, DataTable, TextField, ReferenceField, DateField, EditButton } from 'react-admin'; + +export const ProductList = () => ( + + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product/ProductShow.tsx b/frontend/src/resources/product/ProductShow.tsx new file mode 100644 index 0000000..15b8f56 --- /dev/null +++ b/frontend/src/resources/product/ProductShow.tsx @@ -0,0 +1,18 @@ +import { Show, SimpleShowLayout, TextField, ReferenceField, DateField } from 'react-admin'; + +export const ProductShow = () => ( + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/product/index.ts b/frontend/src/resources/product/index.ts new file mode 100644 index 0000000..072df72 --- /dev/null +++ b/frontend/src/resources/product/index.ts @@ -0,0 +1,4 @@ +export { ProductList } from './ProductList'; +export { ProductEdit } from './ProductEdit'; +export { ProductCreate } from './ProductCreate'; +export { ProductShow } from './ProductShow'; \ No newline at end of file diff --git a/frontend/src/resources/stock-movement-line/StockMovementLineCreate.tsx b/frontend/src/resources/stock-movement-line/StockMovementLineCreate.tsx new file mode 100644 index 0000000..122fc09 --- /dev/null +++ b/frontend/src/resources/stock-movement-line/StockMovementLineCreate.tsx @@ -0,0 +1,16 @@ +import { Create, SimpleForm, ReferenceInput, SelectInput, NumberInput, BooleanInput } from 'react-admin'; + +export const StockMovementLineCreate = () => ( + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement-line/StockMovementLineEdit.tsx b/frontend/src/resources/stock-movement-line/StockMovementLineEdit.tsx new file mode 100644 index 0000000..d9b5b46 --- /dev/null +++ b/frontend/src/resources/stock-movement-line/StockMovementLineEdit.tsx @@ -0,0 +1,17 @@ +import { Edit, SimpleForm, TextInput, ReferenceInput, SelectInput, NumberInput, BooleanInput } from 'react-admin'; + +export const StockMovementLineEdit = () => ( + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement-line/StockMovementLineList.tsx b/frontend/src/resources/stock-movement-line/StockMovementLineList.tsx new file mode 100644 index 0000000..94e0566 --- /dev/null +++ b/frontend/src/resources/stock-movement-line/StockMovementLineList.tsx @@ -0,0 +1,20 @@ +import { List, DataTable, TextField, ReferenceField, NumberField, BooleanField, DateField, EditButton } from 'react-admin'; + +export const StockMovementLineList = () => ( + + + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement-line/StockMovementLineShow.tsx b/frontend/src/resources/stock-movement-line/StockMovementLineShow.tsx new file mode 100644 index 0000000..02b6926 --- /dev/null +++ b/frontend/src/resources/stock-movement-line/StockMovementLineShow.tsx @@ -0,0 +1,19 @@ +import { Show, SimpleShowLayout, TextField, ReferenceField, NumberField, BooleanField, DateField } from 'react-admin'; + +export const StockMovementLineShow = () => ( + + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement-line/index.ts b/frontend/src/resources/stock-movement-line/index.ts new file mode 100644 index 0000000..6b048e7 --- /dev/null +++ b/frontend/src/resources/stock-movement-line/index.ts @@ -0,0 +1,4 @@ +export { StockMovementLineList } from './StockMovementLineList'; +export { StockMovementLineEdit } from './StockMovementLineEdit'; +export { StockMovementLineCreate } from './StockMovementLineCreate'; +export { StockMovementLineShow } from './StockMovementLineShow'; \ No newline at end of file diff --git a/frontend/src/resources/stock-movement/StockMovementCreate.tsx b/frontend/src/resources/stock-movement/StockMovementCreate.tsx new file mode 100644 index 0000000..7ecdb7e --- /dev/null +++ b/frontend/src/resources/stock-movement/StockMovementCreate.tsx @@ -0,0 +1,18 @@ +import { Create, SimpleForm, TextInput, DateInput, SelectInput } from 'react-admin'; + +const kindChoices = [ + { id: 'Receipt', name: 'Receipt' }, + { id: 'Issue', name: 'Issue' }, + { id: 'Transfer', name: 'Transfer' }, + { id: 'Adjustment', name: 'Adjustment' }, +]; + +export const StockMovementCreate = () => ( + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement/StockMovementEdit.tsx b/frontend/src/resources/stock-movement/StockMovementEdit.tsx new file mode 100644 index 0000000..e107901 --- /dev/null +++ b/frontend/src/resources/stock-movement/StockMovementEdit.tsx @@ -0,0 +1,19 @@ +import { Edit, SimpleForm, TextInput, DateInput, SelectInput } from 'react-admin'; + +const kindChoices = [ + { id: 'Receipt', name: 'Receipt' }, + { id: 'Issue', name: 'Issue' }, + { id: 'Transfer', name: 'Transfer' }, + { id: 'Adjustment', name: 'Adjustment' }, +]; + +export const StockMovementEdit = () => ( + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement/StockMovementList.tsx b/frontend/src/resources/stock-movement/StockMovementList.tsx new file mode 100644 index 0000000..0651e3c --- /dev/null +++ b/frontend/src/resources/stock-movement/StockMovementList.tsx @@ -0,0 +1,15 @@ +import { List, DataTable, TextField, DateField, EditButton } from 'react-admin'; + +export const StockMovementList = () => ( + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement/StockMovementShow.tsx b/frontend/src/resources/stock-movement/StockMovementShow.tsx new file mode 100644 index 0000000..e6eb152 --- /dev/null +++ b/frontend/src/resources/stock-movement/StockMovementShow.tsx @@ -0,0 +1,14 @@ +import { Show, SimpleShowLayout, TextField, DateField } from 'react-admin'; + +export const StockMovementShow = () => ( + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-movement/index.ts b/frontend/src/resources/stock-movement/index.ts new file mode 100644 index 0000000..0ef0903 --- /dev/null +++ b/frontend/src/resources/stock-movement/index.ts @@ -0,0 +1,4 @@ +export { StockMovementList } from './StockMovementList'; +export { StockMovementEdit } from './StockMovementEdit'; +export { StockMovementCreate } from './StockMovementCreate'; +export { StockMovementShow } from './StockMovementShow'; \ No newline at end of file diff --git a/frontend/src/resources/stock-status/StockStatusCreate.tsx b/frontend/src/resources/stock-status/StockStatusCreate.tsx new file mode 100644 index 0000000..ac292b1 --- /dev/null +++ b/frontend/src/resources/stock-status/StockStatusCreate.tsx @@ -0,0 +1,13 @@ +import { Create, SimpleForm, ReferenceInput, SelectInput, NumberInput, BooleanInput } from 'react-admin'; + +export const StockStatusCreate = () => ( + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-status/StockStatusEdit.tsx b/frontend/src/resources/stock-status/StockStatusEdit.tsx new file mode 100644 index 0000000..753ab04 --- /dev/null +++ b/frontend/src/resources/stock-status/StockStatusEdit.tsx @@ -0,0 +1,13 @@ +import { Edit, SimpleForm, ReferenceInput, SelectInput, NumberInput, BooleanInput } from 'react-admin'; + +export const StockStatusEdit = () => ( + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-status/StockStatusList.tsx b/frontend/src/resources/stock-status/StockStatusList.tsx new file mode 100644 index 0000000..691b3a6 --- /dev/null +++ b/frontend/src/resources/stock-status/StockStatusList.tsx @@ -0,0 +1,15 @@ +import { List, DataTable, ReferenceField, TextField, NumberField, BooleanField, DateField, EditButton } from 'react-admin'; + +export const StockStatusList = () => ( + + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-status/StockStatusShow.tsx b/frontend/src/resources/stock-status/StockStatusShow.tsx new file mode 100644 index 0000000..3d09ea6 --- /dev/null +++ b/frontend/src/resources/stock-status/StockStatusShow.tsx @@ -0,0 +1,14 @@ +import { Show, SimpleShowLayout, ReferenceField, TextField, NumberField, BooleanField, DateField } from 'react-admin'; + +export const StockStatusShow = () => ( + + + + + + + + + + +); \ No newline at end of file diff --git a/frontend/src/resources/stock-status/index.ts b/frontend/src/resources/stock-status/index.ts new file mode 100644 index 0000000..0a8f0c2 --- /dev/null +++ b/frontend/src/resources/stock-status/index.ts @@ -0,0 +1,4 @@ +export { StockStatusList } from './StockStatusList'; +export { StockStatusEdit } from './StockStatusEdit'; +export { StockStatusCreate } from './StockStatusCreate'; +export { StockStatusShow } from './StockStatusShow'; \ No newline at end of file