Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions dbschema/migrations/00021-m1ud7t6.edgeql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions dbschema/partner.gel
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ module default {
multi languagesOfConsulting: Language;
multi fieldRegions: FieldRegion;
multi countries: Location;
multi strategicAlliances: Partner {
constraint exclusive;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means a partner can only be apart of one strategic alliance.
Are these alliances supposed to bi-directional? I think so.
Right now it is only unidirectional. "SC has an alliance with Wycliffe, but Wycliffe does not have an alliance with SC." That doesn't sound right.
"SC and Wycliffe have an alliance with each other" sounds more correct.
If that's true the implementation here would be different to accommodate.

Also are the alliances transient? A <-> B & B <-> C, meaning additionally A <-> C?

};

parent: Partner {
constraint exclusive;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that a partner can only a parent to one other partner, which is not what you what. Multiple partners can share a single parent partner.

Suggested change
constraint exclusive;

};

startDate: cal::local_date;

Expand Down
8 changes: 8 additions & 0 deletions src/components/partner/dto/create-partner.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ export abstract class CreatePartner {
@IdField({ nullable: true })
readonly languageOfWiderCommunicationId?: ID<'Language'> | null;

@IdField({ nullable: true })
readonly parentId?: ID<'Partner'> | null;

@Field(() => [IDType], { nullable: true })
@IsId({ each: true })
@Transform(({ value }) => uniq(value))
readonly strategicAlliances?: ReadonlyArray<ID<'Partner'>> = [];

@Field(() => [IDType], { nullable: true })
@IsId({ each: true })
@Transform(({ value }) => uniq(value))
Expand Down
11 changes: 11 additions & 0 deletions src/components/partner/dto/partner.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SecuredBoolean,
SecuredDateNullable,
SecuredProperty,
SecuredPropertyList,
SecuredStringNullable,
Sensitivity,
SensitivityField,
Expand Down Expand Up @@ -66,6 +67,9 @@ export class Partner extends Interfaces {
>;

readonly countries: Required<Secured<ReadonlyArray<LinkTo<'Location'>>>>;
readonly strategicAlliances: Required<
Secured<ReadonlyArray<LinkTo<'Partner'>>>
>;

readonly languagesOfConsulting: Required<
Secured<ReadonlyArray<LinkTo<'Language'>>>
Expand All @@ -87,13 +91,20 @@ export class Partner extends Interfaces {

@Field()
readonly departmentIdBlock: SecuredFinanceDepartmentIdBlockNullable;

readonly parent: Secured<LinkTo<'Partner'> | null>;
}

@ObjectType({
description: SecuredProperty.descriptionFor('a partner'),
})
export class SecuredPartner extends SecuredProperty(Partner) {}

@ObjectType({
description: SecuredPropertyList.descriptionFor('a list of partners'),
})
export class SecuredPartners extends SecuredPropertyList(Partner) {}

declare module '~/core/resources/map' {
interface ResourceMap {
Partner: typeof Partner;
Expand Down
7 changes: 7 additions & 0 deletions src/components/partner/dto/update-partner.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export abstract class UpdatePartner {
@IdField({ nullable: true })
readonly languageOfWiderCommunicationId?: ID<'Language'> | null;

@IdField({ nullable: true })
readonly parentId?: ID<'Partner'> | null;

@ListField(() => IDType, { optional: true })
@IsId({ each: true })
readonly strategicAlliances?: ReadonlyArray<ID<'Partner'>>;

@ListField(() => IDType, { optional: true })
@IsId({ each: true })
readonly countries?: ReadonlyArray<ID<'Location'>>;
Expand Down
2 changes: 2 additions & 0 deletions src/components/partner/partner.gel.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class PartnerGelRepository
countries: true,
languagesOfConsulting: true,
departmentIdBlock: departmentIdBlock.hydrate,
strategicAlliances: true,
parent: true,
}),
omit: ['create', 'update'],
})
Expand Down
56 changes: 56 additions & 0 deletions src/components/partner/partner.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export class PartnerRepository extends DtoRepository(Partner) {
fieldRegions: ['FieldRegion', input.fieldRegions],
countries: ['Location', input.countries],
languagesOfConsulting: ['Language', input.languagesOfConsulting],
strategicAlliances: ['Partner', input.strategicAlliances],
parent: ['Partner', input.parentId],
}),
)
.apply(departmentIdBlockUtils.createMaybe(input.departmentIdBlock))
Expand All @@ -118,11 +120,23 @@ export class PartnerRepository extends DtoRepository(Partner) {
countries,
languagesOfConsulting,
departmentIdBlock,
strategicAlliances,
parentId,
...simpleChanges
} = changes;

await this.updateProperties({ id }, simpleChanges);

if (parentId !== undefined) {
if (parentId === id) {
throw new InputException(
'A partner cannot be its own parent organization',
'partner.parent',
);
}
Comment on lines +131 to +136
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already done in the service

Suggested change
if (parentId === id) {
throw new InputException(
'A partner cannot be its own parent organization',
'partner.parent',
);
}

await this.updateRelation('parent', 'Partner', changes.id, parentId);
}

if (pointOfContactId !== undefined) {
await this.updateRelation(
'pointOfContact',
Expand Down Expand Up @@ -169,6 +183,26 @@ export class PartnerRepository extends DtoRepository(Partner) {
}
}

if (strategicAlliances) {
if (strategicAlliances.includes(changes.id)) {
throw new InputException(
'A partner cannot be its own strategic ally',
'partner.strategicAlliances',
);
}
Comment on lines +187 to +192
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (strategicAlliances.includes(changes.id)) {
throw new InputException(
'A partner cannot be its own strategic ally',
'partner.strategicAlliances',
);
}

try {
await this.updateRelationList({
id: changes.id,
relation: 'strategicAlliances',
newList: strategicAlliances,
});
} catch (e) {
throw e instanceof InputException
? e.withField('partner.strategicAlliances')
: e;
}
}

if (languagesOfConsulting) {
try {
await this.updateRelationList({
Expand Down Expand Up @@ -257,6 +291,26 @@ export class PartnerRepository extends DtoRepository(Partner) {
),
),
)
.subQuery('node', (sub) =>
sub
.match([
node('node'),
relation('out', '', 'strategicAlliances'),
node('strategicAlliances', 'Partner'),
])
.return(
collect('strategicAlliances { .id }').as('strategicAlliances'),
),
)
.subQuery('node', (sub) =>
sub
.optionalMatch([
node('node'),
relation('out', '', 'parent', ACTIVE),
node('parent', 'Partner'),
])
.return('parent { .id } as parent'),
)
Comment on lines +305 to +313
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.subQuery('node', (sub) =>
sub
.optionalMatch([
node('node'),
relation('out', '', 'parent', ACTIVE),
node('parent', 'Partner'),
])
.return('parent { .id } as parent'),
)
.optionalMatch([
node('node'),
relation('out', '', 'parent', ACTIVE),
node('parent', 'Partner'),
])

.apply(matchProps())
.optionalMatch([
node('node'),
Expand Down Expand Up @@ -288,6 +342,8 @@ export class PartnerRepository extends DtoRepository(Partner) {
departmentIdBlock: 'departmentIdBlock',
scope: 'scopedRoles',
pinned,
parent: 'parent { .id }',
strategicAlliances: 'strategicAlliances',
}).as('dto'),
);
}
Expand Down
21 changes: 21 additions & 0 deletions src/components/partner/partner.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {
Partner,
PartnerListInput,
PartnerListOutput,
SecuredPartner,
SecuredPartners,
UpdatePartnerInput,
UpdatePartnerOutput,
} from './dto';
Expand All @@ -60,6 +62,25 @@ export class PartnerResolver {
return await partners.load(id);
}

@ResolveField(() => SecuredPartner)
async parent(
@Parent() partner: Partner,
@Loader(PartnerLoader) partners: LoaderOf<PartnerLoader>,
): Promise<SecuredPartner> {
return await mapSecuredValue(partner.parent, ({ id }) => partners.load(id));
}

@ResolveField(() => SecuredPartners)
async strategicAlliances(
@Parent() partner: Partner,
@Loader(PartnerLoader) loader: LoaderOf<PartnerLoader>,
): Promise<SecuredPartners> {
return await loadSecuredIds(loader, {
...partner.strategicAlliances,
value: partner.strategicAlliances.value?.map((alliance) => alliance.id),
});
}

@Query(() => PartnerListOutput, {
description: 'Look up partners',
})
Expand Down
13 changes: 13 additions & 0 deletions src/components/partner/partner.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ export class PartnerService {
};
}

if (input.strategicAlliances?.includes(input.id)) {
throw new InputException(
'A partner cannot be its own strategic ally',
'partner.strategicAlliances',
);
}
if (input.parentId && input.parentId === input.id) {
throw new InputException(
'A partner cannot be its own parent organization',
'partner.parent',
);
}
Comment on lines +114 to +125
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically these constraints should apply on create() as well.


const { departmentIdBlock, ...simpleInput } = input;
const simpleChanges = this.repo.getActualChanges(partner, simpleInput);
const changes = {
Expand Down