per-user trade-direction access scope

This commit is contained in:
Marshal
2026-08-02 22:29:58 +00:00
parent c055abe8c1
commit f4fd469643
47 changed files with 1451 additions and 107 deletions

View File

@@ -59,6 +59,8 @@ import { GlOperationsService } from './gl-operations.service';
import { BookingRequestService } from './booking-request.service';
import { SignaturesService } from '../signatures/signatures.service';
import { BookingsService } from '../bookings/bookings.service';
import { scopedDirections } from '../user-trade-access/trade-scope.util';
import { UserTradeAccessService } from '../user-trade-access/user-trade-access.service';
import { CreateContractDto } from './dto/create-contract.dto';
import { UpdateContractDto } from './dto/update-contract.dto';
import { FilterContractDto } from './dto/filter-contract.dto';
@@ -108,6 +110,7 @@ export class ContractsController {
private readonly glOperationsService: GlOperationsService,
private readonly bookingRequestService: BookingRequestService,
private readonly signaturesService: SignaturesService,
private readonly userTradeAccessService: UserTradeAccessService,
private readonly bookingClearanceService: BookingClearanceService,
private readonly bookingsService: BookingsService,
) {}
@@ -210,7 +213,16 @@ export class ContractsController {
hasFreightPermission(user, FREIGHT_PERMS.bookings.view) ||
hasFreightPermission(user, FREIGHT_PERMS.contracts.view)
) {
return this.contractsService.findAll(filter);
// Per-user trade-direction scope (import/export/intercity checkboxes).
const allowed =
await this.userTradeAccessService.resolveAllowedDirections(user);
const dirs = scopedDirections(allowed, filter.tradeDirection);
return this.contractsService.findAll(
filter,
undefined,
undefined,
dirs ?? undefined,
);
}
const userId = user?.id;
if (!userId) throw new UnauthorizedException('Authentication required');

View File

@@ -1,4 +1,5 @@
import { Module, forwardRef } from '@nestjs/common';
import { UserTradeAccessModule } from '../user-trade-access/user-trade-access.module';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ExchangeModule, ExchangeOptions } from '@edr/api-common';
@@ -89,6 +90,7 @@ import { ContractDocumentViewModelBuilder } from '../../contracts/contract-docum
NotificationsModule,
NotificationInboxModule,
CompaniesModule,
UserTradeAccessModule,
// Provides the admin-editable contract document templates consumed by
// ContractDocumentViewModelBuilder when rendering contract PDFs.
ContractTemplatesModule,

View File

@@ -5,6 +5,7 @@ import { DataSource, In, IsNull, Repository, SelectQueryBuilder } from 'typeorm'
import { Booking } from '../bookings/entities/booking.entity';
import { FileRecord } from '../files/entities/file.entity';
import { applyDirectionScope } from '../user-trade-access/trade-scope.util';
import { Contract } from './entities/contract.entity';
import { ContractApprovalStep } from './entities/contract-approval-step.entity';
import { ContractClearanceCycle } from './entities/contract-clearance-cycle.entity';
@@ -38,6 +39,8 @@ export interface ContractListFilterOptions {
serviceTypeId?: string;
freightType?: string;
tradeDirection?: string;
/** Per-user trade-direction scope — `[]` matches nothing. */
tradeDirections?: string[];
paymentCurrency?: string;
customsClearingEnabled?: boolean;
/** true → only contracts with at least one uploaded clearance document. */
@@ -436,6 +439,9 @@ export class ContractsRepository extends BaseRepository<Contract> {
tradeDirection: options.tradeDirection,
});
}
if (options.tradeDirections) {
applyDirectionScope(qb, 'contract.trade_direction', options.tradeDirections);
}
if (options.paymentCurrency) {
qb.andWhere('contract.payment_currency = :paymentCurrency', {
paymentCurrency: options.paymentCurrency,

View File

@@ -748,6 +748,7 @@ export class ContractsService {
filter: FilterContractDto,
forceCompanyId?: string,
forceCompanyProfileId?: string,
tradeDirections?: string[],
): Promise<PaginatedContracts> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
@@ -763,6 +764,7 @@ export class ContractsService {
serviceTypeId: filter.serviceTypeId,
freightType: filter.freightType,
tradeDirection: filter.tradeDirection,
tradeDirections,
paymentCurrency: filter.paymentCurrency,
createdFrom: filter.createdFrom,
createdTo: filter.createdTo,