parts and areas tested
This commit is contained in:
@@ -14,6 +14,7 @@ import { BuildTypesModule } from './build-types/build-types.module';
|
||||
import { BuildAddressModule } from './build-address/build-address.module';
|
||||
import { BuildIbanModule } from './build-iban/build-iban.module';
|
||||
import { BuildSitesModule } from './build-sites/build-sites.module';
|
||||
import { CompanyModule } from './company/company.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -33,6 +34,7 @@ import { BuildSitesModule } from './build-sites/build-sites.module';
|
||||
BuildAddressModule,
|
||||
BuildIbanModule,
|
||||
BuildSitesModule,
|
||||
CompanyModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
@@ -3,9 +3,13 @@ import { BuildAreaResolver } from './build-area.resolver';
|
||||
import { BuildAreaService } from './build-area.service';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { BuildArea, BuildAreaSchema } from '@/models/build-area.model';
|
||||
import { Build, BuildSchema } from '@/models/build.model';
|
||||
|
||||
@Module({
|
||||
imports: [MongooseModule.forFeature([{ name: BuildArea.name, schema: BuildAreaSchema }])],
|
||||
imports: [MongooseModule.forFeature([
|
||||
{ name: BuildArea.name, schema: BuildAreaSchema },
|
||||
{ name: Build.name, schema: BuildSchema }
|
||||
])],
|
||||
providers: [BuildAreaResolver, BuildAreaService]
|
||||
})
|
||||
export class BuildAreaModule { }
|
||||
|
||||
@@ -6,25 +6,32 @@ import { CreateBuildAreaInput } from './dto/create-build-area.input';
|
||||
import { ListBuildAreaResponse } from './dto/list-build-area.response';
|
||||
import { UpdateBuildAreaInput } from './dto/update-build-area.input';
|
||||
import { ListArguments } from '@/dto/list.input';
|
||||
import { Build, BuildDocument } from '@/models/build.model';
|
||||
|
||||
@Injectable()
|
||||
export class BuildAreaService {
|
||||
|
||||
constructor(@InjectModel(BuildArea.name) private readonly buildAreaModel: Model<BuildAreaDocument>) { }
|
||||
constructor(
|
||||
@InjectModel(BuildArea.name) private readonly buildAreaModel: Model<BuildAreaDocument>,
|
||||
@InjectModel(Build.name) private readonly buildModel: Model<BuildDocument>
|
||||
) { }
|
||||
|
||||
async findAll(projection: any, listArguments: ListArguments): Promise<ListBuildAreaResponse> {
|
||||
const { skip, limit, sort, filters } = listArguments;
|
||||
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
|
||||
if (filters?.buildId) { query.buildId = new Types.ObjectId(filters?.buildId) };
|
||||
const totalCount = await this.buildAreaModel.countDocuments(query).exec();
|
||||
const data = await this.buildAreaModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec();
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
async findById(id: Types.ObjectId, projection?: any): Promise<BuildAreaDocument | null> {
|
||||
return this.buildAreaModel.findById(id, projection, { lean: false }).populate({ path: 'buildArea', select: projection?.buildArea }).exec();
|
||||
}
|
||||
async findById(id: Types.ObjectId, projection?: any): Promise<BuildAreaDocument | null> { return this.buildAreaModel.findById(id, projection, { lean: false }).populate({ path: 'buildArea', select: projection?.buildArea }).exec() }
|
||||
|
||||
async create(input: CreateBuildAreaInput): Promise<BuildAreaDocument> { const buildArea = new this.buildAreaModel(input); return buildArea.save() }
|
||||
async create(input: CreateBuildAreaInput): Promise<BuildAreaDocument> {
|
||||
const getBuildID = await this.buildModel.findById(input?.buildId, { _id: 1 }, { lean: true }).exec();
|
||||
if (getBuildID) { input.buildId = new Types.ObjectId(getBuildID._id) };
|
||||
const buildArea = new this.buildAreaModel(input); return buildArea.save()
|
||||
}
|
||||
|
||||
async update(uuid: string, input: UpdateBuildAreaInput): Promise<BuildAreaDocument> { const buildArea = await this.buildAreaModel.findOne({ uuid }); if (!buildArea) { throw new Error('BuildArea not found') }; buildArea.set(input); return buildArea.save() }
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { ExpiryBaseInput } from "@/models/base.model";
|
||||
import { InputType, Field, ID, Float } from "@nestjs/graphql";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
@InputType()
|
||||
export class CreateBuildAreaInput {
|
||||
export class CreateBuildAreaInput extends ExpiryBaseInput {
|
||||
|
||||
@Field(() => ID, { nullable: false })
|
||||
buildId: Types.ObjectId;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
build?: Types.ObjectId;
|
||||
partTypeId?: Types.ObjectId;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
partType?: Types.ObjectId;
|
||||
|
||||
@Field(() => Float)
|
||||
@Field()
|
||||
areaName: string;
|
||||
|
||||
@Field(() => Float)
|
||||
@Field()
|
||||
areaCode: string;
|
||||
|
||||
@Field()
|
||||
@@ -22,16 +23,16 @@ export class CreateBuildAreaInput {
|
||||
@Field()
|
||||
areaDirection: string;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
areaGrossSize: number;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
areaNetSize: number;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
width: number;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
size: number;
|
||||
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ export class UpdateBuildAreaInput {
|
||||
@Field(() => ID, { nullable: true })
|
||||
partType?: Types.ObjectId;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
@Field({ nullable: true })
|
||||
areaName?: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
@Field({ nullable: true })
|
||||
areaCode?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@@ -22,16 +22,16 @@ export class UpdateBuildAreaInput {
|
||||
@Field({ nullable: true })
|
||||
areaDirection?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Field(() => Float, { nullable: true })
|
||||
areaGrossSize?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Field(() => Float, { nullable: true })
|
||||
areaNetSize?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Field(() => Float, { nullable: true })
|
||||
width?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Field(() => Float, { nullable: true })
|
||||
size?: number;
|
||||
|
||||
}
|
||||
@@ -31,8 +31,7 @@ export class BuildIbanService {
|
||||
buildProjection(fields: Record<string, any>): any {
|
||||
const projection: any = {};
|
||||
for (const key in fields) {
|
||||
if (key === 'buildIban' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`buildIban.${subField}`] = 1 } }
|
||||
else { projection[key] = 1 }
|
||||
if (key === 'buildIban' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`buildIban.${subField}`] = 1 } } else { projection[key] = 1 }
|
||||
}; return projection;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ import { BuildPartsService } from './build-parts.service';
|
||||
import { BuildPartsResolver } from './build-parts.resolver';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { BuildParts, BuildPartsSchema } from '@/models/build-parts.model';
|
||||
import { Build, BuildSchema } from '@/models/build.model';
|
||||
|
||||
@Module({
|
||||
imports: [MongooseModule.forFeature([{ name: BuildParts.name, schema: BuildPartsSchema }])],
|
||||
imports: [MongooseModule.forFeature([
|
||||
{ name: BuildParts.name, schema: BuildPartsSchema },
|
||||
{ name: Build.name, schema: BuildSchema }])],
|
||||
providers: [BuildPartsService, BuildPartsResolver]
|
||||
})
|
||||
export class BuildPartsModule { }
|
||||
@@ -2,27 +2,36 @@ import { Resolver, Query, Args, ID, Info, Mutation } from '@nestjs/graphql';
|
||||
import { Types } from 'mongoose';
|
||||
import { BuildParts } from '@/models/build-parts.model';
|
||||
import { CreateBuildPartsInput } from './dto/create-build-part.input';
|
||||
import { BuildPartsService } from './build-parts.service';
|
||||
import { ListArguments } from '@/dto/list.input';
|
||||
import { ListBuildPartsResponse } from './dto/list-build-parts.response';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
import type { GraphQLResolveInfo } from 'graphql';
|
||||
import { BuildPartsService } from './build-parts.service';
|
||||
import { UpdateBuildPartsInput } from './dto/update-build-parts.input';
|
||||
|
||||
@Resolver()
|
||||
export class BuildPartsResolver {
|
||||
|
||||
constructor(private readonly buildPartsService: BuildPartsService) { }
|
||||
|
||||
@Query(() => [BuildParts], { name: 'BuildParts' })
|
||||
async getBuildParts(@Info() info: GraphQLResolveInfo): Promise<BuildParts[]> {
|
||||
const fields = graphqlFields(info); const projection = this.buildPartsService.buildProjection(fields); return this.buildPartsService.findAll(projection);
|
||||
@Query(() => ListBuildPartsResponse, { name: 'buildParts' })
|
||||
async getBuildParts(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise<ListBuildPartsResponse> {
|
||||
const fields = graphqlFields(info); const projection = this.buildPartsService.buildProjection(fields?.data); const { skip, limit, sort, filters } = input;
|
||||
return await this.buildPartsService.findAll(projection, skip, limit, sort, filters);
|
||||
}
|
||||
|
||||
@Query(() => BuildParts, { name: 'BuildParts', nullable: true })
|
||||
@Query(() => BuildParts, { name: 'buildPart', nullable: true })
|
||||
async getBuildPart(@Args('id', { type: () => ID }) id: string, @Info() info: GraphQLResolveInfo): Promise<BuildParts | null> {
|
||||
const fields = graphqlFields(info); const projection = this.buildPartsService.buildProjection(fields); return this.buildPartsService.findById(new Types.ObjectId(id), projection);
|
||||
}
|
||||
|
||||
@Mutation(() => BuildParts, { name: 'createBuildPart' })
|
||||
async createBuildPart(@Args('input') input: CreateBuildPartsInput): Promise<BuildParts> {
|
||||
return this.buildPartsService.create(input);
|
||||
}
|
||||
async createBuildPart(@Args('input') input: CreateBuildPartsInput): Promise<BuildParts> { return this.buildPartsService.create(input) }
|
||||
|
||||
@Mutation(() => Boolean, { name: 'deleteBuildPart' })
|
||||
async deleteBuildPart(@Args('uuid') uuid: string): Promise<boolean> { return this.buildPartsService.delete(uuid) }
|
||||
|
||||
@Mutation(() => BuildParts, { name: 'updateBuildPart' })
|
||||
async updateBuildPart(@Args('uuid') uuid: string, @Args('input') input: UpdateBuildPartsInput): Promise<BuildParts> { return this.buildPartsService.update(uuid, input) }
|
||||
|
||||
}
|
||||
|
||||
@@ -3,14 +3,24 @@ import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Types, Model } from 'mongoose';
|
||||
import { BuildParts, BuildPartsDocument } from '@/models/build-parts.model';
|
||||
import { CreateBuildPartsInput } from './dto/create-build-part.input';
|
||||
import { ListBuildPartsResponse } from './dto/list-build-parts.response';
|
||||
import { UpdateBuildPartsInput } from './dto/update-build-parts.input';
|
||||
import { Build, BuildDocument } from '@/models/build.model';
|
||||
|
||||
@Injectable()
|
||||
export class BuildPartsService {
|
||||
|
||||
constructor(@InjectModel(BuildParts.name) private readonly buildPartsModel: Model<BuildPartsDocument>) { }
|
||||
constructor(
|
||||
@InjectModel(BuildParts.name) private readonly buildPartsModel: Model<BuildPartsDocument>,
|
||||
@InjectModel(Build.name) private readonly buildModel: Model<BuildDocument>
|
||||
) { }
|
||||
|
||||
async findAll(projection?: any): Promise<BuildPartsDocument[]> {
|
||||
return this.buildPartsModel.find({}, projection, { lean: false }).populate({ path: 'buildParts', select: projection?.buildParts }).exec();
|
||||
async findAll(projection: any, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListBuildPartsResponse> {
|
||||
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
|
||||
if (query?.buildId) { query.buildId = new Types.ObjectId(query?.buildId) };
|
||||
const totalCount = await this.buildPartsModel.countDocuments(query).exec();
|
||||
const data = await this.buildPartsModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec();
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
async findById(id: Types.ObjectId, projection?: any): Promise<BuildPartsDocument | null> {
|
||||
@@ -18,10 +28,22 @@ export class BuildPartsService {
|
||||
}
|
||||
|
||||
async create(input: CreateBuildPartsInput): Promise<BuildPartsDocument> {
|
||||
const buildParts = new this.buildPartsModel(input);
|
||||
return buildParts.save();
|
||||
const getBuildID = await this.buildModel.findById(input?.buildId, { _id: 1 }, { lean: true }).exec();
|
||||
if (getBuildID) { input.buildId = new Types.ObjectId(getBuildID._id) };
|
||||
if (input?.directionId) { input.directionId = new Types.ObjectId(input?.directionId) };
|
||||
if (input?.typeId) { input.typeId = new Types.ObjectId(input?.typeId) };
|
||||
const buildParts = new this.buildPartsModel(input); return buildParts.save()
|
||||
}
|
||||
|
||||
async update(uuid: string, input: UpdateBuildPartsInput): Promise<BuildPartsDocument> {
|
||||
const buildParts = await this.buildPartsModel.findOne({ _id: new Types.ObjectId(uuid) }); if (!buildParts) { throw new Error('BuildParts not found') };
|
||||
if (input?.buildId) { const getBuildID = await this.buildModel.findById(input?.buildId, { _id: 1 }, { lean: true }).exec(); if (getBuildID) { input.buildId = new Types.ObjectId(getBuildID._id) } };
|
||||
if (input?.directionId) { input.directionId = new Types.ObjectId(input?.directionId) };
|
||||
if (input?.typeId) { input.typeId = new Types.ObjectId(input?.typeId) }; buildParts.set(input); return buildParts.save()
|
||||
}
|
||||
|
||||
async delete(uuid: string): Promise<boolean> { console.log(uuid); const buildParts = await this.buildPartsModel.deleteMany({ _id: new Types.ObjectId(uuid) }); return buildParts.deletedCount > 0 }
|
||||
|
||||
buildProjection(fields: Record<string, any>): any {
|
||||
const projection: any = {};
|
||||
for (const key in fields) {
|
||||
|
||||
@@ -1,10 +1,55 @@
|
||||
import { InputType, Field, ID } from '@nestjs/graphql';
|
||||
|
||||
import { InputType, Field, ID, Float } from '@nestjs/graphql';
|
||||
import { Types } from 'mongoose';
|
||||
|
||||
@InputType()
|
||||
export class CreateBuildPartsInput {
|
||||
|
||||
@Field(() => ID)
|
||||
build: string;
|
||||
@Field(() => ID, { nullable: false })
|
||||
buildId: Types.ObjectId;
|
||||
|
||||
@Field({ nullable: false })
|
||||
addressGovCode: string;
|
||||
|
||||
@Field(() => Float, { nullable: false })
|
||||
no: number;
|
||||
|
||||
@Field(() => Float, { nullable: false })
|
||||
level: number;
|
||||
|
||||
@Field({ nullable: false })
|
||||
code: string;
|
||||
|
||||
@Field(() => Float, { nullable: false })
|
||||
grossSize: number;
|
||||
|
||||
@Field(() => Float, { nullable: false })
|
||||
netSize: number;
|
||||
|
||||
@Field({ nullable: false })
|
||||
defaultAccessory: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
humanLivability: boolean;
|
||||
|
||||
@Field({ nullable: false })
|
||||
key: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
directionId: Types.ObjectId;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
typeId: Types.ObjectId;
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
active: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
isConfirmed: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
expiryStarts: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
expiryEnds: string;
|
||||
|
||||
}
|
||||
|
||||
13
backend/src/build-parts/dto/list-build-parts.response.ts
Normal file
13
backend/src/build-parts/dto/list-build-parts.response.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { BuildParts } from "@/models/build-parts.model";
|
||||
import { Field, Int, ObjectType } from "@nestjs/graphql";
|
||||
|
||||
@ObjectType()
|
||||
export class ListBuildPartsResponse {
|
||||
|
||||
@Field(() => [BuildParts], { nullable: true })
|
||||
data?: BuildParts[];
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
totalCount?: number;
|
||||
|
||||
}
|
||||
50
backend/src/build-parts/dto/update-build-parts.input.ts
Normal file
50
backend/src/build-parts/dto/update-build-parts.input.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ExpiryBaseInput } from "@/models/base.model";
|
||||
import { InputType, Field, ID, Float } from "@nestjs/graphql";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
@InputType()
|
||||
export class UpdateBuildPartsInput extends ExpiryBaseInput {
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
buildId?: Types.ObjectId;
|
||||
|
||||
@Field({ nullable: true })
|
||||
addressGovCode?: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
no?: number;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
level?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
code?: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
grossSize?: number;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
netSize?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
defaultAccessory?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
humanLivability?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
key?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
directionId?: Types.ObjectId;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
typeId?: Types.ObjectId;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
active?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
isConfirmed?: boolean;
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { UpdateBuildSitesInput } from './dto/update-build-sites.input';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { BuildSites, BuildSiteDocument } from '@/models/build-site.model';
|
||||
import { Model } from 'mongoose';
|
||||
import { Build, BuildDocument } from '@/models/build.model';
|
||||
|
||||
@Injectable()
|
||||
export class BuildSitesService {
|
||||
@@ -23,7 +24,10 @@ export class BuildSitesService {
|
||||
return this.buildSitesModel.findById(id, projection, { lean: false }).populate({ path: 'buildSites', select: projection?.buildSites }).exec();
|
||||
}
|
||||
|
||||
async create(input: CreateBuildSitesInput): Promise<BuildSiteDocument> { const buildSite = new this.buildSitesModel(input); return buildSite.save() }
|
||||
async create(input: CreateBuildSitesInput): Promise<BuildSiteDocument> {
|
||||
|
||||
const buildSite = new this.buildSitesModel(input); return buildSite.save()
|
||||
}
|
||||
|
||||
async update(uuid: string, input: UpdateBuildSitesInput): Promise<BuildSiteDocument> { const buildSite = await this.buildSitesModel.findOne({ uuid }); if (!buildSite) { throw new Error('BuildSite not found') }; buildSite.set(input); return buildSite.save() }
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangableBase } from "@/models/base.model";
|
||||
import { ExpiryBaseInput } from "@/models/base.model";
|
||||
import { InputType, Field } from "@nestjs/graphql";
|
||||
|
||||
@InputType()
|
||||
export class UpdateBuildTypesInput extends ChangableBase {
|
||||
export class UpdateBuildTypesInput extends ExpiryBaseInput {
|
||||
|
||||
@Field({ nullable: true })
|
||||
type?: string;
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BuildResolver } from './build.resolver';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { BuildSchema } from '@/models/build.model';
|
||||
import { BuildSchema, BuildIbanSchema } from '@/models/build.model';
|
||||
import { BuildService } from './build.service';
|
||||
import { BuildAreaSchema } from '@/models/build-area.model';
|
||||
import { CompanySchema } from '@/models/company.model';
|
||||
import { PersonSchema } from '@/models/person.model';
|
||||
import { BuildTypesSchema } from '@/models/build-types.model';
|
||||
|
||||
@Module({
|
||||
imports: [MongooseModule.forFeature([{ name: 'Build', schema: BuildSchema }])],
|
||||
imports: [MongooseModule.forFeature([
|
||||
{ name: 'Build', schema: BuildSchema }, { name: 'BuildArea', schema: BuildAreaSchema }, { name: 'BuildIban', schema: BuildIbanSchema },
|
||||
{ name: 'Company', schema: CompanySchema }, { name: 'Person', schema: PersonSchema }, { name: 'BuildTypes', schema: BuildTypesSchema },
|
||||
])],
|
||||
providers: [BuildResolver, BuildService]
|
||||
})
|
||||
export class BuildModule { }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import graphqlFields from 'graphql-fields';
|
||||
import { Resolver, Query, Args, ID, Info, Mutation } from '@nestjs/graphql';
|
||||
import { Types } from 'mongoose';
|
||||
import { Build } from '@/models/build.model';
|
||||
import { CreateBuildInput } from './dto/create-build.input';
|
||||
import { UpdateBuildResponsibleInput, UpdateBuildAttributeInput } from './dto/update-build.input';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
import { UpdateBuildResponsibleInput, UpdateBuildAttributeInput, UpdateBuildInput } from './dto/update-build.input';
|
||||
import { BuildService } from './build.service';
|
||||
import type { GraphQLResolveInfo } from 'graphql';
|
||||
import { ListArguments } from '@/dto/list.input';
|
||||
import { ListBuildResponse } from './dto/list-build.response';
|
||||
import { ListBuildResponse, ListPartialAreaResponse, ListPartialCompanyResponse, ListPartialIbanResponse } from './dto/list-build.response';
|
||||
import type { GraphQLResolveInfo } from 'graphql';
|
||||
|
||||
@Resolver()
|
||||
export class BuildResolver {
|
||||
@@ -16,7 +16,22 @@ export class BuildResolver {
|
||||
|
||||
@Query(() => ListBuildResponse, { name: 'builds' })
|
||||
async getBuilds(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise<ListBuildResponse> {
|
||||
const fields = graphqlFields(info); const projection = this.buildService.buildProjection(fields); return this.buildService.findAll(projection, input.skip, input.limit, input.sort, input.filters);
|
||||
const fields = graphqlFields(info); const projection = this.buildService.buildProjection(fields); return this.buildService.findAll(input.skip, input.limit, input.sort, input.filters);
|
||||
}
|
||||
|
||||
@Query(() => ListPartialAreaResponse, { name: 'getBuildsAreas' })
|
||||
async getBuildsAreas(@Info() info: GraphQLResolveInfo, @Args('id') id: string, @Args('input') input: ListArguments): Promise<ListPartialAreaResponse> {
|
||||
const fields = graphqlFields(info); const projection = this.buildService.buildProjection(fields); return this.buildService.findAllAreas(id, input.skip, input.limit, input.sort, input.filters);
|
||||
}
|
||||
|
||||
@Query(() => ListPartialIbanResponse, { name: 'getBuildsIbans' })
|
||||
async getBuildsIbans(@Info() info: GraphQLResolveInfo, @Args('id') id: string, @Args('input') input: ListArguments): Promise<ListPartialIbanResponse> {
|
||||
const fields = graphqlFields(info); const projection = this.buildService.buildProjection(fields); return this.buildService.findAllIbans(id, input.skip, input.limit, input.sort, input.filters);
|
||||
}
|
||||
|
||||
@Query(() => ListPartialCompanyResponse, { name: 'getBuildsCompaniesAndResponsibles' })
|
||||
async getBuildsCompaniesAndResponsibles(@Info() info: GraphQLResolveInfo, @Args('id') id: string, @Args('input') input: ListArguments): Promise<ListPartialCompanyResponse> {
|
||||
const fields = graphqlFields(info); const projection = this.buildService.buildProjection(fields); return this.buildService.findAllCompaniesAndResponsibles(id, input.skip, input.limit, input.sort, input.filters);
|
||||
}
|
||||
|
||||
@Query(() => Build, { name: 'build', nullable: true })
|
||||
@@ -24,17 +39,19 @@ export class BuildResolver {
|
||||
const fields = graphqlFields(info); const projection = this.buildService.buildProjection(fields); return this.buildService.findById(new Types.ObjectId(id), projection);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, { name: 'deleteBuild' })
|
||||
async deleteBuild(@Args('uuid') uuid: string): Promise<boolean> { return this.buildService.delete(uuid) }
|
||||
|
||||
@Mutation(() => Build, { name: 'createBuild' })
|
||||
async createBuild(@Args('input') input: CreateBuildInput): Promise<Build> { return this.buildService.create(input) }
|
||||
|
||||
@Mutation(() => Build, { name: 'updateBuild' })
|
||||
async updateBuild(@Args('uuid') uuid: string, @Args('input') input: UpdateBuildInput): Promise<Build> { return this.buildService.update(uuid, input) }
|
||||
|
||||
@Mutation(() => Build, { name: 'updateBuildResponsible' })
|
||||
async updateBuildResponsible(@Args('uuid', { type: () => ID }) uuid: string, @Args('input') input: UpdateBuildResponsibleInput): Promise<Build> {
|
||||
return this.buildService.updateResponsible(uuid, input);
|
||||
}
|
||||
async updateBuildResponsible(@Args('uuid') uuid: string, @Args('input') input: UpdateBuildResponsibleInput): Promise<Build> { return this.buildService.updateResponsible(uuid, input) }
|
||||
|
||||
@Mutation(() => Build, { name: 'updateBuildAttribute' })
|
||||
async updateBuildAttribute(@Args('uuid', { type: () => ID }) uuid: string, @Args('input') input: UpdateBuildAttributeInput): Promise<Build> {
|
||||
return this.buildService.updateAttribute(uuid, input);
|
||||
}
|
||||
async updateBuildAttribute(@Args('uuid') uuid: string, @Args('input') input: UpdateBuildAttributeInput): Promise<Build> { return this.buildService.updateAttribute(uuid, input) }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Types, Model } from 'mongoose';
|
||||
import { Build, BuildDocument } from '@/models/build.model';
|
||||
import { Build, BuildDocument, BuildIban, BuildIbanDocument } from '@/models/build.model';
|
||||
import { CreateBuildInput } from './dto/create-build.input';
|
||||
import { UpdateBuildAttributeInput, UpdateBuildResponsibleInput } from './dto/update-build.input';
|
||||
import { ListBuildResponse } from './dto/list-build.response';
|
||||
import { UpdateBuildAttributeInput, UpdateBuildInput, UpdateBuildResponsibleInput } from './dto/update-build.input';
|
||||
import { ListBuildResponse, ListPartialAreaResponse, ListPartialCompanyResponse, ListPartialIbanResponse } from './dto/list-build.response';
|
||||
import { cleanRefArrayField } from '@/lib/getListOfModelByIDs';
|
||||
import { BuildArea, BuildAreaDocument } from '@/models/build-area.model';
|
||||
import { Company, CompanyDocument } from '@/models/company.model';
|
||||
import { Person, PersonDocument } from '@/models/person.model';
|
||||
import { BuildTypes, BuildTypesDocument } from '@/models/build-types.model';
|
||||
|
||||
@Injectable()
|
||||
export class BuildService {
|
||||
|
||||
constructor(@InjectModel(Build.name) private readonly buildModel: Model<BuildDocument>) { }
|
||||
constructor(
|
||||
@InjectModel(Build.name) private readonly buildModel: Model<BuildDocument>,
|
||||
@InjectModel(BuildArea.name) private readonly areaModel: Model<BuildAreaDocument>,
|
||||
@InjectModel(BuildIban.name) private readonly ibanModel: Model<BuildIbanDocument>,
|
||||
@InjectModel(Company.name) private readonly companyModel: Model<CompanyDocument>,
|
||||
@InjectModel(Person.name) private readonly personModel: Model<PersonDocument>,
|
||||
@InjectModel(BuildTypes.name) private readonly buildTypeModel: Model<BuildTypesDocument>,
|
||||
) { }
|
||||
|
||||
async findAll(projection: any, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListBuildResponse> {
|
||||
async findAll(skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListBuildResponse> {
|
||||
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
|
||||
const totalCount = await this.buildModel.countDocuments(query).exec();
|
||||
const data = await this.buildModel.find(query).skip(skip).limit(limit).sort(sort).exec();
|
||||
const data = await this.buildModel.find(query).populate('buildType').skip(skip).limit(limit).sort(sort).exec();
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
@@ -22,14 +34,98 @@ export class BuildService {
|
||||
return this.buildModel.findById(id, projection, { lean: false }).populate({ path: 'buildArea', select: projection?.buildArea }).exec();
|
||||
}
|
||||
|
||||
async create(input: CreateBuildInput): Promise<BuildDocument> { const buildArea = new this.buildModel(input); return buildArea.save() }
|
||||
async create(input: CreateBuildInput): Promise<BuildDocument> {
|
||||
const getObjectIDofToken = await this.buildTypeModel.findOne({ token: input.buildType });
|
||||
const build = new this.buildModel(input);
|
||||
if (!getObjectIDofToken?._id) { throw new Error('Build type not found') }
|
||||
const idOfBuildTypes = new Types.ObjectId(getObjectIDofToken._id)
|
||||
build.set({ buildType: idOfBuildTypes });
|
||||
await build.populate('buildType')
|
||||
return build.save()
|
||||
}
|
||||
|
||||
async update(uuid: string, input: UpdateBuildInput): Promise<BuildDocument> {
|
||||
let idOfBuildTypes: Types.ObjectId | null = null;
|
||||
if (input.buildType) {
|
||||
const buildTypeDoc = await this.buildTypeModel.findOne({ token: input.buildType });
|
||||
if (!buildTypeDoc?._id) { throw new Error('Build type not found') }; idOfBuildTypes = new Types.ObjectId(buildTypeDoc._id);
|
||||
}
|
||||
const build = await this.buildModel.findById(new Types.ObjectId(uuid));
|
||||
if (!build) { throw new Error('Build not found') }
|
||||
const { info, buildType: _ignoredBuildType, ...rest } = input;
|
||||
if (Object.keys(rest).length > 0) { build.set(rest) }
|
||||
if (info) { build.set({ info: { ...(build.info as any)?.toObject?.() ?? build.info ?? {}, ...info } }) }
|
||||
if (idOfBuildTypes) { build.set({ buildType: idOfBuildTypes }) }
|
||||
const saved = await build.save(); await saved.populate('buildType'); return saved;
|
||||
}
|
||||
|
||||
async delete(uuid: string): Promise<boolean> {
|
||||
const build = await this.buildModel.deleteMany({ _id: new Types.ObjectId(uuid) }); return build.deletedCount > 0;
|
||||
}
|
||||
|
||||
async updateResponsible(uuid: string, input: UpdateBuildResponsibleInput): Promise<BuildDocument> {
|
||||
const build = await this.buildModel.findOne({ uuid }); if (!build) { throw new Error('Build not found') }; build.set({ responsible: input }); return build.save()
|
||||
const build = await this.buildModel.findOne({ _id: new Types.ObjectId(uuid) }); if (!build) { throw new Error('Build not found') }; build.set({ responsible: input }); return build.save()
|
||||
}
|
||||
|
||||
async updateAttribute(uuid: string, input: UpdateBuildAttributeInput): Promise<BuildDocument> {
|
||||
const build = await this.buildModel.findOne({ uuid }); if (!build) { throw new Error('Build not found') }; build.set({ attribute: input }); return build.save()
|
||||
const build = await this.buildModel.findOne({ _id: new Types.ObjectId(uuid) }); if (!build) { throw new Error('Build not found') }; build.set({ attribute: input }); return build.save()
|
||||
}
|
||||
|
||||
async findAllAreas(id: string, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListPartialAreaResponse> {
|
||||
const buildId = new Types.ObjectId(id);
|
||||
const { keptIds } = await cleanRefArrayField({ parentModel: this.buildModel, refModel: this.areaModel, parentId: buildId, fieldName: "areas", });
|
||||
if (keptIds.length === 0) { return { data: [], totalCount: 0 } }
|
||||
const query: any = { _id: { $in: keptIds } };
|
||||
if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) }
|
||||
const totalCount = await this.areaModel.countDocuments(query).exec();
|
||||
const data = await this.areaModel.find(query).skip(skip).limit(limit).sort(sort).exec();
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
async findAllIbans(id: string, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListPartialIbanResponse> {
|
||||
const buildId = new Types.ObjectId(id);
|
||||
const { keptIds } = await cleanRefArrayField({ parentModel: this.buildModel, refModel: this.ibanModel, parentId: buildId, fieldName: "ibans", });
|
||||
if (keptIds.length === 0) { return { data: [], totalCount: 0 } }
|
||||
const query: any = { _id: { $in: keptIds } };
|
||||
if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) }
|
||||
const totalCount = await this.ibanModel.countDocuments(query).exec();
|
||||
const data = await this.ibanModel.find(query).skip(skip).limit(limit).sort(sort).exec();
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
async findAllCompaniesAndResponsibles(id: string, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListPartialCompanyResponse> {
|
||||
const buildId = new Types.ObjectId(id);
|
||||
const build = await this.buildModel.findById(buildId).lean();
|
||||
if (!build) throw new Error('Build not found');
|
||||
const responsibles = (build.responsibles || []) as { company?: string; person?: string }[];
|
||||
if (responsibles.length === 0) { return { data: [], totalCount: 0 } }
|
||||
console.dir({ responsibles }, { depth: Infinity });
|
||||
const companyIds = Array.from(new Set(responsibles.map(r => r.company).filter((id): id is string => !!id))).map(id => new Types.ObjectId(id));
|
||||
const personIds = Array.from(new Set(responsibles.map(r => r.person).filter((id): id is string => !!id))).map(id => new Types.ObjectId(id));
|
||||
console.dir({ companyIds, personIds }, { depth: Infinity });
|
||||
const t = await this.companyModel.find({ _id: { $in: companyIds } })
|
||||
const l = await this.companyModel.find({ _id: { $in: companyIds } }).lean()
|
||||
console.dir({ t, l }, { depth: Infinity })
|
||||
const [companies, persons] = await Promise.all([this.companyModel.find({ _id: { $in: companyIds } }).lean(), this.personModel.find({ _id: { $in: personIds } }).lean(),]);
|
||||
console.dir({ companies, persons }, { depth: Infinity })
|
||||
const companyMap = new Map(companies.map(c => [String(c._id), c]));
|
||||
const personMap = new Map(persons.map(p => [String(p._id), p]));
|
||||
const cleanedResponsibles: { company?: string; person?: string }[] = [];
|
||||
const resolvedList: { company?: any; person?: any }[] = [];
|
||||
for (const r of responsibles) {
|
||||
const companyId = r.company && String(r.company);
|
||||
const personId = r.person && String(r.person);
|
||||
const companyDoc = companyId ? companyMap.get(companyId) : undefined;
|
||||
const personDoc = personId ? personMap.get(personId) : undefined;
|
||||
if ((companyId && !companyDoc) || (personId && !personDoc)) { continue }
|
||||
cleanedResponsibles.push({ company: companyId, person: personId });
|
||||
resolvedList.push({ company: companyDoc, person: personDoc });
|
||||
}
|
||||
console.dir({ buildId, cleanedResponsibles, resolvedList }, { depth: Infinity });
|
||||
await this.buildModel.updateOne({ _id: buildId }, { $set: { responsibles: cleanedResponsibles } });
|
||||
const totalCount = resolvedList.length;
|
||||
const data = resolvedList.slice(skip, skip + limit);
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
buildProjection(fields: Record<string, any>): any {
|
||||
|
||||
@@ -49,15 +49,15 @@ export class CreateBuildInfoInput {
|
||||
@Field()
|
||||
garageCount: number;
|
||||
|
||||
@Field()
|
||||
managementRoomId: number;
|
||||
@Field({ nullable: true })
|
||||
managementRoomId?: string;
|
||||
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class CreateBuildInput extends ExpiryBaseInput {
|
||||
|
||||
@Field(() => ID)
|
||||
@Field()
|
||||
buildType: string;
|
||||
|
||||
@Field()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Build } from "@/models/build.model";
|
||||
import { Field, Int, ObjectType } from "@nestjs/graphql";
|
||||
import { Build, BuildIban } from "@/models/build.model";
|
||||
import { BuildArea } from "@/models/build-area.model";
|
||||
import { Company } from "@/models/company.model";
|
||||
import { Person } from "@/models/person.model";
|
||||
|
||||
@ObjectType()
|
||||
export class ListBuildResponse {
|
||||
@@ -11,3 +14,44 @@ export class ListBuildResponse {
|
||||
totalCount?: number;
|
||||
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ListPartialAreaResponse {
|
||||
|
||||
@Field(() => [BuildArea], { nullable: true })
|
||||
data?: BuildArea[];
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
totalCount?: number;
|
||||
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ListPartialIbanResponse {
|
||||
|
||||
@Field(() => [BuildIban], { nullable: true })
|
||||
data?: BuildIban[];
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
totalCount?: number;
|
||||
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class ResponsibleCompanyPerson {
|
||||
@Field(() => Company, { nullable: true })
|
||||
company?: Company;
|
||||
|
||||
@Field(() => Person, { nullable: true })
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ListPartialCompanyResponse {
|
||||
|
||||
@Field(() => [ResponsibleCompanyPerson])
|
||||
data: ResponsibleCompanyPerson[];
|
||||
|
||||
@Field(() => Int)
|
||||
totalCount: number;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { InputType, Field, ID } from "@nestjs/graphql";
|
||||
import { ExpiryBaseInput } from "@/models/base.model";
|
||||
|
||||
@InputType()
|
||||
export class UpdateResponsibleInput {
|
||||
@@ -41,3 +42,70 @@ export class UpdateBuildResponsibleInput {
|
||||
person?: string;
|
||||
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class UpdateBuildInfoInput {
|
||||
|
||||
@Field({ nullable: true })
|
||||
govAddressCode?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
buildName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
buildNo?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
maxFloor?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
undergroundFloor?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
buildDate?: Date;
|
||||
|
||||
@Field({ nullable: true })
|
||||
decisionPeriodDate?: Date;
|
||||
|
||||
@Field({ nullable: true })
|
||||
taxNo?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
liftCount?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
heatingSystem?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
coolingSystem?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
hotWaterSystem?: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
blockServiceManCount?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
securityServiceManCount?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
garageCount?: number;
|
||||
|
||||
@Field({ nullable: true })
|
||||
managementRoomId?: number;
|
||||
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class UpdateBuildInput extends ExpiryBaseInput {
|
||||
|
||||
@Field({ nullable: true })
|
||||
buildType?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
collectionToken?: string;
|
||||
|
||||
@Field(() => UpdateBuildInfoInput, { nullable: true })
|
||||
info?: UpdateBuildInfoInput;
|
||||
|
||||
}
|
||||
|
||||
@@ -60,3 +60,53 @@ mutation CreateBuild($input: CreateBuildInput!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ListBuild($input: ListArguments!) {
|
||||
builds(input: $input) {
|
||||
data {
|
||||
_id
|
||||
buildType {
|
||||
token
|
||||
typeToken
|
||||
type
|
||||
}
|
||||
collectionToken
|
||||
info {
|
||||
govAddressCode
|
||||
buildName
|
||||
buildNo
|
||||
maxFloor
|
||||
undergroundFloor
|
||||
buildDate
|
||||
decisionPeriodDate
|
||||
taxNo
|
||||
liftCount
|
||||
heatingSystem
|
||||
coolingSystem
|
||||
hotWaterSystem
|
||||
blockServiceManCount
|
||||
securityServiceManCount
|
||||
garageCount
|
||||
managementRoomId
|
||||
}
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
|
||||
query GetBuildsAreas($id: String!, $input: ListArguments!) {
|
||||
getBuildsAreas(id: $id, input: $input) {
|
||||
data {
|
||||
_id
|
||||
uuid
|
||||
areaName
|
||||
areaCode
|
||||
areaType
|
||||
areaNetSize
|
||||
areaDirection
|
||||
areaGrossSize
|
||||
}
|
||||
totalCount
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
||||
11
backend/src/company/company.module.ts
Normal file
11
backend/src/company/company.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CompanyService } from './company.service';
|
||||
import { CompanyResolver } from './company.resolver';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { Company, CompanySchema } from '@/models/company.model';
|
||||
|
||||
@Module({
|
||||
imports: [MongooseModule.forFeature([{ name: Company.name, schema: CompanySchema }])],
|
||||
providers: [CompanyService, CompanyResolver]
|
||||
})
|
||||
export class CompanyModule { }
|
||||
18
backend/src/company/company.resolver.spec.ts
Normal file
18
backend/src/company/company.resolver.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CompanyResolver } from './company.resolver';
|
||||
|
||||
describe('CompanyResolver', () => {
|
||||
let resolver: CompanyResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CompanyResolver],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<CompanyResolver>(CompanyResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
29
backend/src/company/company.resolver.ts
Normal file
29
backend/src/company/company.resolver.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Resolver, Args, ID, Info, Mutation, Query } from '@nestjs/graphql';
|
||||
import { CompanyService } from './company.service';
|
||||
import { Company } from '@/models/company.model';
|
||||
import { CreateCompanyInput } from './dto/create-company.input';
|
||||
import { Types } from 'mongoose';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
import type { GraphQLResolveInfo } from 'graphql';
|
||||
import { ListCompanyResponse } from './dto/list-company.response';
|
||||
import { ListArguments } from '@/dto/list.input';
|
||||
|
||||
@Resolver()
|
||||
export class CompanyResolver {
|
||||
|
||||
constructor(private readonly companyService: CompanyService) { }
|
||||
|
||||
@Query(() => ListCompanyResponse, { name: 'companies' })
|
||||
async getCompanies(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise<ListCompanyResponse> {
|
||||
const fields = graphqlFields(info); const projection = this.companyService.buildProjection(fields); return this.companyService.findAll(input.skip, input.limit, input.sort, input.filters);
|
||||
}
|
||||
|
||||
@Query(() => Company, { name: 'company', nullable: true })
|
||||
async getCompany(@Args('id', { type: () => ID }) id: string, @Info() info: GraphQLResolveInfo): Promise<Company | null> {
|
||||
const fields = graphqlFields(info); const projection = this.companyService.buildProjection(fields); return this.companyService.findById(new Types.ObjectId(id), projection);
|
||||
}
|
||||
|
||||
@Mutation(() => Company, { name: 'createCompany' })
|
||||
async createCompany(@Args('input') input: CreateCompanyInput): Promise<Company> { return this.companyService.create(input) }
|
||||
|
||||
}
|
||||
18
backend/src/company/company.service.spec.ts
Normal file
18
backend/src/company/company.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CompanyService } from './company.service';
|
||||
|
||||
describe('CompanyService', () => {
|
||||
let service: CompanyService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CompanyService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CompanyService>(CompanyService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
39
backend/src/company/company.service.ts
Normal file
39
backend/src/company/company.service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Types, Model } from 'mongoose';
|
||||
import { Company, CompanyDocument } from '@/models/company.model';
|
||||
import { CreateCompanyInput } from './dto/create-company.input';
|
||||
import { ListCompanyResponse } from './dto/list-company.response';
|
||||
import { UpdateCompanyInput } from './dto/update-company.input';
|
||||
|
||||
@Injectable()
|
||||
export class CompanyService {
|
||||
|
||||
constructor(@InjectModel(Company.name) private readonly companyModel: Model<CompanyDocument>) { }
|
||||
|
||||
async findAll(projection: any, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListCompanyResponse> {
|
||||
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
|
||||
const totalCount = await this.companyModel.countDocuments(query).exec();
|
||||
const data = await this.companyModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec();
|
||||
return { data, totalCount };
|
||||
}
|
||||
|
||||
async findById(id: Types.ObjectId, projection?: any): Promise<CompanyDocument | null> {
|
||||
return this.companyModel.findById(id, projection, { lean: false }).populate({ path: 'company', select: projection?.company }).exec();
|
||||
}
|
||||
|
||||
async create(input: CreateCompanyInput): Promise<CompanyDocument> { const company = new this.companyModel(input); return company.save() }
|
||||
|
||||
async update(uuid: string, input: UpdateCompanyInput): Promise<CompanyDocument> { const company = await this.companyModel.findOne({ uuid }); if (!company) { throw new Error('Company not found') }; company.set(input); return company.save() }
|
||||
|
||||
async delete(uuid: string): Promise<boolean> { const company = await this.companyModel.deleteMany({ uuid }); return company.deletedCount > 0 }
|
||||
|
||||
buildProjection(fields: Record<string, any>): any {
|
||||
const projection: any = {};
|
||||
for (const key in fields) {
|
||||
if (key === 'buildSites' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`buildSites.${subField}`] = 1 } }
|
||||
else { projection[key] = 1 }
|
||||
}; return projection;
|
||||
}
|
||||
|
||||
}
|
||||
11
backend/src/company/dto/create-company.input.ts
Normal file
11
backend/src/company/dto/create-company.input.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
import { InputType, Field, ID } from '@nestjs/graphql';
|
||||
|
||||
|
||||
@InputType()
|
||||
export class CreateCompanyInput {
|
||||
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
}
|
||||
14
backend/src/company/dto/list-company.response.ts
Normal file
14
backend/src/company/dto/list-company.response.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Field, ObjectType } from "@nestjs/graphql";
|
||||
import { Int } from "@nestjs/graphql";
|
||||
import { Company } from "@/models/company.model";
|
||||
|
||||
@ObjectType()
|
||||
export class ListCompanyResponse {
|
||||
|
||||
@Field(() => [Company], { nullable: true })
|
||||
data?: Company[];
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
totalCount?: number;
|
||||
|
||||
}
|
||||
9
backend/src/company/dto/update-company.input.ts
Normal file
9
backend/src/company/dto/update-company.input.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { InputType, Field, ID } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class UpdateCompanyInput {
|
||||
|
||||
@Field({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
}
|
||||
25
backend/src/lib/getListOfModelByIDs.ts
Normal file
25
backend/src/lib/getListOfModelByIDs.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Types, Model } from "mongoose";
|
||||
|
||||
type ObjectIdLike = Types.ObjectId | string;
|
||||
|
||||
interface CleanRefArrayParams {
|
||||
parentModel: Model<any>;
|
||||
refModel: Model<any>;
|
||||
parentId: ObjectIdLike;
|
||||
fieldName: string;
|
||||
}
|
||||
|
||||
export async function cleanRefArrayField({ parentModel, refModel, parentId, fieldName }: CleanRefArrayParams): Promise<{ keptIds: Types.ObjectId[]; removedIds: Types.ObjectId[] }> {
|
||||
const parent = await parentModel.findById(parentId).lean();
|
||||
if (!parent) { throw new Error("Parent document not found") }
|
||||
const rawIds: ObjectIdLike[] = parent[fieldName] || [];
|
||||
if (!Array.isArray(rawIds) || rawIds.length === 0) { return { keptIds: [], removedIds: [] } }
|
||||
const ids = rawIds.map((id) => typeof id === "string" ? new Types.ObjectId(id) : id);
|
||||
const existing = await refModel.find({ _id: { $in: ids } }).select("_id").lean();
|
||||
const existingSet = new Set(existing.map((d) => String(d._id)));
|
||||
const keptIds = ids.filter((id) => existingSet.has(String(id)));
|
||||
const removedIds = ids.filter((id) => !existingSet.has(String(id)));
|
||||
await parentModel.updateOne({ _id: parentId }, { $set: { [fieldName]: keptIds } });
|
||||
return { keptIds, removedIds };
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ export class BuildArea extends Base {
|
||||
@Field(() => ID)
|
||||
readonly _id: string;
|
||||
|
||||
@Field(() => Float)
|
||||
@Field()
|
||||
@Prop({ required: true })
|
||||
areaName: string;
|
||||
|
||||
@Field(() => Float)
|
||||
@Field()
|
||||
@Prop({ required: true })
|
||||
areaCode: string;
|
||||
|
||||
@@ -26,19 +26,19 @@ export class BuildArea extends Base {
|
||||
@Prop({ required: true })
|
||||
areaDirection: string;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
@Prop({ required: true })
|
||||
areaGrossSize: number;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
@Prop({ required: true })
|
||||
areaNetSize: number;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
@Prop({ required: true })
|
||||
width: number;
|
||||
|
||||
@Field()
|
||||
@Field(() => Float)
|
||||
@Prop({ required: true })
|
||||
size: number;
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import { Base } from '@/models/base.model';
|
||||
@Schema({ timestamps: true })
|
||||
export class BuildParts extends Base {
|
||||
|
||||
@Field(() => ID)
|
||||
readonly _id: string;
|
||||
|
||||
@Field(() => ID)
|
||||
@Prop({ type: Types.ObjectId, ref: 'Build', required: true })
|
||||
buildId: Types.ObjectId;
|
||||
@@ -47,13 +50,14 @@ export class BuildParts extends Base {
|
||||
@Prop({ required: true })
|
||||
key: string;
|
||||
|
||||
@Field(() => ID)
|
||||
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: true })
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: false })
|
||||
directionId: Types.ObjectId;
|
||||
|
||||
@Field(() => ID)
|
||||
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: true })
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: false })
|
||||
typeId: Types.ObjectId;
|
||||
|
||||
}
|
||||
|
||||
export type BuildPartsDocument = BuildParts & Document;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
|
||||
import { Base } from '@/models/base.model';
|
||||
import { Person } from '@/models/person.model';
|
||||
import { Company } from '@/models/company.model';
|
||||
import { BuildTypes } from './build-types.model';
|
||||
|
||||
@ObjectType()
|
||||
@Schema({ timestamps: true })
|
||||
@@ -37,13 +38,13 @@ export class BuildIban extends Base {
|
||||
@ObjectType()
|
||||
export class BuildResponsible {
|
||||
|
||||
@Field(() => [String], { nullable: true, defaultValue: [] })
|
||||
@Prop({ type: [String], default: [] })
|
||||
company?: string[];
|
||||
@Field(() => String, { nullable: true })
|
||||
@Prop({ type: String, default: '' })
|
||||
company?: string;
|
||||
|
||||
@Field(() => [String], { nullable: true, defaultValue: [] })
|
||||
@Prop({ type: [String], default: [] })
|
||||
person?: string[];
|
||||
@Field(() => String, { nullable: true })
|
||||
@Prop({ type: String, default: '' })
|
||||
person?: string;
|
||||
|
||||
}
|
||||
|
||||
@@ -110,9 +111,9 @@ export class BuildInfo {
|
||||
@Prop({ required: true })
|
||||
garageCount: number;
|
||||
|
||||
@Field(() => Int)
|
||||
@Prop({ required: true })
|
||||
managementRoomId: number;
|
||||
@Field(() => String, { nullable: true })
|
||||
@Prop({ required: false })
|
||||
managementRoomId?: string;
|
||||
|
||||
}
|
||||
|
||||
@@ -123,13 +124,9 @@ export class Build {
|
||||
@Field()
|
||||
readonly _id: string;
|
||||
|
||||
// @Field(() => ID)
|
||||
// @Prop({ type: Types.ObjectId, ref: 'BuildType', required: true })
|
||||
// buildType: Types.ObjectId;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@Prop({ required: true })
|
||||
buildType: string;
|
||||
@Field(() => BuildTypes)
|
||||
@Prop({ type: Types.ObjectId, ref: BuildTypes.name, required: true })
|
||||
buildType: Types.ObjectId;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@Prop({ required: true })
|
||||
|
||||
@@ -6,6 +6,10 @@ import { Base } from '@/models/base.model';
|
||||
@ObjectType()
|
||||
@Schema({ timestamps: true })
|
||||
export class Company extends Base {
|
||||
|
||||
@Field(() => ID)
|
||||
readonly _id: string;
|
||||
|
||||
@Field(() => ID)
|
||||
@Prop({ required: true })
|
||||
name: string;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { UserType } from '@/models/user-type.model';
|
||||
|
||||
@ObjectType()
|
||||
export class LivingSpaces extends Base {
|
||||
|
||||
@Field(() => ID)
|
||||
@Prop({ type: Types.ObjectId, ref: BuildParts.name, required: true })
|
||||
part: Types.ObjectId;
|
||||
@@ -25,8 +26,8 @@ export class LivingSpaces extends Base {
|
||||
@Field(() => ID)
|
||||
@Prop({ type: Types.ObjectId, ref: UserType.name, required: true })
|
||||
userType: Types.ObjectId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type LivingSpacesDocument = LivingSpaces & Document;
|
||||
export const LivingSpacesSchema = SchemaFactory.createForClass(LivingSpaces);
|
||||
|
||||
@@ -36,6 +36,10 @@ export class User extends Base {
|
||||
@Field(() => ID)
|
||||
_id: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Prop({ required: false, default: '' })
|
||||
avatar?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Prop({ default: () => new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) })
|
||||
expiresAt?: Date;
|
||||
|
||||
@@ -21,6 +21,9 @@ export class CollectionTokenInput {
|
||||
@InputType()
|
||||
export class CreateUserInput {
|
||||
|
||||
@Field({ nullable: true })
|
||||
avatar?: string;
|
||||
|
||||
@Field()
|
||||
password: string;
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ export class UpdateCollectionTokenInput {
|
||||
@InputType()
|
||||
export class UpdateUserInput {
|
||||
|
||||
@Field({ nullable: true })
|
||||
avatar?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
tag?: string;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user