This commit is contained in:
Marshal
2026-07-20 10:52:20 +00:00
committed by Hagernesh
parent 0727de0f4d
commit 540f4a1ff6
7 changed files with 64 additions and 23 deletions

View File

@@ -374,23 +374,18 @@ export class ContractsService {
if (companyProfileId) {
// Business-license files are FileRecords (resource "company_profiles");
// carry the live ones by reference. Staged/pending uploads are excluded by
// code. Codes are slugged from each document name so they group under
// "Profile documents" on the contract detail page.
// code. The `business_license` prefix is preserved so the portal groups
// them under "Business license" instead of the clearance catch-all — the
// index suffix keeps multiple licences distinct.
const records = await this.filesService.findByResource(
companyProfileId,
'company_profiles',
);
const slug = (name: string) =>
name
.toLowerCase()
.replace(/\.[a-z0-9]+$/, '')
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '') || 'profile_document';
records
.filter((r) => r.code === 'business_license')
.forEach((r, i) => {
const code = `${slug(r.name)}_${i + 1}`;
const code = `business_license_${i + 1}`;
if (existingCodes.has(code)) return;
docs.push({
code,

View File

@@ -3444,19 +3444,22 @@ export class BookingBatchService implements OnModuleInit {
/**
* Physical wagons marshalled in the schedule's built train, or null when the
* schedule has no built train (or the consist is still empty) and the legacy
* locomotive-derived capacity must apply. This count is what caps a built
* train's bookings: 50 wagons coupled → 50 wagon slots, no more.
* schedule has NO built train and the legacy locomotive-derived capacity must
* apply. This count is what caps a built train's bookings: 50 wagons coupled
* → 50 wagon slots, no more.
*
* A built train with an EMPTY consist returns 0, NOT null: zero coupled
* wagons means zero capacity. Folding that case into null used to hand an
* un-consisted train the abstract locomotive budget, so an empty train
* advertised its full maxWagons as free space and accepted bookings the
* allocator could never place.
*/
private async builtTrainWagonCount(
schedule: TrainSchedule,
): Promise<number | null> {
const trainId = schedule.trainSet?.train?.id;
if (!trainId) return null;
const count = await this.dataSource
.getRepository(Wagon)
.count({ where: { trainId } });
return count > 0 ? count : null;
return this.dataSource.getRepository(Wagon).count({ where: { trainId } });
}
/**

View File

@@ -7131,9 +7131,19 @@ export class TrainSchedulingService {
await this.dataSource.transaction(async (manager) => {
await manager.getRepository(TrainSetWagon).delete(trainSetWagonId);
// Recount from the slot rows rather than decrementing the cached counter.
// A blind `wagonCount - 1` desyncs the moment two removals race or the
// in-memory schedule graph is stale, and the counter is what the schedule
// capacity math reads.
const remaining = await manager.getRepository(TrainSetWagon).find({
where: { trainSetId: schedule.trainSetId },
select: { id: true, lengthMeters: true },
});
await manager.getRepository(TrainSet).update(schedule.trainSetId, {
wagonCount: Math.max(0, (schedule.trainSet?.wagonCount ?? 0) - 1),
totalLengthMeters: Math.max(0, (schedule.trainSet?.totalLengthMeters ?? 0) - (wagon.lengthMeters ?? 0)),
wagonCount: remaining.length,
totalLengthMeters: roundTons(
remaining.reduce((sum, w) => sum + (Number(w.lengthMeters) || 0), 0),
),
});
});