update: add company services and products
This commit is contained in:
@@ -10,8 +10,8 @@ jobs:
|
||||
|
||||
env:
|
||||
DANAK_SERVER: "https://captain.dev.danakcorp.com"
|
||||
APP_TOKEN: 1b5da4107e67bb72c17bcecdaea51e6ccd0c545a4e7ffbcf4fd7e2c83bfe24b0
|
||||
APP_NAME: danak-api
|
||||
APP_TOKEN: d33544dfb7c716effb5cadb16906aaab130f2573e2c431a5b194bef770297255
|
||||
APP_NAME: danak-zone-api
|
||||
GITHUB_TOKEN: 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
|
||||
|
||||
steps:
|
||||
|
||||
@@ -122,6 +122,7 @@ export const enum CommonMessage {
|
||||
SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد",
|
||||
UPDATED = "با موفقیت آپدیت شد",
|
||||
IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت باید یکی از مقادیر ۰ و ۱ باشد",
|
||||
DATE_MUST_BE_DATE = "تاریخ باید یک تاریخ معتبر به صورت رشته باشد",
|
||||
}
|
||||
|
||||
export const enum CategoryMessage {
|
||||
@@ -476,7 +477,7 @@ export const enum InvoiceMessage {
|
||||
INVALID_DISCOUNT_CODE = "کد تخفیف نامعتبر است",
|
||||
ORIGINAL_PRICE_NOT_FOUND = "قیمت اصلی صورت حساب یافت نشد",
|
||||
NOT_FOUND_BY_ID_OR_NOT_BELONG_TO_USER = "صورت حساب یافت نشد یا متعلق به کاربر نمی باشد",
|
||||
DISCOUNT_ALREADY_APPLIED = "ت`خفیف قبلا برای این صورت حساب اعمال شده است",
|
||||
DISCOUNT_ALREADY_APPLIED = "تخفیف قبلا برای این صورت حساب اعمال شده است",
|
||||
DISCOUNT_EXPIRED = "تخفیف منقضی شده است",
|
||||
MAX_RECURRING_CYCLES_MUST_BE_POSITIVE = "تعداد تکرار باید بزرگتر از ۰ باشد",
|
||||
ITEMS_REQUIRED = "آیتمها مورد نیاز است",
|
||||
@@ -538,3 +539,59 @@ export const enum IndustryMessage {
|
||||
UPDATED_SUCCESSFULLY = "صنعت با موفقیت به روز رسانی شد",
|
||||
INDUSTRY_DELETED_SUCCESSFULLY = "صنعت با موفقیت حذف شد",
|
||||
}
|
||||
|
||||
export const enum CompanyMessage {
|
||||
ADDRESS_MUST_BE_STRING = "آدرس شرکت باید یک رشته باشد",
|
||||
ADDRESS_REQUIRED = "آدرس شرکت مورد نیاز است",
|
||||
CHIEF_EXECUTIVE_MUST_BE_STRING = "نام مدیرعامل باید یک رشته باشد",
|
||||
CHIEF_EXECUTIVE_REQUIRED = "نام مدیرعامل مورد نیاز است",
|
||||
COVER_IMAGE_URL_MUST_BE_URL = "آدرس تصویر کاور شرکت باید یک URL معتبر باشد",
|
||||
COVER_IMAGE_URL_REQUIRED = "آدرس تصویر کاور شرکت مورد نیاز است",
|
||||
DATE_OF_ESTABLISHMENT_MUST_BE_DATE = "تاریخ تاسیس شرکت باید یک تاریخ معتبر باشد",
|
||||
DATE_OF_ESTABLISHMENT_REQUIRED = "تاریخ تاسیس شرکت مورد نیاز است",
|
||||
DESCRIPTION_MUST_BE_STRING = "توضیحات شرکت باید یک رشته باشد",
|
||||
DESCRIPTION_REQUIRED = "توضیحات شرکت مورد نیاز است",
|
||||
EMAIL_MUST_BE_VALID = "ایمیل شرکت باید معتبر باشد",
|
||||
EMAIL_REQUIRED = "ایمیل شرکت مورد نیاز است",
|
||||
IDENTIFICATION_NUMBER_MUST_BE_STRING = "شماره ثبت شرکت باید یک رشته باشد",
|
||||
IDENTIFICATION_NUMBER_REQUIRED = "شماره ثبت شرکت مورد نیاز است",
|
||||
INDUSTRY_ID_REQUIRED = "صنعت مورد نیاز است",
|
||||
INDUSTRY_ID_SHOULD_BE_UUID = "شناسه صنعت باید یک UUID معتبر باشد",
|
||||
IS_ACTIVE_MUST_BE_BOOLEAN = "وضعیت فعالیت شرکت باید یک مقدار بولین باشد",
|
||||
IS_ACTIVE_REQUIRED = "وضعیت فعالیت شرکت مورد نیاز است",
|
||||
MAP_ADDRESS_LINK_MUST_BE_URL = "لینک نقشه شرکت باید یک URL معتبر باشد",
|
||||
MAP_ADDRESS_LINK_REQUIRED = "لینک نقشه شرکت مورد نیاز است",
|
||||
NAME_MUST_BE_STRING = "نام شرکت باید یک رشته باشد",
|
||||
NAME_REQUIRED = "نام شرکت مورد نیاز است",
|
||||
PHONE_MUST_BE_STRING = "شماره تلفن شرکت باید یک رشته باشد",
|
||||
PHONE_REQUIRED = "شماره تلفن شرکت مورد نیاز است",
|
||||
PROFILE_IMAGE_URL_MUST_BE_URL = "آدرس تصویر پروفایل شرکت باید یک URL معتبر باشد",
|
||||
PROFILE_IMAGE_URL_REQUIRED = "آدرس تصویر پروفایل شرکت مورد نیاز است",
|
||||
WEBSITE_URL_MUST_BE_URL = "آدرس وبسایت شرکت باید یک URL معتبر باشد",
|
||||
WEBSITE_URL_REQUIRED = "آدرس وبسایت شرکت مورد نیاز است",
|
||||
NAME_LENGTH = "نام شرکت باید بین ۳ تا ۱۰۰ کاراکتر باشد",
|
||||
CHIEF_EXECUTIVE_LENGTH = "نام مدیرعامل باید بین ۳ تا ۱۰۰ کاراکتر باشد",
|
||||
COMPANY_CREATED_SUCCESSFULLY = "شرکت با موفقیت ایجاد شد",
|
||||
COMPANY_ALREADY_EXISTS = "این شرکت قبلا ثبت شده است",
|
||||
COMPANY_ALREADY_EXISTS_NAME = "این نام شرکت قبلا ثبت شده است",
|
||||
COMPANY_ALREADY_EXISTS_PHONE = "این شماره تلفن قبلا ثبت شده است",
|
||||
COMPANY_ALREADY_EXISTS_EMAIL = "این ایمیل قبلا ثبت شده است",
|
||||
COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER = "این شماره ثبت شرکت قبلا ثبت شده است",
|
||||
COMPANY_NOT_FOUND = "شرکت یافت نشد",
|
||||
UPDATED_SUCCESSFULLY = "با موفقیت به روز رسانی شد",
|
||||
DELETED_SUCCESSFULLY = "با موفقیت حذف شد",
|
||||
PRODUCT_TITLE_REQUIRED = "عنوان محصول الزامی است",
|
||||
PRODUCT_TITLE_MUST_BE_STRING = "عنوان محصول باید رشته باشد",
|
||||
PRODUCT_IMAGES_REQUIRED = "تصاویر محصول الزامی است",
|
||||
PRODUCT_IMAGES_MUST_BE_ARRAY = "تصاویر محصول باید آرایه باشد",
|
||||
PRODUCT_IMAGES_MUST_BE_URL = "تصاویر محصول باید آدرس معتبر باشد",
|
||||
SERVICE_TITLE_REQUIRED = "عنوان خدمت الزامی است",
|
||||
SERVICE_TITLE_MUST_BE_STRING = "عنوان خدمت باید رشته باشد",
|
||||
SERVICE_IMAGES_REQUIRED = "تصاویر خدمت الزامی است",
|
||||
SERVICE_IMAGES_MUST_BE_ARRAY = "تصاویر خدمت باید آرایه باشد",
|
||||
SERVICE_IMAGES_MUST_BE_URL = "تصاویر خدمت باید آدرس معتبر باشد",
|
||||
PRODUCTS_REQUIRED = "محصولات الزامی است",
|
||||
SERVICES_REQUIRED = "خدمات الزامی است",
|
||||
PRODUCTS_MUST_BE_ARRAY = "محصولات باید آرایه باشد",
|
||||
SERVICES_MUST_BE_ARRAY = "خدمات باید آرایه باشد",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsDateString, IsEnum, IsIn, IsOptional, IsString } from "class-validator";
|
||||
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { CommonMessage, CompanyMessage } from "../../../common/enums/message.enum";
|
||||
import { CompanyStatus } from "../enums/company-status.enum";
|
||||
|
||||
export class CompanyListQueryDto extends PaginationDto {
|
||||
@IsOptional()
|
||||
@IsString({ message: CompanyMessage.NAME_MUST_BE_STRING })
|
||||
@ApiPropertyOptional({ description: "نام شرکت" })
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: CompanyMessage.INDUSTRY_ID_SHOULD_BE_UUID })
|
||||
@ApiPropertyOptional({ description: "شناسه صنعت" })
|
||||
industryId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsIn([1, 0], { message: CommonMessage.IS_ACTIVE_SHOULD_BE_1_0 })
|
||||
@ApiPropertyOptional({ description: "ads status", example: 1 })
|
||||
isActive?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(CompanyStatus)
|
||||
@ApiPropertyOptional({ description: "status", example: CompanyStatus.APPROVED, enum: CompanyStatus })
|
||||
status?: CompanyStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: CommonMessage.SEARCH_QUERY_STRING })
|
||||
@ApiPropertyOptional({ description: "Search query", example: "search query" })
|
||||
q?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString(undefined, { message: CommonMessage.DATE_MUST_BE_DATE })
|
||||
@ApiPropertyOptional({ description: "تاریخ ثبت", example: "2021-01-01" })
|
||||
registrationDate?: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsArray, IsNotEmpty, IsString, IsUrl } from "class-validator";
|
||||
|
||||
import { CompanyMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateCompanyProductDto {
|
||||
@IsNotEmpty({ message: CompanyMessage.PRODUCT_TITLE_REQUIRED })
|
||||
@IsString({ message: CompanyMessage.PRODUCT_TITLE_MUST_BE_STRING })
|
||||
@ApiProperty({ description: "عنوان محصول", example: "محصول نمونه" })
|
||||
title: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.PRODUCT_IMAGES_REQUIRED })
|
||||
@IsArray({ message: CompanyMessage.PRODUCT_IMAGES_MUST_BE_ARRAY })
|
||||
@IsUrl({ protocols: ["http", "https"] }, { each: true, message: CompanyMessage.PRODUCT_IMAGES_MUST_BE_URL })
|
||||
@ApiProperty({ description: "تصاویر محصول", example: ["https://example.com/image1.jpg", "https://example.com/image2.jpg"] })
|
||||
imagesUrl: string[];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsArray, IsNotEmpty, IsString, IsUrl } from "class-validator";
|
||||
|
||||
import { CompanyMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateCompanyServiceDto {
|
||||
@IsNotEmpty({ message: CompanyMessage.SERVICE_TITLE_REQUIRED })
|
||||
@IsString({ message: CompanyMessage.SERVICE_TITLE_MUST_BE_STRING })
|
||||
@ApiProperty({ description: "عنوان خدمت", example: "خدمت نمونه" })
|
||||
title: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.SERVICE_IMAGES_REQUIRED })
|
||||
@IsArray({ message: CompanyMessage.SERVICE_IMAGES_MUST_BE_ARRAY })
|
||||
@IsUrl({ protocols: ["http", "https"] }, { each: true, message: CompanyMessage.SERVICE_IMAGES_MUST_BE_URL })
|
||||
@ApiProperty({ description: "تصاویر خدمت", example: ["https://example.com/image1.jpg", "https://example.com/image2.jpg"] })
|
||||
imagesUrl: string[];
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { ApiProperty, PickType } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsUrl,
|
||||
Length,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
|
||||
import { CreateCompanyProductDto } from "./create-company-product.dto";
|
||||
import { CreateCompanyServiceDto } from "./create-company-service.dto";
|
||||
import { CompanyMessage } from "../../../common/enums/message.enum";
|
||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
||||
|
||||
export class CreateCompanyDto extends PickType(CompleteRegistrationDto, ["phone", "firstName", "lastName", "password", "nationalCode"]) {
|
||||
@IsNotEmpty({ message: CompanyMessage.NAME_REQUIRED })
|
||||
@Length(3, 255, { message: CompanyMessage.NAME_LENGTH })
|
||||
@IsString({ message: CompanyMessage.NAME_MUST_BE_STRING })
|
||||
@ApiProperty({ description: "نام شرکت", example: "شرکت تولیدی و صنعتی کارگر" })
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.EMAIL_REQUIRED })
|
||||
@IsEmail(undefined, { message: CompanyMessage.EMAIL_MUST_BE_VALID })
|
||||
@ApiProperty({ description: "ایمیل", example: "info@example.com" })
|
||||
email: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.IDENTIFICATION_NUMBER_REQUIRED })
|
||||
@IsString({ message: CompanyMessage.IDENTIFICATION_NUMBER_MUST_BE_STRING })
|
||||
@ApiProperty({ description: "شماره ثبت", example: "1234567890" })
|
||||
identificationNumber: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.DATE_OF_ESTABLISHMENT_REQUIRED })
|
||||
@IsDateString(undefined, { message: CompanyMessage.DATE_OF_ESTABLISHMENT_MUST_BE_DATE })
|
||||
@ApiProperty({ description: "تاریخ تاسیس", example: "2021-01-01" })
|
||||
dateOfEstablishment: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.ADDRESS_REQUIRED })
|
||||
@IsString({ message: CompanyMessage.ADDRESS_MUST_BE_STRING })
|
||||
@ApiProperty({ description: "آدرس", example: "تهران، خیابان ولیعصر، پلاک 123" })
|
||||
address: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.MAP_ADDRESS_LINK_REQUIRED })
|
||||
@IsUrl({ protocols: ["http", "https"] }, { message: CompanyMessage.MAP_ADDRESS_LINK_MUST_BE_URL })
|
||||
@ApiProperty({ description: "لینک آدرس", example: "https://maps.app.goo.gl/1234567890" })
|
||||
mapAddressLink: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.DESCRIPTION_REQUIRED })
|
||||
@IsString({ message: CompanyMessage.DESCRIPTION_MUST_BE_STRING })
|
||||
@ApiProperty({ description: "توضیحات", example: "شرکت تولیدی و صنعتی کارگر" })
|
||||
description: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.WEBSITE_URL_REQUIRED })
|
||||
@IsUrl({ protocols: ["http", "https"] }, { message: CompanyMessage.WEBSITE_URL_MUST_BE_URL })
|
||||
@ApiProperty({ description: "آدرس وبسایت", example: "https://www.example.com" })
|
||||
websiteUrl: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.PROFILE_IMAGE_URL_REQUIRED })
|
||||
@IsUrl({ protocols: ["http", "https"] }, { message: CompanyMessage.PROFILE_IMAGE_URL_MUST_BE_URL })
|
||||
@ApiProperty({ description: "آدرس تصویر پروفایل", example: "https://www.example.com/profile.jpg" })
|
||||
profileImageUrl: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.COVER_IMAGE_URL_REQUIRED })
|
||||
@IsUrl({ protocols: ["http", "https"] }, { message: CompanyMessage.COVER_IMAGE_URL_MUST_BE_URL })
|
||||
@ApiProperty({ description: "آدرس تصویر کاور", example: "https://www.example.com/cover.jpg" })
|
||||
coverImageUrl: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNotEmpty({ message: CompanyMessage.IS_ACTIVE_REQUIRED })
|
||||
@IsBoolean({ message: CompanyMessage.IS_ACTIVE_MUST_BE_BOOLEAN })
|
||||
@ApiProperty({ description: "وضعیت فعالیت", example: true })
|
||||
isActive?: boolean;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.INDUSTRY_ID_REQUIRED })
|
||||
@IsUUID("7", { message: CompanyMessage.INDUSTRY_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "شناسه صنعت", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||
industryId: string;
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.PRODUCTS_REQUIRED })
|
||||
@IsArray({ message: CompanyMessage.PRODUCTS_MUST_BE_ARRAY })
|
||||
@ArrayMinSize(1, { message: CompanyMessage.PRODUCTS_REQUIRED })
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateCompanyProductDto)
|
||||
@ApiProperty({ description: "محصولات شرکت", type: [CreateCompanyProductDto], required: false })
|
||||
products: CreateCompanyProductDto[];
|
||||
|
||||
@IsNotEmpty({ message: CompanyMessage.SERVICES_REQUIRED })
|
||||
@IsArray({ message: CompanyMessage.SERVICES_MUST_BE_ARRAY })
|
||||
@ArrayMinSize(1, { message: CompanyMessage.SERVICES_REQUIRED })
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateCompanyServiceDto)
|
||||
@ApiProperty({ description: "خدمات شرکت", type: [CreateCompanyServiceDto], required: false })
|
||||
services: CreateCompanyServiceDto[];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PartialType } from "@nestjs/swagger";
|
||||
|
||||
import { CreateCompanyDto } from "./create-company.dto";
|
||||
|
||||
export class UpdateCompanyDto extends PartialType(CreateCompanyDto) {}
|
||||
@@ -1,4 +1,48 @@
|
||||
import { Controller } from "@nestjs/common";
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { ApiOperation } from "@nestjs/swagger";
|
||||
|
||||
import { CompanyListQueryDto } from "./DTO/company-list-query.dto";
|
||||
import { CreateCompanyDto } from "./DTO/create-company.dto";
|
||||
import { UpdateCompanyDto } from "./DTO/update-company.dto";
|
||||
import { CompaniesService } from "./services/companies.service";
|
||||
import { ParamDto } from "../../common/DTO/param.dto";
|
||||
@Controller("companies")
|
||||
export class CompaniesController {}
|
||||
export class CompaniesController {
|
||||
constructor(private readonly companiesService: CompaniesService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "create company (admin)" })
|
||||
create(@Body() createCompanyDto: CreateCompanyDto) {
|
||||
return this.companiesService.create(createCompanyDto);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@ApiOperation({ summary: "update company (admin)" })
|
||||
update(@Param() paramDto: ParamDto, @Body() updateCompanyDto: UpdateCompanyDto) {
|
||||
return this.companiesService.updateCompanyById(paramDto.id, updateCompanyDto);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "delete company (admin)" })
|
||||
delete(@Param() paramDto: ParamDto) {
|
||||
return this.companiesService.deleteCompanyById(paramDto.id);
|
||||
}
|
||||
|
||||
@Get("list")
|
||||
@ApiOperation({ summary: "get companies list (admin)" })
|
||||
getCompaniesList(@Query() query: CompanyListQueryDto) {
|
||||
return this.companiesService.getCompaniesList(query);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "get company by id" })
|
||||
get(@Param() paramDto: ParamDto) {
|
||||
return this.companiesService.getCompanyById(paramDto.id);
|
||||
}
|
||||
|
||||
@Patch(":id/toggle-status")
|
||||
@ApiOperation({ summary: "toggle company status (admin)" })
|
||||
toggleStatus(@Param() paramDto: ParamDto) {
|
||||
return this.companiesService.toggleStatus(paramDto.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ import { MikroOrmModule } from "@mikro-orm/nestjs";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { CompaniesController } from "./companies.controller";
|
||||
import { CompanyProduct } from "./entities/company-product.entity";
|
||||
import { CompanyService } from "./entities/company-service.entity";
|
||||
import { Company } from "./entities/company.entity";
|
||||
import { CompaniesService } from "./services/companies.service";
|
||||
|
||||
import { Industry } from "../industries/entities/industry.entity";
|
||||
import { UsersModule } from "../users/users.module";
|
||||
import { UtilsModule } from "../utils/utils.module";
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Company])],
|
||||
imports: [MikroOrmModule.forFeature([Company, Industry, CompanyProduct, CompanyService]), UsersModule, UtilsModule],
|
||||
providers: [CompaniesService],
|
||||
controllers: [CompaniesController],
|
||||
exports: [CompaniesService],
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Entity, ManyToOne, Property } from "@mikro-orm/core";
|
||||
|
||||
import { Company } from "./company.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
|
||||
@Entity()
|
||||
export class CompanyProduct extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 250, nullable: false })
|
||||
title!: string;
|
||||
|
||||
@Property({ type: "array", nullable: false })
|
||||
imagesUrl!: string[];
|
||||
|
||||
@ManyToOne(() => Company, { deleteRule: "cascade" })
|
||||
company!: Company;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Entity, ManyToOne, Property } from "@mikro-orm/core";
|
||||
|
||||
import { Company } from "./company.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
|
||||
@Entity()
|
||||
export class CompanyService extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 250, nullable: false })
|
||||
title!: string;
|
||||
|
||||
@Property({ type: "array", nullable: false })
|
||||
imagesUrl!: string[];
|
||||
|
||||
@ManyToOne(() => Company, { deleteRule: "cascade" })
|
||||
company!: Company;
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Entity, EntityRepositoryType, ManyToOne, Opt, Property } from "@mikro-orm/core";
|
||||
import { Cascade, Collection, Entity, EntityRepositoryType, Enum, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
|
||||
|
||||
import { CompanyProduct } from "./company-product.entity";
|
||||
import { CompanyService } from "./company-service.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { Industry } from "../../industries/entities/industry.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { CompanyStatus } from "../enums/company-status.enum";
|
||||
import { CompanyRepository } from "../repositories/company.repository";
|
||||
|
||||
@Entity({ repository: () => CompanyRepository })
|
||||
@@ -9,15 +13,6 @@ export class Company extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 255, unique: true })
|
||||
name!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 255 })
|
||||
chiefExecutive!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 255, unique: true })
|
||||
phone!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 255, unique: true })
|
||||
email!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 255, unique: true })
|
||||
identificationNumber!: string;
|
||||
|
||||
@@ -45,8 +40,24 @@ export class Company extends BaseEntity {
|
||||
@Property({ type: "boolean", default: true })
|
||||
isActive!: boolean & Opt;
|
||||
|
||||
@Enum({ items: () => CompanyStatus, nativeEnumName: "company_status", nullable: false })
|
||||
status!: CompanyStatus;
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
@OneToMany(() => CompanyProduct, (product) => product.company, { cascade: [Cascade.ALL] })
|
||||
products = new Collection<CompanyProduct>(this);
|
||||
|
||||
@OneToMany(() => CompanyService, (service) => service.company, { cascade: [Cascade.ALL] })
|
||||
services = new Collection<CompanyService>(this);
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
@ManyToOne(() => Industry, { deleteRule: "restrict" })
|
||||
industry!: Industry;
|
||||
|
||||
@ManyToOne(() => User, { deleteRule: "restrict" })
|
||||
user!: User;
|
||||
|
||||
[EntityRepositoryType]?: CompanyRepository;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum CompanyStatus {
|
||||
APPROVED = "APPROVED",
|
||||
PENDING = "PENDING",
|
||||
REJECTED = "REJECTED",
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
import { EntityRepository } from "@mikro-orm/core";
|
||||
|
||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||
import { CompanyListQueryDto } from "../DTO/company-list-query.dto";
|
||||
import { Company } from "../entities/company.entity";
|
||||
|
||||
export class CompanyRepository extends EntityRepository<Company> {}
|
||||
export class CompanyRepository extends EntityRepository<Company> {
|
||||
//
|
||||
async getCompaniesListForAdmin(queryDto: CompanyListQueryDto) {
|
||||
const { limit, skip } = PaginationUtils(queryDto);
|
||||
|
||||
return await this.findAndCount(
|
||||
{
|
||||
deletedAt: null,
|
||||
...(queryDto.status && { status: queryDto.status }),
|
||||
...(queryDto.q && { name: { $ilike: `%${queryDto.q}%` } }),
|
||||
...(queryDto.industryId && { industry: { id: queryDto.industryId } }),
|
||||
...(queryDto.isActive !== undefined && { isActive: queryDto.isActive === 1 }),
|
||||
...(queryDto.registrationDate && { createdAt: { $gte: queryDto.registrationDate, $lte: queryDto.registrationDate } }),
|
||||
...(queryDto.name && { name: { $ilike: `%${queryDto.name}%` } }),
|
||||
},
|
||||
{ offset: skip, limit, orderBy: { createdAt: "DESC" } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,172 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Collection, EntityManager } from "@mikro-orm/postgresql";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { CompanyMessage, IndustryMessage } from "../../../common/enums/message.enum";
|
||||
import { Industry } from "../../industries/entities/industry.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { UsersService } from "../../users/services/users.service";
|
||||
import { PasswordService } from "../../utils/providers/password.service";
|
||||
import { CompanyListQueryDto } from "../DTO/company-list-query.dto";
|
||||
import { CreateCompanyDto } from "../DTO/create-company.dto";
|
||||
import { UpdateCompanyDto } from "../DTO/update-company.dto";
|
||||
import { CompanyProduct } from "../entities/company-product.entity";
|
||||
import { CompanyService } from "../entities/company-service.entity";
|
||||
import { Company } from "../entities/company.entity";
|
||||
import { CompanyStatus } from "../enums/company-status.enum";
|
||||
import { CompanyRepository } from "../repositories/company.repository";
|
||||
|
||||
@Injectable()
|
||||
export class CompaniesService {
|
||||
constructor(private readonly companyRepository: CompanyRepository) {}
|
||||
constructor(
|
||||
private readonly companyRepository: CompanyRepository,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly passwordService: PasswordService,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async create(createDto: CreateCompanyDto) {
|
||||
const em = this.em.fork();
|
||||
try {
|
||||
em.begin();
|
||||
|
||||
const industry = await em.findOne(Industry, { id: createDto.industryId, deletedAt: null });
|
||||
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
|
||||
|
||||
const [existingCompany, existIdentityNumber] = await Promise.all([
|
||||
em.findOne(Company, { name: createDto.name }),
|
||||
em.findOne(Company, { identificationNumber: createDto.identificationNumber }),
|
||||
]);
|
||||
|
||||
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
|
||||
if (existIdentityNumber) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
|
||||
|
||||
const hashedPassword = await this.passwordService.hashPassword(createDto.password);
|
||||
const user = await this.usersService.createUser(createDto, hashedPassword, em);
|
||||
|
||||
const company = em.create(Company, { ...createDto, industry, status: CompanyStatus.APPROVED, user });
|
||||
|
||||
const products = createDto.products.map((productData) => em.create(CompanyProduct, { ...productData, company }));
|
||||
company.products = new Collection(products);
|
||||
|
||||
const services = createDto.services.map((serviceData) => em.create(CompanyService, { ...serviceData, company }));
|
||||
company.services = new Collection(services);
|
||||
|
||||
await em.persistAndFlush(company);
|
||||
await em.commit();
|
||||
|
||||
return {
|
||||
message: CompanyMessage.COMPANY_CREATED_SUCCESSFULLY,
|
||||
company,
|
||||
user,
|
||||
};
|
||||
} catch (error) {
|
||||
await em.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
//-----------------------------------
|
||||
|
||||
async updateCompanyById(id: string, updateDto: UpdateCompanyDto) {
|
||||
const em = this.em.fork();
|
||||
try {
|
||||
em.begin();
|
||||
|
||||
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user"] });
|
||||
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
||||
|
||||
const updatedCompany = em.assign(company, updateDto);
|
||||
|
||||
if (updateDto.industryId) {
|
||||
const industry = await em.findOne(Industry, { id: updateDto.industryId, deletedAt: null });
|
||||
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
|
||||
updatedCompany.industry = industry;
|
||||
}
|
||||
|
||||
if (updateDto.name) {
|
||||
const existingCompany = await em.findOne(Company, { name: updateDto.name, deletedAt: null, id: { $ne: company.id } });
|
||||
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
|
||||
}
|
||||
|
||||
if (updateDto.identificationNumber) {
|
||||
const existingCompany = await em.findOne(Company, {
|
||||
identificationNumber: updateDto.identificationNumber,
|
||||
deletedAt: null,
|
||||
id: { $ne: company.id },
|
||||
});
|
||||
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
|
||||
}
|
||||
|
||||
const userUpdateData: Partial<User> = {};
|
||||
if (updateDto.phone) userUpdateData.phone = updateDto.phone;
|
||||
if (updateDto.email) userUpdateData.email = updateDto.email;
|
||||
if (updateDto.firstName) userUpdateData.firstName = updateDto.firstName;
|
||||
if (updateDto.lastName) userUpdateData.lastName = updateDto.lastName;
|
||||
if (updateDto.nationalCode) userUpdateData.nationalCode = updateDto.nationalCode;
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) await this.usersService.updateUser(company.user.id, userUpdateData, em);
|
||||
|
||||
await em.flush();
|
||||
await em.commit();
|
||||
|
||||
return {
|
||||
message: CompanyMessage.UPDATED_SUCCESSFULLY,
|
||||
updatedCompany,
|
||||
};
|
||||
} catch (error) {
|
||||
await em.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
async getCompanyById(id: string) {
|
||||
const company = await this.findCompanyById(id);
|
||||
return { company };
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
async getCompaniesList(queryDto: CompanyListQueryDto) {
|
||||
const [companies, count] = await this.companyRepository.getCompaniesListForAdmin(queryDto);
|
||||
return {
|
||||
companies,
|
||||
count,
|
||||
paginate: true,
|
||||
};
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
async deleteCompanyById(id: string) {
|
||||
const company = await this.findCompanyById(id);
|
||||
|
||||
company.deletedAt = dayjs().toDate();
|
||||
|
||||
await this.em.flush();
|
||||
return {
|
||||
message: CompanyMessage.DELETED_SUCCESSFULLY,
|
||||
};
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
async toggleStatus(id: string) {
|
||||
const company = await this.findCompanyById(id);
|
||||
|
||||
company.isActive = !company.isActive;
|
||||
|
||||
await this.em.flush();
|
||||
|
||||
return {
|
||||
message: CompanyMessage.UPDATED_SUCCESSFULLY,
|
||||
};
|
||||
}
|
||||
//-----------------------------------
|
||||
private async findCompanyById(id: string) {
|
||||
const company = await this.companyRepository.findOne({ id, deletedAt: null });
|
||||
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
|
||||
return company;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class IndustryRepository extends EntityRepository<Industry> {
|
||||
.offset(skip);
|
||||
|
||||
if (queryDto.q) {
|
||||
queryBuilder.andWhere({ name: { $ilike: `%${queryDto.q}%` } });
|
||||
queryBuilder.andWhere({ title: { $ilike: `%${queryDto.q}%` } });
|
||||
}
|
||||
|
||||
if (queryDto.isActive !== undefined) {
|
||||
|
||||
@@ -3,10 +3,17 @@ import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, Opt, Pr
|
||||
import { RefreshToken } from "./refresh-token.entity";
|
||||
import { Role } from "./role.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { Company } from "../../companies/entities/company.entity";
|
||||
import { UserRepository } from "../repositories/user.repository";
|
||||
|
||||
@Entity({ repository: () => UserRepository })
|
||||
export class User extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 150 })
|
||||
firstName!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 200 })
|
||||
lastName!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 150, nullable: true })
|
||||
email?: string;
|
||||
|
||||
@@ -19,12 +26,6 @@ export class User extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 150 })
|
||||
password!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 150 })
|
||||
firstName!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 200 })
|
||||
lastName!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 12, nullable: true })
|
||||
birthDate?: string;
|
||||
|
||||
@@ -47,5 +48,8 @@ export class User extends BaseEntity {
|
||||
@OneToMany(() => RefreshToken, (token) => token.user)
|
||||
refreshTokens = new Collection<RefreshToken>(this);
|
||||
|
||||
@OneToMany(() => Company, (company) => company.user)
|
||||
companies = new Collection<Company>(this);
|
||||
|
||||
[EntityRepositoryType]?: UserRepository;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import slugify from "slugify";
|
||||
|
||||
import { UserMessage } from "../../../common/enums/message.enum";
|
||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
||||
import { CreateCompanyDto } from "../../companies/DTO/create-company.dto";
|
||||
import { Role } from "../entities/role.entity";
|
||||
import { User } from "../entities/user.entity";
|
||||
import { RoleEnum } from "../enums/role.enum";
|
||||
@@ -45,16 +46,22 @@ export class UsersService {
|
||||
|
||||
/*******************************/
|
||||
|
||||
async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string, em: EntityManager) {
|
||||
const role = await em.findOne(Role, { name: RoleEnum.USER });
|
||||
async createUser(registerDto: CompleteRegistrationDto | CreateCompanyDto, hashedPassword: string, em: EntityManager) {
|
||||
const [role, existPhone, existUser] = await Promise.all([
|
||||
em.findOne(Role, { name: RoleEnum.USER }),
|
||||
em.findOne(User, { phone: registerDto.phone }),
|
||||
em.findOne(User, { nationalCode: registerDto.nationalCode }),
|
||||
]);
|
||||
|
||||
if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND);
|
||||
|
||||
const existPhone = await em.findOne(User, { phone: registerDto.phone });
|
||||
if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST);
|
||||
|
||||
const existUser = await em.findOne(User, { nationalCode: registerDto.nationalCode });
|
||||
if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
|
||||
|
||||
if ("email" in registerDto) {
|
||||
const existEmail = await em.findOne(User, { email: registerDto.email });
|
||||
if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST);
|
||||
}
|
||||
|
||||
const userName = slugify(`${registerDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true });
|
||||
|
||||
const user = em.create(User, {
|
||||
@@ -76,4 +83,31 @@ export class UsersService {
|
||||
await this.em.flush();
|
||||
return user;
|
||||
}
|
||||
|
||||
/*******************************/
|
||||
|
||||
async updateUser(id: string, updateData: Partial<User>, em: EntityManager) {
|
||||
const user = await em.findOne(User, { id, deletedAt: null });
|
||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
|
||||
if (updateData.phone) {
|
||||
const existingUser = await em.findOne(User, { phone: updateData.phone, deletedAt: null, id: { $ne: id } });
|
||||
if (existingUser) throw new BadRequestException(UserMessage.PHONE_EXIST);
|
||||
}
|
||||
|
||||
if (updateData.email) {
|
||||
const existingUser = await em.findOne(User, { email: updateData.email, deletedAt: null, id: { $ne: id } });
|
||||
if (existingUser) throw new BadRequestException(UserMessage.EMAIL_EXIST);
|
||||
}
|
||||
|
||||
if (updateData.nationalCode) {
|
||||
const existingUser = await em.findOne(User, { nationalCode: updateData.nationalCode, deletedAt: null, id: { $ne: id } });
|
||||
if (existingUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
|
||||
}
|
||||
|
||||
em.assign(user, updateData);
|
||||
await em.flush();
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user