feat: yard scoping to position

This commit is contained in:
Nathnael
2026-08-18 12:55:11 +00:00
parent 983cc02e50
commit 6823a32fee
27 changed files with 2657 additions and 257 deletions

View File

@@ -38,15 +38,21 @@ export class WarehouseInventoryController {
@Get()
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
@ApiOperation({ summary: 'List warehouse inventory' })
findAll(@Query() filter: FilterWarehouseInventoryDto) {
return this.inventoryService.findAll(filter);
findAll(
@Query() filter: FilterWarehouseInventoryDto,
@CurrentUser() user: TCurrentUser,
) {
return this.inventoryService.findAll(filter, user);
}
@Get('ready-for-loading')
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
@ApiOperation({ summary: 'List inventory ready for loading' })
findReadyForLoading(@Query() filter: FilterWarehouseInventoryDto) {
return this.inventoryService.findReadyForLoading(filter);
findReadyForLoading(
@Query() filter: FilterWarehouseInventoryDto,
@CurrentUser() user: TCurrentUser,
) {
return this.inventoryService.findReadyForLoading(filter, user);
}
@Get('inquiry')

View File

@@ -39,6 +39,7 @@ import {
import { SignaturesService } from '../signatures/signatures.service';
import { StampSettingsService } from '../stamp-settings/stamp-settings.service';
import { LogoSettingsService } from '../logo-settings/logo-settings.service';
import { YardScopeService } from '../rule-engine/services/yard-scope.service';
import { logoImageCss, logoMarkup } from '../billing/documents/logo-markup.util';
import { sealClass, sealImageCss, sealMarkup } from '../billing/documents/seal-markup.util';
import { BulkInspectDto } from './dto/bulk-inspect.dto';
@@ -423,6 +424,7 @@ export class WarehouseInventoryService {
private readonly events: EventEmitter2,
private readonly stampSettings: StampSettingsService,
private readonly logoSettings: LogoSettingsService,
private readonly yardScope: YardScopeService,
) {}
/**
@@ -978,7 +980,16 @@ export class WarehouseInventoryService {
// ── Listing ────────────────────────────────────────────────────────────
async findAll(filter: FilterWarehouseInventoryDto): Promise<WarehouseInventory[]> {
/**
* `user` drives yard access scoping: a desk mapped to yards sees only those
* yards' inventory. Optional so internal callers that are not serving a
* request (schedulers, other services) are unaffected — they pass nothing and
* get the unscoped list, which is what they had before.
*/
async findAll(
filter: FilterWarehouseInventoryDto,
user?: unknown,
): Promise<WarehouseInventory[]> {
const createdAt =
filter.dateFrom && filter.dateTo
? Between(new Date(filter.dateFrom), new Date(filter.dateTo))
@@ -1020,6 +1031,32 @@ export class WarehouseInventoryService {
});
}
// Yard scoping — applied to `base` before the search branch splits it, so
// both OR arms carry the constraint. A null result means "do not narrow".
//
// Scoped on `warehouse.stationId`, NOT on `inventory.yardId`: those are two
// different id spaces that share a name. `warehouse_inventory.yard_id` is a
// FK to `warehouse_yards` — a yard INSIDE a warehouse — while the desk↔yard
// mapping is against `freight.yards`, the network yard, which inventory
// reaches through `warehouses.station_id`. Filtering `yardId` against
// mapped network yards matches nothing and hides every row (observed: all
// 34 rows disappeared before this was corrected).
//
// `filter.yardId` is likewise a warehouse-yard id, so it is NOT passed as
// the requested yard here; `filter.facilityId` is the station-yard filter.
const scopedYardIds = await this.yardScope.listFilterYardIds(
user as never,
filter.facilityId,
'warehouse-inventory list',
);
if (scopedYardIds) {
if (!scopedYardIds.length) return [];
base.warehouse = {
...((base.warehouse as FindOptionsWhere<Warehouse>) ?? {}),
stationId: scopedYardIds.length === 1 ? scopedYardIds[0] : In(scopedYardIds),
};
}
const search = filter.search?.trim();
const where: FindManyOptions<WarehouseInventory>['where'] = search
? [
@@ -1037,8 +1074,11 @@ export class WarehouseInventoryService {
return items;
}
findReadyForLoading(filter: FilterWarehouseInventoryDto): Promise<WarehouseInventory[]> {
return this.findAll({ ...filter, status: 'READY_FOR_LOADING' });
findReadyForLoading(
filter: FilterWarehouseInventoryDto,
user?: unknown,
): Promise<WarehouseInventory[]> {
return this.findAll({ ...filter, status: 'READY_FOR_LOADING' }, user);
}
/**