Repository: naukmagymdb/gym
Pull request #50: Suppliers issue resolved
Author: login: MaksymSemk
Created: 2025-04-29T21:02:20Z | Merged: 2025-04-30T16:49:35Z

Fixes #41 
Realised endpoints for supplier

Linked issue #41: Suppliers
- **GET** `/suppliers`
- **GET** `/suppliers/:id`
- **GET** `/suppliers/:id/products`
- **POST** `/suppliers`
- **PUT** `/suppliers/:id`
- **DELETE** `/suppliers/:id`

Diff:
diff --git a/packages/backend/src/database/schema/schema.sql b/packages/backend/src/database/schema/schema.sql
index b684fde..012fc4e 100644
--- a/packages/backend/src/database/schema/schema.sql
+++ b/packages/backend/src/database/schema/schema.sql
@@ -88,6 +88,60 @@ CREATE TABLE Supplier_Products (
     ON DELETE NO ACTION ON UPDATE CASCADE
 );
 
+-- Insert into Supplier table
+INSERT INTO Supplier (EDRPOU, Phone_num, Email) VALUES
+(31000432, '+380501112233', 'fitgear@suppliers.ua'),
+(32450981, '+380631234111', 'activepro@equipments.com'),
+(33789412, '+380671998877', 'powerup@sportsup.ua'),
+(34982017, '+380932226655', 'gymbasics@fitmail.net'),
+(36212045, '+380681234567', 'protrainer@suphub.org'),
+(37550987, '+380991111222', 'sportx@suppliers.ua');
+
+-- Expanded Products
+INSERT INTO Products (goods_name) VALUES
+('Football Ball'),
+('Basketball Ball'),
+('Tennis Racket'),
+('Boxing Gloves'),
+('Running Shoes'),
+('Swimming Goggles'),
+('Dumbbells 5kg'),
+('Dumbbells 10kg'),
+('Treadmill Pro 5000'),
+('Exercise Bike X100'),
+('Resistance Bands Set'),
+('Yoga Block Cork'),
+('Yoga Mat Deluxe'),
+('Pull-up Bar'),
+('Kettlebell 12kg'),
+('Speed Rope'),
+('Bench Press Machine'),
+('Elliptical Trainer');
+
+-- Link Suppliers to Products
+INSERT INTO Supplier_Products (EDRPOU, Goods_id) VALUES
+-- fitgear
+(31000432, 1), (31000432, 5), (31000432, 7), (31000432, 11),
+-- activepro
+(32450981, 2), (32450981, 6), (32450981, 8), (32450981, 10), (32450981, 17),
+-- powerup
+(33789412, 3), (33789412, 4), (33789412, 9), (33789412, 12), (33789412, 16),
+-- gymbasics
+(34982017, 13), (34982017, 14), (34982017, 15), (34982017, 18),
+-- protrainer
+(36212045, 2), (36212045, 5), (36212045, 7), (36212045, 13), (36212045, 17),
+-- sportx
+(37550987, 1), (37550987, 6), (37550987, 10), (37550987, 15), (37550987, 18),
+
+-- Shared supplies
+(31000432, 13),
+(32450981, 1),
+(33789412, 5),
+(34982017, 7),
+(36212045, 10),
+(37550987, 11);
+
+
 -- Abonements
 CREATE TABLE Abonementtype (
   Abonement_type VARCHAR(50) PRIMARY KEY,
diff --git a/packages/backend/src/repositories/repository.module.ts b/packages/backend/src/repositories/repository.module.ts
index 4bf1350..0ad510a 100644
--- a/packages/backend/src/repositories/repository.module.ts
+++ b/packages/backend/src/repositories/repository.module.ts
@@ -2,9 +2,16 @@ import { Module } from '@nestjs/common';
 import { DepartmentModule } from './departments/department.module';
 import { ProductsModule } from './products/products.module';
 import { StaffModule } from './staff/staff.module';
+import { SuppliersModule } from './suppliers/suppliers.module';
 import { VisitorModule } from './visitors/visitor.module';
 
 @Module({
-  imports: [DepartmentModule, StaffModule, VisitorModule, ProductsModule],
+  imports: [
+    DepartmentModule,
+    StaffModule,
+    VisitorModule,
+    ProductsModule,
+    SuppliersModule,
+  ],
 })
 export class RepositoryModule {}
diff --git a/packages/backend/src/repositories/suppliers/dto/create-supplier.dto.ts b/packages/backend/src/repositories/suppliers/dto/create-supplier.dto.ts
new file mode 100644
index 0000000..6766e4e
--- /dev/null
+++ b/packages/backend/src/repositories/suppliers/dto/create-supplier.dto.ts
@@ -0,0 +1,15 @@
+import { IsEmail, IsNotEmpty, IsPhoneNumber, IsString } from 'class-validator';
+
+export class CreateSupplierDto {
+  @IsNotEmpty()
+  @IsString()
+  edrpou: string;
+
+  @IsNotEmpty()
+  @IsEmail()
+  email: string;
+
+  @IsNotEmpty()
+  @IsPhoneNumber()
+  phone_num: string;
+}
diff --git a/packages/backend/src/repositories/suppliers/dto/update-supplier.dto.ts b/packages/backend/src/repositories/suppliers/dto/update-supplier.dto.ts
new file mode 100644
index 0000000..1da6eed
--- /dev/null
+++ b/packages/backend/src/repositories/suppliers/dto/update-supplier.dto.ts
@@ -0,0 +1,11 @@
+import { IsEmail, IsOptional, IsPhoneNumber } from 'class-validator';
+
+export class UpdateSupplierDto {
+  @IsOptional()
+  @IsEmail()
+  email?: string | null;
+
+  @IsOptional()
+  @IsPhoneNumber()
+  phone_num?: string | null;
+}
diff --git a/packages/backend/src/repositories/suppliers/suppliers.controller.ts b/packages/backend/src/repositories/suppliers/suppliers.controller.ts
new file mode 100644
index 0000000..174bc1b
--- /dev/null
+++ b/packages/backend/src/repositories/suppliers/suppliers.controller.ts
@@ -0,0 +1,59 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  ParseIntPipe, // Import ParseIntPipe
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppliersService: SuppliersService) {}
+
+  @Post()
+  create(@Body() supplierDto: CreateSupplierDto) {
+    return this.suppliersService.create(supplierDto);
+  }
+
+  @Get()
+  findAll(
+    @Query('sortBy') sortBy: string = 'edrpou',
+    @Query('order') order: 'asc' | 'desc' = 'asc',
+  ) {
+    return this.suppliersService.findAll(sortBy, order);
+  }
+
+  @Get(':id')
+  findOne(@Param('id', ParseIntPipe) edrpou: number) {
+    return this.suppliersService.findOne(edrpou);
+  }
+
+  @Get(':id/products')
+  getProductsBySupplier(
+    @Param('id', ParseIntPipe) edrpou: number,
+    @Query('sortBy') sortBy: string = 'goods_id',
+    @Query('order') order: 'asc' | 'desc' = 'asc',
+  ) {
+    return this.suppliersService.getProductsBySupplier(edrpou, sortBy, order);
+  }
+
+  @Put(':id')
+  update(
+    @Param('id', ParseIntPipe) edrpou: number,
+    @Body() updateSupplierDto: UpdateSupplierDto,
+  ) {
+    return this.suppliersService.update(edrpou, updateSupplierDto);
+  }
+
+  @Delete(':id')
+  remove(@Param('id', ParseIntPipe) edrpou: number) {
+    return this.suppliersService.delete(edrpou);
+  }
+}
diff --git a/packages/backend/src/repositories/suppliers/suppliers.module.ts b/packages/backend/src/repositories/suppliers/suppliers.module.ts
new file mode 100644
index 0000000..efd11a2
--- /dev/null
+++ b/packages/backend/src/repositories/suppliers/suppliers.module.ts
@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { SuppliersController } from './suppliers.controller';
+import { SuppliersService } from './suppliers.repository';
+
+@Module({
+  controllers: [SuppliersController],
+  providers: [SuppliersService],
+})
+export class SuppliersModule {}
diff --git a/packages/backend/src/repositories/suppliers/suppliers.repository.ts b/packages/backend/src/repositories/suppliers/suppliers.repository.ts
new file mode 100644
index 0000000..2aada5d
--- /dev/null
+++ b/packages/backend/src/repositories/suppliers/suppliers.repository.ts
@@ -0,0 +1,111 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(supplierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.one(query, supplierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  async findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return await this.db.any(query);
+  }
+
+  async findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    return await this.db.oneOrNone(query, [edrpou]);
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = $(email)');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = $(phone_num)');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  async delete(edrpou: number) {
+    const query = `
+      DELETE FROM supplier 
+      WHERE edrpou = $1
+      RETURNING *
+    `;
+    return await this.db.oneOrNone(query, [edrpou]);
+  }
+
+  async getProductsBySupplier(
+    edrpou: number,
+    sortBy: string,
+    order: 'asc' | 'desc',
+  ) {
+    const allowedSortFields = ['goods_id', 'goods_name']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT *
+      FROM products
+      WHERE goods_id IN (
+        SELECT goods_id
+        FROM supplier_products
+        WHERE edrpou = $1
+      )
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+
+    return await this.db.any(query, [edrpou]);
+  }
+}


Reviews:
user: login: myroslavve
body: Взагалі варто це ще обговорити але по-моєму коли в базі даних не знаходите дані на які прийшов реквест краще помилку кидати а не null просто повертати, бо потім на фронтенді це не дуже зручно буде (не критично але краще коли буде якась 404 чи 500 помилка прилітати і можна зразу там юзеру сказати що створити не вдалось чи щось таке).
@EWOKWICKET @MaksymSemk подумайте
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T00:07:53Z
user: login: myroslavve
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T08:21:13Z
user: login: EWOKWICKET
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T08:36:26Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T08:51:51Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T08:55:06Z
user: login: EWOKWICKET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T09:27:32Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T09:29:56Z
user: login: EWOKWICKET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T10:16:38Z
user: login: EWOKWICKET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T11:42:36Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:31:27Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:32:33Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:44:56Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:45:09Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:47:19Z
user: login: myroslavve
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T13:20:35Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:07:34Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:08:10Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:32:20Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:32:35Z
user: login: EWOKWICKET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:37:48Z
user: login: EWOKWICKET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:37:55Z
user: login: myroslavve
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T14:55:13Z
user: login: EWOKWICKET
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T16:48:41Z

Review comments:
diff_hunk: @@ -0,0 +1,58 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppiersService: SuppliersService) {}
path: packages/backend/src/repositories/suppliers/suppliers.controller.ts
user: login: myroslavve
body: suppliersService
created_at: 2025-04-29T23:21:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppiersService: SuppliersService) {}
+
+  @Post()
+  create(@Body() suppierDto: CreateSupplierDto) {
path: packages/backend/src/repositories/suppliers/suppliers.controller.ts
user: login: myroslavve
body: supplierDto
created_at: 2025-04-29T23:22:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: myroslavve
body: краще тут помилку кидати, бо в контролері бачу що просто повертається результат цієї функції і виходить що на фронтенд ця стрічка буде приходити, це не зручно буде обробляти.
created_at: 2025-04-29T23:45:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: myroslavve
body: В `` стрічках (template strings) джаваскрипт підставляє змінні в ${}. тут краще як ти і в інших зробив звичайні дужки використати і передати ті параметри в функцію яка викликає цю query. (Ну бо щоб захиститись від SQL injections)
created_at: 2025-04-30T00:00:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
+    const query = `
+      DELETE FROM supplier 
+      WHERE edrpou = $1
+      RETURNING *
+    `;
+    return this.db.oneOrNone(query, [edrpou]);
+  }
+
+  getProductsBySupplier(edrpou: number, sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['goods_id', 'goods_name']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT *
+      FROM products
+      WHERE goods_id IN (
+        SELECT goods_id
+        FROM supplier_products
+        WHERE edrpou = $1
+      )
+      ORDER BY ${safeSortBy} ${safeOrder}
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: myroslavve
body: тут теж дужки звичайні для безпеки)
created_at: 2025-04-30T00:02:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: myroslavve
body: Ну раз ви пізніше зробите то ок ігноруй це поки
created_at: 2025-04-30T08:21:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,11 @@
+import { IsEmail, IsOptional, IsPhoneNumber } from 'class-validator';
+
+export class UpdateSupplierDto {
+  @IsOptional()
+  @IsEmail()
+  email: string;
+
+  @IsOptional()
+  @IsPhoneNumber()
+  phone_num: string;
+}
path: packages/backend/src/repositories/suppliers/dto/update-supplier.dto.ts
user: login: EWOKWICKET
body: зроби поля nullable. IsOptional() для валідації, а nullable для тс
created_at: 2025-04-30T08:22:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppiersService: SuppliersService) {}
+
+  @Post()
+  create(@Body() suppierDto: CreateSupplierDto) {
+    return this.suppiersService.create(suppierDto);
+  }
+
+  @Get()
+  findAll(
+    @Query('sortBy') sortBy: string = 'edrpou',
+    @Query('order') order: 'asc' | 'desc' = 'asc',
+  ) {
+    return this.suppiersService.findAll(sortBy, order);
+  }
+
+  @Get(':id')
+  findOne(@Param('id') edrpou: string) {
+    return this.suppiersService.findOne(+edrpou);
+  }
path: packages/backend/src/repositories/suppliers/suppliers.controller.ts
user: login: EWOKWICKET
body: в таких випадках краще ParseIntPipe використовуй, щоб воно на вході перетворювало. Так чистіше буде
created_at: 2025-04-30T08:24:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: наче немає сенсу використовувати oneOrNone саме для create, бо воно або створить і поверне, або кине помилку
created_at: 2025-04-30T08:29:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: я для цього, до речі, в останньому пр додав repository.service, де є метод prepareUpdateData. Ну це не проблема
created_at: 2025-04-30T08:32:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: тут назва чітко delete має бути
created_at: 2025-04-30T08:34:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppiersService: SuppliersService) {}
path: packages/backend/src/repositories/suppliers/suppliers.controller.ts
user: login: MaksymSemk
body: Це я зробив одруківку при автоматичному створенні цих всіх файлків і виходить не всюди виправив одруківки. Перероблю
created_at: 2025-04-30T08:51:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: Виходить, що NestJS автоматично генерує метод remove в реквесті delete. Я думав краще залишити все як є. Якщо треба, то, звісно, перейменую. Але все-таки чому треба його перейменовувати? Щоб було зрозуміліше?
created_at: 2025-04-30T08:55:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: Це нест згенерував?
Ну у нас рест апі, а там за домовленістю назви є
created_at: 2025-04-30T09:27:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: Таке, це згенерував нест і я вибирав рест апі
created_at: 2025-04-30T09:29:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: ну я не знаю, чого він таке зробив
created_at: 2025-04-30T10:16:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: тут і так захищено, бо робляться перевірки, чи належить введений параметр стовпцям. І якщо ні, то дефолтний ставиться
created_at: 2025-04-30T11:42:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppiersService: SuppliersService) {}
path: packages/backend/src/repositories/suppliers/suppliers.controller.ts
user: login: MaksymSemk
body: Перейменував
created_at: 2025-04-30T12:31:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Param,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+import { SuppliersService } from './suppliers.repository';
+
+@Controller('suppliers')
+export class SuppliersController {
+  constructor(private readonly suppiersService: SuppliersService) {}
+
+  @Post()
+  create(@Body() suppierDto: CreateSupplierDto) {
path: packages/backend/src/repositories/suppliers/suppliers.controller.ts
user: login: MaksymSemk
body: Перейменував
created_at: 2025-04-30T12:32:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: Переробив
created_at: 2025-04-30T12:44:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
+    const query = `
+      DELETE FROM supplier 
+      WHERE edrpou = $1
+      RETURNING *
+    `;
+    return this.db.oneOrNone(query, [edrpou]);
+  }
+
+  getProductsBySupplier(edrpou: number, sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['goods_id', 'goods_name']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT *
+      FROM products
+      WHERE goods_id IN (
+        SELECT goods_id
+        FROM supplier_products
+        WHERE edrpou = $1
+      )
+      ORDER BY ${safeSortBy} ${safeOrder}
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: Переробив
created_at: 2025-04-30T12:45:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,11 @@
+import { IsEmail, IsOptional, IsPhoneNumber } from 'class-validator';
+
+export class UpdateSupplierDto {
+  @IsOptional()
+  @IsEmail()
+  email: string;
+
+  @IsOptional()
+  @IsPhoneNumber()
+  phone_num: string;
+}
path: packages/backend/src/repositories/suppliers/dto/update-supplier.dto.ts
user: login: MaksymSemk
body: Зробив
created_at: 2025-04-30T12:47:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: myroslavve
body: > тут і так захищено, бо робляться перевірки, чи належить введений параметр стовпцям. І якщо ні, то дефолтний ставиться

ок але хай уже буде повсюду однаково
created_at: 2025-04-30T13:20:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: > Переробив

Короче не переробив. Виходить, що не можна в orderby передати параметри, бо вийде синтаксична помилка в sql запиті. 
Вийде таке: ORDER BY 'edrpou' 'asc'
А не таке, як ми очікуємо: ORDER BY edrpou asc
Тому бд кидає помилку
created_at: 2025-04-30T14:07:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: Я спробую по максимуму де можна зробити через звичані дужки

> > тут і так захищено, бо робляться перевірки, чи належить введений параметр стовпцям. І якщо ні, то дефолтний ставиться
> 
> ок але хай уже буде повсюду однаково


created_at: 2025-04-30T14:08:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: як замерджимо з твоїй пул реквестом то, можливо, додам, як не забуду)
created_at: 2025-04-30T14:32:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
+    return this.db.any(query);
+  }
+
+  findOne(edrpou: number) {
+    const query = `
+        SELECT * FROM supplier WHERE edrpou = $1
+      `;
+    const res = this.db.oneOrNone(query, [edrpou]);
+    return res;
+  }
+
+  async update(edrpou: number, updateSupplierDto: UpdateSupplierDto) {
+    const fields = [];
+    const values: any = { edrpou };
+
+    if (updateSupplierDto.email !== undefined) {
+      fields.push('email = ${email}');
+      values.email = updateSupplierDto.email;
+    }
+
+    if (updateSupplierDto.phone_num !== undefined) {
+      fields.push('phone_num = ${phone_num}');
+      values.phone_num = updateSupplierDto.phone_num;
+    }
+
+    if (fields.length === 0) {
+      return this.findOne(edrpou); // No fields to update, return the existing supplier
+    }
+
+    const query = `
+      UPDATE supplier
+      SET ${fields.join(', ')}
+      WHERE edrpou = $(edrpou)
+      RETURNING *
+    `;
+
+    return await this.db.oneOrNone(query, values);
+  }
+
+  remove(edrpou: number) {
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: MaksymSemk
body: Перейменував

created_at: 2025-04-30T14:32:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,108 @@
+import { Injectable } from '@nestjs/common';
+import pgPromise from 'pg-promise';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateSupplierDto } from './dto/create-supplier.dto';
+import { UpdateSupplierDto } from './dto/update-supplier.dto';
+
+@Injectable()
+export class SuppliersService {
+  private db: pgPromise.IDatabase<any>;
+
+  constructor(private readonly databaseService: DatabaseService) {
+    this.db = databaseService.getDb();
+  }
+
+  async create(suppierDto: CreateSupplierDto) {
+    try {
+      const query = `
+      INSERT INTO supplier (edrpou, email, phone_num)
+      VALUES ($(edrpou), $(email), $(phone_num)) 
+      RETURNING *
+    `;
+      const res = await this.db.oneOrNone(query, suppierDto);
+      return res;
+    } catch (e) {
+      return 'Supplier with this EDRPOU already exists';
+    }
+  }
+
+  findAll(sortBy: string, order: 'asc' | 'desc') {
+    const allowedSortFields = ['edrpou', 'email', 'phone_num']; // whitelist fields
+    const allowedOrders = ['asc', 'desc'];
+
+    const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'goods_id';
+    const safeOrder = allowedOrders.includes(order) ? order : 'asc';
+
+    const query = `
+      SELECT * FROM supplier
+      ORDER BY ${safeSortBy} ${safeOrder}
+    `;
path: packages/backend/src/repositories/suppliers/suppliers.repository.ts
user: login: EWOKWICKET
body: > > тут і так захищено, бо робляться перевірки, чи належить введений параметр стовпцям. І якщо ні, то дефолтний ставиться
> 
> ок але хай уже буде повсюду однаково

я новий коміт викотив, там додав всякі штуки
created_at: 2025-04-30T14:37:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,11 @@
+import { IsEmail, IsOptional, IsPhoneNumber } from 'class-validator';
+
+export class UpdateSupplierDto {
+  @IsOptional()
+  @IsEmail()
+  email: string | null;
+
+  @IsOptional()
+  @IsPhoneNumber()
+  phone_num: string | null;
+}
path: packages/backend/src/repositories/suppliers/dto/update-supplier.dto.ts
user: login: EWOKWICKET
body: в принципі без різниці, але краще просто email? і phone_num? зробити
created_at: 2025-04-30T14:37:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: EWOKWICKET
created_at: 2025-04-30T08:15:26Z
body: > Взагалі варто це ще обговорити але по-моєму коли в базі даних не знаходите дані на які прийшов реквест краще помилку кидати а не null просто повертати, бо потім на фронтенді це не дуже зручно буде (не критично але краще коли буде якась 404 чи 500 помилка прилітати і можна зразу там юзеру сказати що створити не вдалось чи щось таке). @EWOKWICKET @MaksymSemk подумайте

Взагалі да, але я це поки скіпаю. Покращення пізніше зробимо. Поки що базу напишемо
author_association: CONTRIBUTOR
user: login: myroslavve
created_at: 2025-04-30T08:20:41Z
body: > Взагалі да, але я це поки скіпаю. Покращення пізніше зробимо. Поки що базу напишемо

Ок тоді ок
author_association: CONTRIBUTOR