updated living space

This commit is contained in:
2025-11-27 20:22:40 +03:00
parent 3aebb79d36
commit eaca36573e
57 changed files with 2434 additions and 147 deletions

View File

@@ -15,6 +15,7 @@ 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';
import { LivingSpaceModule } from './living-space/living-space.module';
@Module({
imports: [
@@ -35,6 +36,7 @@ import { CompanyModule } from './company/company.module';
BuildIbanModule,
BuildSitesModule,
CompanyModule,
LivingSpaceModule,
],
controllers: [AppController],
providers: [AppService],

View File

@@ -3,22 +3,24 @@ 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';
import { UpdateCompanyInput } from './dto/update-company.input';
import type { GraphQLResolveInfo } from 'graphql';
import graphqlFields from 'graphql-fields';
@Resolver()
export class CompanyResolver {
constructor(private readonly companyService: CompanyService) { }
@Query(() => ListCompanyResponse, { name: 'companies' })
@Query(() => ListCompanyResponse, { name: 'getCompanies' })
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);
const fields = graphqlFields(info); const projection = this.companyService.buildProjection(fields?.data); const { skip, limit, sort, filters } = input;
return await this.companyService.findAll(projection, skip, limit, sort, filters);
}
@Query(() => Company, { name: 'company', nullable: true })
@Query(() => Company, { name: 'getCompany', 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);
}
@@ -26,4 +28,10 @@ export class CompanyResolver {
@Mutation(() => Company, { name: 'createCompany' })
async createCompany(@Args('input') input: CreateCompanyInput): Promise<Company> { return this.companyService.create(input) }
@Mutation(() => Company, { name: 'updateCompany' })
async updateCompany(@Args('uuid') uuid: string, @Args('input') input: UpdateCompanyInput): Promise<Company> { return this.companyService.update(uuid, input) }
@Mutation(() => Boolean, { name: 'deleteCompany' })
async deleteCompany(@Args('uuid') uuid: string): Promise<boolean> { return this.companyService.delete(uuid) }
}

View File

@@ -1,11 +1,49 @@
import { ExpiryBaseInput } from '@/models/base.model';
import { InputType, Field, ID } from '@nestjs/graphql';
@InputType()
export class CreateCompanyInput {
export class CreateCompanyInput extends ExpiryBaseInput {
@Field()
name: string;
formal_name: string;
@Field()
company_type: string;
@Field()
commercial_type: string;
@Field()
tax_no: string;
@Field()
public_name: string;
@Field()
company_tag: string;
@Field({ defaultValue: 'TR' })
default_lang_type: string;
@Field({ defaultValue: 'TL' })
default_money_type: string;
@Field({ defaultValue: false })
is_commercial?: boolean;
@Field({ defaultValue: false })
is_blacklist?: boolean;
@Field()
parent_id?: string;
@Field()
workplace_no?: string;
@Field()
official_address?: string;
@Field()
top_responsible_company?: string;
}

View File

@@ -1,9 +1,48 @@
import { InputType, Field, ID } from '@nestjs/graphql';
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class UpdateCompanyInput {
@Field({ nullable: true })
name?: string;
formal_name?: string;
@Field({ nullable: true })
company_type?: string;
@Field({ nullable: true })
commercial_type?: string;
@Field({ nullable: true })
tax_no?: string;
@Field({ nullable: true })
public_name?: string;
@Field({ nullable: true })
company_tag?: string;
@Field({ nullable: true })
default_lang_type?: string;
@Field({ nullable: true })
default_money_type?: string;
@Field({ nullable: true })
is_commercial?: boolean;
@Field({ nullable: true })
is_blacklist?: boolean;
@Field({ nullable: true })
parent_id?: string;
@Field({ nullable: true })
workplace_no?: string;
@Field({ nullable: true })
official_address?: string;
@Field({ nullable: true })
top_responsible_company?: string;
}

View File

@@ -22,4 +22,3 @@ export async function cleanRefArrayField({ parentModel, refModel, parentId, fiel
await parentModel.updateOne({ _id: parentId }, { $set: { [fieldName]: keptIds } });
return { keptIds, removedIds };
}

View File

@@ -0,0 +1,25 @@
import { ExpiryBaseInput } from '@/models/base.model';
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateLivingSpaceInput extends ExpiryBaseInput {
@Field()
buildID: string;
@Field()
collectionToken: string;
@Field()
partID: string;
@Field()
userTypeID: string;
@Field()
companyID: string;
@Field()
personID: string;
}

View File

@@ -0,0 +1,14 @@
import { Field, ObjectType } from "@nestjs/graphql";
import { Int } from "@nestjs/graphql";
import { LivingSpaces } from "@/models/living-spaces.model";
@ObjectType()
export class ListLivingSpaceResponse {
@Field(() => [LivingSpaces], { nullable: true })
data?: LivingSpaces[];
@Field(() => Int, { nullable: true })
totalCount?: number;
}

View File

@@ -0,0 +1,25 @@
import { ExpiryBaseInput } from '@/models/base.model';
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class UpdateLivingSpaceInput extends ExpiryBaseInput {
@Field({ nullable: true })
buildID?: string;
@Field({ nullable: true })
collectionToken?: string;
@Field({ nullable: true })
partID?: string;
@Field({ nullable: true })
userTypeID?: string;
@Field({ nullable: true })
companyID?: string;
@Field({ nullable: true })
personID?: string;
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { LivingSpaceService } from './living-space.service';
import { LivingSpaceResolver } from './living-space.resolver';
import { MongooseModule } from '@nestjs/mongoose';
import { LivingSpaces, LivingSpacesSchema } from '@/models/living-spaces.model';
@Module({
imports: [MongooseModule.forFeature([{ name: LivingSpaces.name, schema: LivingSpacesSchema }])],
providers: [LivingSpaceService, LivingSpaceResolver]
})
export class LivingSpaceModule { }

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LivingSpaceResolver } from './living-space.resolver';
describe('LivingSpaceResolver', () => {
let resolver: LivingSpaceResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LivingSpaceResolver],
}).compile();
resolver = module.get<LivingSpaceResolver>(LivingSpaceResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@@ -0,0 +1,37 @@
import { Resolver, Args, ID, Info, Mutation, Query } from '@nestjs/graphql';
import { Types } from 'mongoose';
import { ListArguments } from '@/dto/list.input';
import { LivingSpaces } from '@/models/living-spaces.model';
import { ListLivingSpaceResponse } from './dto/list-living-space.response';
import { CreateLivingSpaceInput } from './dto/create-living-space.input';
import { UpdateLivingSpaceInput } from './dto/update-living-space.input';
import { LivingSpaceService } from './living-space.service';
import type { GraphQLResolveInfo } from 'graphql';
import graphqlFields from 'graphql-fields';
@Resolver()
export class LivingSpaceResolver {
constructor(private readonly livingSpaceService: LivingSpaceService) { }
@Query(() => ListLivingSpaceResponse, { name: 'getLivingSpaces' })
async getLivingSpaces(@Info() info: GraphQLResolveInfo, @Args('input') input: ListArguments): Promise<ListLivingSpaceResponse> {
const fields = graphqlFields(info); const projection = this.livingSpaceService.buildProjection(fields?.data); const { skip, limit, sort, filters } = input;
return await this.livingSpaceService.findAll(projection, skip, limit, sort, filters);
}
@Query(() => LivingSpaces, { name: 'getLivingSpace', nullable: true })
async getLivingSpace(@Args('id', { type: () => ID }) id: string, @Info() info: GraphQLResolveInfo): Promise<LivingSpaces | null> {
const fields = graphqlFields(info); const projection = this.livingSpaceService.buildProjection(fields); return this.livingSpaceService.findById(new Types.ObjectId(id), projection);
}
@Mutation(() => LivingSpaces, { name: 'createLivingSpace' })
async createLivingSpace(@Args('input') input: CreateLivingSpaceInput): Promise<LivingSpaces> { return this.livingSpaceService.create(input) }
@Mutation(() => LivingSpaces, { name: 'updateLivingSpace' })
async updateLivingSpace(@Args('uuid') uuid: string, @Args('input') input: UpdateLivingSpaceInput): Promise<LivingSpaces> { return this.livingSpaceService.update(uuid, input) }
@Mutation(() => Boolean, { name: 'deleteLivingSpace' })
async deleteLivingSpace(@Args('uuid') uuid: string, @Args('collectionToken') collectionToken: string): Promise<boolean> { return this.livingSpaceService.delete(uuid, collectionToken) }
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LivingSpaceService } from './living-space.service';
describe('LivingSpaceService', () => {
let service: LivingSpaceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LivingSpaceService],
}).compile();
service = module.get<LivingSpaceService>(LivingSpaceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,74 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Types, Model, Connection } from 'mongoose';
import { getDynamicLivingSpaceModel, LivingSpaces, LivingSpacesDocument } from '@/models/living-spaces.model';
import { ListLivingSpaceResponse } from './dto/list-living-space.response';
import { CreateLivingSpaceInput } from './dto/create-living-space.input';
import { UpdateLivingSpaceInput } from './dto/update-living-space.input';
import { InjectConnection } from '@nestjs/mongoose';
interface UpdateInput {
build?: Types.ObjectId;
collectionToken?: string;
userType?: Types.ObjectId;
part?: Types.ObjectId;
company?: Types.ObjectId;
person?: Types.ObjectId;
expiryStarts?: Date;
expiryEnds?: Date;
}
@Injectable()
export class LivingSpaceService {
constructor(
@InjectModel(LivingSpaces.name) private readonly livingSpaceModel: Model<LivingSpacesDocument>,
@InjectConnection() private readonly connection: Connection
) { }
async findAll(projection: any, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListLivingSpaceResponse> {
if (!filters?.collectionToken) { throw new Error('Collection token is required') }
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
const totalCount = await this.livingSpaceModel.countDocuments(query).exec();
const data = await this.livingSpaceModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec();
return { data, totalCount };
}
async findById(id: Types.ObjectId, projection?: any): Promise<LivingSpacesDocument | null> {
if (!projection?.collectionToken) { throw new Error('Collection token is required') }
const selectedModel = getDynamicLivingSpaceModel(projection.collectionToken, this.connection);
return selectedModel.findById(id, projection, { lean: false }).populate({ path: 'company', select: projection?.company }).exec();
}
async create(input: CreateLivingSpaceInput): Promise<LivingSpacesDocument> {
if (!input.collectionToken) { throw new Error('Collection token is required') }
const LivingSpaceModel = getDynamicLivingSpaceModel(input.collectionToken, this.connection);
const docInput: Partial<LivingSpacesDocument> = {
build: new Types.ObjectId(input.buildID), part: new Types.ObjectId(input.partID), userType: new Types.ObjectId(input.userTypeID),
company: new Types.ObjectId(input.companyID), person: new Types.ObjectId(input.personID),
expiryStarts: input.expiryStarts ? new Date(input.expiryStarts) : new Date(), expiryEnds: input.expiryEnds ? new Date(input.expiryEnds) : new Date('2099-12-31'),
}; const doc = new LivingSpaceModel(docInput); return await doc.save()
}
async update(uuid: string, input: UpdateLivingSpaceInput): Promise<LivingSpacesDocument> {
if (!input.collectionToken) { throw new Error('Collection token is required') }
const selectedModel = getDynamicLivingSpaceModel(input.collectionToken, this.connection);
const newInput: UpdateInput = {}
if (input?.buildID) { newInput.build = new Types.ObjectId(input.buildID) }; if (input?.userTypeID) { newInput.userType = new Types.ObjectId(input.userTypeID) }
if (input?.partID) { newInput.part = new Types.ObjectId(input.partID) }; if (input?.companyID) { newInput.company = new Types.ObjectId(input.companyID) }
if (input?.personID) { newInput.person = new Types.ObjectId(input.personID) };
if (input?.expiryStarts) { newInput.expiryStarts = input.expiryStarts }; if (input?.expiryEnds) { newInput.expiryEnds = input.expiryEnds }
const company = await selectedModel.findOne({ uuid }); if (!company) { throw new Error('Company not found') }; company.set(newInput); return company.save();
}
async delete(uuid: string, collectionToken: string): Promise<boolean> { if (!collectionToken) { throw new Error('Collection token is required') } const selectedModel = getDynamicLivingSpaceModel(collectionToken, this.connection); const company = await selectedModel.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;
}
}

View File

@@ -90,8 +90,6 @@ export class CreatedBase {
}
@ObjectType({ isAbstract: true })
export class ExpiryBase {

View File

@@ -10,9 +10,61 @@ export class Company extends Base {
@Field(() => ID)
readonly _id: string;
@Field(() => ID)
@Field()
@Prop({ required: true })
name: string;
formal_name: string;
@Field()
@Prop({ required: true })
company_type: string;
@Field()
@Prop({ required: true })
commercial_type: string;
@Field()
@Prop({ required: true })
tax_no: string;
@Field()
@Prop({ required: true })
public_name: string;
@Field()
@Prop({ required: true })
company_tag: string;
@Field()
@Prop({ required: true, default: 'TR' })
default_lang_type: string;
@Field()
@Prop({ required: true, default: 'TL' })
default_money_type: string;
@Field()
@Prop({ required: true, default: false })
is_commercial: boolean;
@Field()
@Prop({ required: true, default: false })
is_blacklist: boolean;
@Field()
@Prop({ required: false })
parent_id: string;
@Field()
@Prop({ required: false })
workplace_no: string;
@Field()
@Prop({ required: false })
official_address: string;
@Field()
@Prop({ required: false })
top_responsible_company: string;
}

View File

@@ -1,6 +1,5 @@
import { Document, Model, Types, Connection, connection as defaultConnection } from 'mongoose';
import { randomUUID } from 'crypto';
import { Prop, SchemaFactory } from '@nestjs/mongoose';
import { Document, Model, Types, Connection } from 'mongoose';
import { Prop, SchemaFactory, Schema } from '@nestjs/mongoose';
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Base } from '@/models/base.model';
import { Build } from '@/models/build.model';
@@ -9,9 +8,14 @@ import { Person } from '@/models/person.model';
import { Company } from '@/models/company.model';
import { UserType } from '@/models/user-type.model';
@ObjectType()
@Schema({ timestamps: true })
export class LivingSpaces extends Base {
@Field(() => ID)
readonly _id: string
@Field(() => ID)
@Prop({ type: Types.ObjectId, ref: Build.name, required: true })
build: Types.ObjectId;
@@ -37,9 +41,9 @@ export class LivingSpaces extends Base {
export type LivingSpacesDocument = LivingSpaces & Document;
export const LivingSpacesSchema = SchemaFactory.createForClass(LivingSpaces);
export function getDynamicLivingSpaceModel(collectionToken: string, conn?: Connection): Model<LivingSpacesDocument> {
const connection = conn || defaultConnection;
export function getDynamicLivingSpaceModel(collectionToken: string, connection: Connection): Model<LivingSpacesDocument> {
const collectionName = `LivingSpaces${collectionToken}`;
const schema = SchemaFactory.createForClass(LivingSpaces);
if (connection.models[collectionName]) { return connection.models[collectionName] as Model<LivingSpacesDocument> }
throw new Error('No Living Spaces is found')
return connection.model(collectionName, schema, collectionName) as unknown as Model<LivingSpacesDocument>;
}