From ef4485115d02c54c62e5c881ce10a05c88fbe6de Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sat, 14 Mar 2026 09:31:47 +0100 Subject: [PATCH] pdf --- api/src/model/dto/key-handover.dto.ts | 26 +++++----- api/src/model/entitites/index.ts | 1 + .../entitites/key-handout-pdf-data.entity.ts | 52 +++++++++++++++++++ api/src/model/entitites/key-handout.entity.ts | 6 ++- api/src/model/repositories/index.ts | 1 + .../key-handout-pdf-data.repository.ts | 10 ++++ api/src/modules/key/key.controller.ts | 5 +- api/src/modules/key/key.service.ts | 43 +++++++++++---- api/src/shared/database/database.module.ts | 8 ++- .../storage/services/pdf/pdf.service.ts | 16 +++--- api/src/shared/storage/storage.module.ts | 3 +- .../create-handover-pdf-dialog.component.html | 5 ++ .../create-handover-pdf-dialog.component.ts | 3 +- .../handover-dialog.component.ts | 11 ++-- client/src/styles.scss | 15 ++++++ 15 files changed, 157 insertions(+), 48 deletions(-) create mode 100644 api/src/model/entitites/key-handout-pdf-data.entity.ts create mode 100644 api/src/model/repositories/key-handout-pdf-data.repository.ts diff --git a/api/src/model/dto/key-handover.dto.ts b/api/src/model/dto/key-handover.dto.ts index ce48ce0..d8bc94a 100644 --- a/api/src/model/dto/key-handover.dto.ts +++ b/api/src/model/dto/key-handover.dto.ts @@ -1,17 +1,17 @@ -export class KeyHandoverPDFDataDto { - handoverId!: string; - handoverDate!: Date; - place!: string; +export class KeyHandoutPDFDataDto { + handoverId: string; + handoverDate: Date; + place: string; - giverName!: string; - giverAddress?: string; + giverName: string; + giverAddress: string; - receiverName!: string; - receiverAddress?: string; + receiverName: string; + receiverAddress: string; - keyType!: string; - keyNumber?: string; - quantity!: number; - objectDescription?: string; - notes?: string; + keyType: string; + keyNumber: string; + quantity: number; + objectDescription: string; + notes: string; } \ No newline at end of file diff --git a/api/src/model/entitites/index.ts b/api/src/model/entitites/index.ts index 185ff2a..00df338 100644 --- a/api/src/model/entitites/index.ts +++ b/api/src/model/entitites/index.ts @@ -5,3 +5,4 @@ export * from './customer.entity'; export * from './key-handout.entity'; export * from './activity.entity'; export * from './user'; +export * from './key-handout-pdf-data.entity'; diff --git a/api/src/model/entitites/key-handout-pdf-data.entity.ts b/api/src/model/entitites/key-handout-pdf-data.entity.ts new file mode 100644 index 0000000..e470be8 --- /dev/null +++ b/api/src/model/entitites/key-handout-pdf-data.entity.ts @@ -0,0 +1,52 @@ +import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; +import { KeyHandout } from "./key-handout.entity"; + +@Entity() +export class KeyHandoutPdfDataEntity { + + + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => KeyHandout, handout => handout.pdfs) + handout: KeyHandout + + @Column({ type: 'date' }) + handoverDate: Date; + + @Column({ name: 'giver_name' }) + giverName: string; + + @Column({ name: 'giver_address', nullable: true }) + giverAddress: string; + + @Column({ name: 'receiver_name' }) + receiverName: string; + + @Column({ name: 'receiver_address', nullable: true }) + receiverAddress: string; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @Column({ name: 'file_name', nullable: true }) + fileName: string; + + @Column({ name: 'key_nr' }) + keyNumber: string; + + @Column({ type: 'int', default: 1 }) + quantity: number; + + @Column({ name: 'object_description', nullable: true }) + objectDescription: string; + + @Column({ nullable: true }) + notes: string; + + @UpdateDateColumn({ name: 'updatet_at' }) + updatedAt: Date; + + + +} \ No newline at end of file diff --git a/api/src/model/entitites/key-handout.entity.ts b/api/src/model/entitites/key-handout.entity.ts index ec6e7c2..9d3acbf 100644 --- a/api/src/model/entitites/key-handout.entity.ts +++ b/api/src/model/entitites/key-handout.entity.ts @@ -4,11 +4,13 @@ import { CreateDateColumn, Entity, ManyToOne, + OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; import { Key } from './key.entity'; import { Customer } from './customer.entity'; import { User } from './user'; +import { KeyHandoutPdfDataEntity } from './key-handout-pdf-data.entity'; @Entity() export class KeyHandout { @@ -34,8 +36,8 @@ export class KeyHandout { user: User; - @Column({ nullable: true }) - pdfFormKey: string; + @OneToMany(() => KeyHandoutPdfDataEntity, pdf => pdf.handout) + pdfs: KeyHandoutPdfDataEntity[]; @BeforeInsert() insertTimestamp() { diff --git a/api/src/model/repositories/index.ts b/api/src/model/repositories/index.ts index 11bc00a..09b8ab2 100644 --- a/api/src/model/repositories/index.ts +++ b/api/src/model/repositories/index.ts @@ -7,3 +7,4 @@ export * from './key.repository'; export * from './customer.repository'; export * from './activity.repository'; export * from './user.settings.repository'; +export * from './key-handout-pdf-data.repository'; diff --git a/api/src/model/repositories/key-handout-pdf-data.repository.ts b/api/src/model/repositories/key-handout-pdf-data.repository.ts new file mode 100644 index 0000000..373685e --- /dev/null +++ b/api/src/model/repositories/key-handout-pdf-data.repository.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { Repository, DataSource } from 'typeorm'; +import { KeyHandoutPdfDataEntity } from '../entitites'; + +@Injectable() +export class KeyHandoutPdfDataEntityRepository extends Repository { + constructor(dataSource: DataSource) { + super(KeyHandoutPdfDataEntity, dataSource.createEntityManager()); + } +} diff --git a/api/src/modules/key/key.controller.ts b/api/src/modules/key/key.controller.ts index e643868..f5b7da1 100644 --- a/api/src/modules/key/key.controller.ts +++ b/api/src/modules/key/key.controller.ts @@ -8,7 +8,6 @@ import { Put, Req, Res, - Sse, UseGuards, } from '@nestjs/common'; import { Response } from 'express'; @@ -16,7 +15,7 @@ import { KeyService } from './key.service'; import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface'; import { AuthGuard } from 'src/core/guards/auth.guard'; import { Key } from 'src/model/entitites'; -import { KeyHandoverPDFDataDto } from 'src/model/dto/key-handover.dto'; +import { KeyHandoutPDFDataDto } from 'src/model/dto/key-handover.dto'; @UseGuards(AuthGuard) @@ -91,7 +90,7 @@ export class KeyController { } @Post('pdf') - async generatePdf(@Body() dto: KeyHandoverPDFDataDto, @Res() res: Response) { + async generatePdf(@Body() dto: KeyHandoutPDFDataDto, @Res() res: Response) { const { pdf, response } = await this.service.createPdf(dto); res.setHeader( diff --git a/api/src/modules/key/key.service.ts b/api/src/modules/key/key.service.ts index b570379..177ebd1 100644 --- a/api/src/modules/key/key.service.ts +++ b/api/src/modules/key/key.service.ts @@ -2,8 +2,8 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { Customer, Cylinder, Key, User } from 'src/model/entitites'; import { CylinderRepository, + KeyHandoutPdfDataEntityRepository, KeyRepository, - KeySystemRepository, } from 'src/model/repositories'; import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository'; import { ActivityHelperService } from 'src/shared/service/activity.logger.service'; @@ -12,9 +12,7 @@ import { FindOperator, IsNull, Not } from 'typeorm'; import { ConfigService } from '@nestjs/config'; import { MailService } from '../mail/mail.service'; import { SseService } from '../realtime/sse/sse.service'; -import { KeyHandoverPDFDataDto } from 'src/model/dto/key-handover.dto'; -import { firstValueFrom } from 'rxjs'; -import { HttpService } from '@nestjs/axios'; +import { KeyHandoutPDFDataDto } from 'src/model/dto/key-handover.dto'; import { PdfService } from 'src/shared/storage/services/pdf/pdf.service'; import { AxiosResponse } from 'axios'; @@ -29,8 +27,11 @@ export class KeyService { private readonly configService: ConfigService, private readonly mailService: MailService, private readonly sseService: SseService, - private readonly pdfService: PdfService - ) { } + private readonly pdfService: PdfService, + private readonly handoutPDFRepo: KeyHandoutPdfDataEntityRepository + ) { + // this.getLatestHandoverPDF('a4f4f32b-96de-4f20-b5bd-ac3a128da121') + } get isDevelopMode(): boolean { return (this.configService.get('DEVELOP_MODE') || '').toLowerCase() == 'true'; @@ -168,7 +169,7 @@ export class KeyService { timestamp: { direction: 'DESC' }, created: { direction: 'DESC' }, }, - relations: ['customer'], + relations: ['customer', 'pdfs'], }); } @@ -234,12 +235,34 @@ export class KeyService { return k; } - async createPdf(dto: KeyHandoverPDFDataDto): Promise<{ pdf: Buffer, response: AxiosResponse}> { - const x = await this.pdfService.generatePDF(dto) - return x; + async createPdf(dto: KeyHandoutPDFDataDto): Promise<{ pdf: Buffer, response: AxiosResponse}> { + const handout = await this.handoverRepo.findOneByOrFail({ id: dto.handoverId }) + const data = { + ...dto, + handout + } + + data.handoverDate = new Date(data.handoverDate); + + const entity = await this.handoutPDFRepo.save(this.handoutPDFRepo.create(data)); + + const fileName = await this.pdfService.generatePDF(dto); + entity.createdAt = new Date(); + entity.fileName = fileName; + await this.handoutPDFRepo.save(entity); + const file = await this.pdfService.getPDFByHandoverKey(fileName); + return file; } async getPdf(handoverId: string): Promise<{ pdf: Buffer, response: AxiosResponse}> { return this.pdfService.getPDFByHandoverKey(handoverId); } + + async getLatestHandoverPDF(handoverId: string) { + const pdf = await this.handoutPDFRepo.findOne({ + where: { handout: { id: handoverId}}, + order: {createdAt: 'DESC' }, + }); + console.log(pdf) + } } diff --git a/api/src/shared/database/database.module.ts b/api/src/shared/database/database.module.ts index 220efbf..cd6a7cf 100644 --- a/api/src/shared/database/database.module.ts +++ b/api/src/shared/database/database.module.ts @@ -6,6 +6,7 @@ import { Cylinder, Key, KeyHandout, + KeyHandoutPdfDataEntity, Role, SSOUser, User, @@ -18,6 +19,7 @@ import { ActivityRepository, CustomerRepository, CylinderRepository, + KeyHandoutPdfDataEntityRepository, KeyRepository, KeySystemRepository, RoleRepository, @@ -41,7 +43,8 @@ const ENTITIES = [ Activity, EmailLog, UserSettings, - Impersonation + Impersonation, + KeyHandoutPdfDataEntity ]; const REPOSITORIES = [ UserRepository, @@ -55,7 +58,8 @@ const REPOSITORIES = [ ActivityRepository, EmailLogRepository, UserSettingsRepository, - ImpersonationRepository + ImpersonationRepository, + KeyHandoutPdfDataEntityRepository ]; @Module({ diff --git a/api/src/shared/storage/services/pdf/pdf.service.ts b/api/src/shared/storage/services/pdf/pdf.service.ts index c74adae..86c0d3f 100644 --- a/api/src/shared/storage/services/pdf/pdf.service.ts +++ b/api/src/shared/storage/services/pdf/pdf.service.ts @@ -1,9 +1,9 @@ import { HttpService } from '@nestjs/axios'; -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { AxiosResponse } from 'axios'; import { firstValueFrom } from 'rxjs'; -import { KeyHandoverPDFDataDto } from 'src/model/dto/key-handover.dto'; +import { KeyHandoutPDFDataDto } from 'src/model/dto/key-handover.dto'; import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository'; @Injectable() @@ -13,17 +13,13 @@ export class PdfService { constructor( private readonly configService: ConfigService, - private readonly httpService: HttpService, - private readonly handoverRepo: KeyHandoutRepository + private readonly httpService: HttpService ) { } - public async generatePDF(dto: KeyHandoverPDFDataDto): Promise<{ pdf: Buffer, response: AxiosResponse}> { - const h = await this.handoverRepo.findOneByOrFail({ id: dto.handoverId }); + public async generatePDF(dto: KeyHandoutPDFDataDto): Promise { const response = await this.post(`${this.STORAGESERVER}/pdf/keyhandover`, dto); - if (response?.data == null) { return; } - h.pdfFormKey = response.data; - await this.handoverRepo.save(h); - return this.getPDFByHandoverKey(response.data) + if (response?.data == null) { throw new HttpException('Konnte nicht erstellt werden', HttpStatus.INTERNAL_SERVER_ERROR) } + return response.data; } diff --git a/api/src/shared/storage/storage.module.ts b/api/src/shared/storage/storage.module.ts index 5011069..1d31b97 100644 --- a/api/src/shared/storage/storage.module.ts +++ b/api/src/shared/storage/storage.module.ts @@ -3,10 +3,9 @@ import { StorageService } from './storage.service'; import { HttpModule } from '@nestjs/axios'; import { ConfigModule } from '@nestjs/config'; import { PdfService } from './services/pdf/pdf.service'; -import { DatabaseModule } from '../database/database.module'; @Module({ - imports: [HttpModule, ConfigModule, DatabaseModule], + imports: [HttpModule, ConfigModule], providers: [StorageService, PdfService ], exports: [ StorageService, PdfService ] }) diff --git a/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.html b/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.html index dbe72eb..841280a 100644 --- a/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.html +++ b/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.html @@ -1,3 +1,8 @@ +@if(isLoading) { +
+ +
+}

Übergabeprotokoll

diff --git a/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.ts b/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.ts index 68cd2bc..8b85dc9 100644 --- a/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.ts +++ b/client/src/app/modules/keys/components/create-handover-pdf-dialog/create-handover-pdf-dialog.component.ts @@ -8,10 +8,11 @@ import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { IKeyHandoverPDF } from '../../../../model/interface/handover-pdf.interface'; +import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; @Component({ selector: 'app-create-handover-pdf-dialog', - imports: [MatDialogModule, FormsModule, ReactiveFormsModule, CommonModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule], + imports: [MatDialogModule, FormsModule, ReactiveFormsModule, CommonModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule, MatProgressSpinnerModule], templateUrl: './create-handover-pdf-dialog.component.html', styleUrl: './create-handover-pdf-dialog.component.scss', }) diff --git a/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts b/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts index 6f9cf8f..ebe3089 100644 --- a/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts +++ b/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts @@ -83,11 +83,11 @@ export class HandoverDialogComponent extends AgGridContainerComponent { { colId: 'download', headerName: 'PDF', - field: 'pdfFormKey', + field: 'pdfs', width: 60, tooltipValueGetter: () => 'Übergabeprotokoll herunterladen', cellRenderer: () => '', - cellClass: (data: any) => data.value ? 'download-pdf' : '', + cellClass: (data: any) => data.value.length ? 'download-pdf' : '', onCellClicked: (event) => { this.downloadHandoverPDF(event.value) }, @@ -232,9 +232,10 @@ export class HandoverDialogComponent extends AgGridContainerComponent { //this.downloadHandoverPDF('5b1f92d9-c66c-49ad-b654-b9a9a6a59752') } - downloadHandoverPDF(key: string) { - if (!key) { return; } - this.api.getHandoverPdf(key) + downloadHandoverPDF(pdfs: any[]) { + if (!pdfs || pdfs.length < 1) { return; } + + this.api.getHandoverPdf(pdfs[0]['fileName']) } } diff --git a/client/src/styles.scss b/client/src/styles.scss index 9dca9b8..a8e50b5 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -246,4 +246,19 @@ div.ag-row { display: flex; justify-content: space-between; gap: 12px; +} + +.loading-overlay { + position: absolute; + height: 100%; + width: 100%; + overflow: hidden; + padding: 0; + margin: 0; + box-sizing: border-box; + z-index: 2; + background: rgba(255, 255, 255, 0.5); + display: flex; + align-items: center; + justify-content: center; } \ No newline at end of file