Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite Printing #128

Merged
merged 8 commits into from
Feb 18, 2025
Merged

Rewrite Printing #128

merged 8 commits into from
Feb 18, 2025

Conversation

david-loe
Copy link
Owner

@david-loe david-loe commented Feb 18, 2025

fixes #125

Summary by CodeRabbit

  • New Features

    • Unified PDF report generation now delivers consistent reports across travel, expense, and healthcare submissions.
    • Form labels have been updated to clearly indicate advances from the employer.
    • Localization enhancements provide clearer approval messages.
  • Refactor

    • Report printing has been streamlined with improved state logging for more robust tracking.
  • Chores

    • System configuration has been updated to version 1.5.3.

Copy link
Contributor

coderabbitai bot commented Feb 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This PR refactors and consolidates PDF report generation across multiple modules. Individual controller methods in expense, healthcare cost, and travel reports now use a unified reportPrinter.print method. Legacy PDF generation files have been removed, and a new ReportPrinter class is introduced in a dedicated printer.ts file. Logging capabilities are enhanced across models by adding a log field and a helper function logObject, with corresponding migrations and type updates. Localization strings and front-end labels have been adjusted to reflect these changes.

Changes

File(s) Change Summary
backend/controller/expenseReportController.ts
backend/controller/healthCareCostController.ts
backend/controller/travelController.ts
Replaced individual report generation functions (e.g., generateExpenseReportReport, generateHealthCareCostReport, generateTravelReport, generateAdvanceReport) with reportPrinter.print in various controller methods; updated method signatures accordingly.
backend/models/expenseReport.ts
backend/models/healthCareCost.ts
backend/models/travel.ts
backend/controller/types.ts
common/types.ts
Enhanced logging by adding a new log field (using logObject) in expense, healthcare cost, and travel models; updated interfaces to omit or include the log property and introduced new types for logging and PDF formatting.
backend/factory.ts
backend/migrations.ts
backend/models/settings.ts
Introduced a unified ReportPrinter instance with configuration options; added a migration step (writeLogFromHistory) for logging historical data; updated settings to invoke reportPrinter.setDistanceRefunds after saving.
backend/pdf/advance.ts
backend/pdf/expenseReport.ts
backend/pdf/healthCareCost.ts
backend/pdf/travel.ts
backend/pdf/helper.ts
backend/pdf/printer.ts
Deleted legacy PDF generation files and removed functions responsible for generating PDFs; updated PDF helper to use reportPrinter.print instead of multiple specialized functions; introduced a new printer.ts file that implements the ReportPrinter, ReportPrint, and PDFDrawer classes for unified PDF creation.
common/locales/de.json
common/locales/en.json
frontend/src/components/expenseReport/forms/ExpenseReportForm.vue
frontend/src/components/travel/forms/TravelApplyForm.vue
Updated localization entries and labels: added new labels (e.g., "advanceFromEmployer") and modified existing strings to improve clarity; front-end form labels now reference the updated localization keys.
backend/data/settings.json Bumped the version from "1.5.2" to "1.5.3".

Sequence Diagram(s)

sequenceDiagram
    participant User as Client
    participant Controller as Controller Method
    participant ReportPrinter as ReportPrinter Instance
    participant ReportPrint as Internal Report Generator
    participant PDFDrawer as PDF Drawing Engine
    participant Storage as Data Storage

    User->>Controller: Request report generation
    Controller->>ReportPrinter: Call print(report, language)
    ReportPrinter->>ReportPrint: Initialize report printing
    ReportPrint->>PDFDrawer: Render report elements
    ReportPrint->>Storage: Retrieve assets (document & logo)
    PDFDrawer-->>ReportPrint: Return rendered sections
    ReportPrinter-->>Controller: Return generated PDF
    Controller-->>User: Send PDF response
Loading

Possibly related PRs

  • Introduce System Settings #115: The changes in the main PR, which involve replacing the generateExpenseReportReport function with reportPrinter.print in the expenseReportController.ts, are related to the changes in the retrieved PR, where similar replacements occur in the healthCareCostController and other controllers, indicating a unified approach to report generation across multiple controllers.
  • Add Project Supervisors #100: The changes in the main PR, which focus on replacing the report generation method in the expenseReportController.ts, are related to the modifications in the retrieved PR that also involve updates to the expenseReportController.ts, specifically enhancing the getExpenseReport method to include project supervision checks. Both PRs modify the same controller file, indicating a direct connection in their functionality.
  • PWA  #118: The changes in the main PR, which involve replacing the generateExpenseReportReport function with the reportPrinter.print method in the expenseReportController.ts, are related to the changes in the retrieved PR, where similar replacements are made for generating healthcare cost reports in the healthCareCostController.ts using reportPrinter.print. Both PRs modify the report generation logic to utilize the reportPrinter module.

Poem

Hop along, my fellow coder, in fields of refactored light,
I’ve ditched the old PDF ways and embraced a future bright.
With a unified printer and logs that clearly tell,
Our reports now dance in harmony—oh, isn’t it swell?
I’m a happy little rabbit, bounding through the code,
Cheering every neat change on this adventurous road!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8853e72 and e9509c1.

📒 Files selected for processing (3)
  • backend/factory.ts (1 hunks)
  • backend/models/travel.ts (4 hunks)
  • frontend/src/vueform.config.ts (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (2)
backend/pdf/helper.ts (2)

85-94: 🛠️ Refactor suggestion

Use optional chaining to simplify checks.

Currently:

if (cost.cost && cost.cost.receipts) { ... }

This can be made more concise:

Proposed diff:

- if (cost.cost && cost.cost.receipts) {
+ if (cost.cost?.receipts) {

722-730: 🛠️ Refactor suggestion

Address control character (\u000B) in regex.

Static analysis flags “Unexpected control character” in your regex. Consider removing or safely handling \u000B. This diff removes the vertical tab from the split pattern:

- const lineSplit = (text: string) => text.split(/[\n\f\r\u000B]/)
+ const lineSplit = (text: string) => text.split(/[\n\f\r]/)
🧹 Nitpick comments (4)
backend/pdf/printer.ts (1)

139-253: Consider splitting the run() method for improved readability.

The run() method in ReportPrint is quite long. Extracting separate functions (like for advanced text formatting or specialized drawing) can make the code more modular and easier to maintain.

backend/models/helper.ts (1)

36-47: Add JSDoc documentation and improve type safety.

The logObject function is well-structured, but could benefit from documentation and type improvements.

Apply this diff to enhance the implementation:

+/**
+ * Creates a logging structure for tracking state changes with dates and editors.
+ * @param states - Array of valid states to track
+ * @returns Schema configuration for the log object
+ */
 export function logObject<T extends AnyState>(states: readonly T[]) {
   const logEntry = { type: { date: { type: Date, required: true }, editor: { type: Schema.Types.ObjectId, ref: 'User', required: true } } }
-  const log: { type: { [key in T]?: typeof logEntry }; required: true; default: () => {} } = {
+  const log: { type: { [key in T]?: typeof logEntry }; required: true; default: () => Record<string, never> } = {
     type: {},
     required: true,
-    default: () => ({})
+    default: () => Object.create(null)
   }
   for (const state of states) {
     log.type[state] = logEntry
   }
   return log
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 38-38: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

backend/models/expenseReport.ts (1)

91-91: Consider using a type-safe log entry creation.

The log entry creation could benefit from type safety.

Apply this diff to improve type safety:

-  this.log[this.state] = { date: new Date(), editor: this.editor }
+  type LogEntry = { date: Date; editor: typeof this.editor }
+  this.log[this.state] = { date: new Date(), editor: this.editor } as LogEntry
common/types.ts (1)

493-494: Consider expanding font options.

The fontNames array is limited to only 'NotoSans'. Consider adding more font options to support diverse formatting needs.

-export const fontNames = ['NotoSans'] as const
+export const fontNames = ['NotoSans', 'Arial', 'TimesNewRoman', 'Helvetica'] as const
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb1785f and 8853e72.

⛔ Files ignored due to path filters (1)
  • common/fonts/NotoSans.ttf is excluded by !**/*.ttf
📒 Files selected for processing (23)
  • backend/controller/expenseReportController.ts (4 hunks)
  • backend/controller/healthCareCostController.ts (6 hunks)
  • backend/controller/travelController.ts (5 hunks)
  • backend/controller/types.ts (1 hunks)
  • backend/data/settings.json (1 hunks)
  • backend/factory.ts (1 hunks)
  • backend/migrations.ts (1 hunks)
  • backend/models/expenseReport.ts (4 hunks)
  • backend/models/healthCareCost.ts (4 hunks)
  • backend/models/helper.ts (2 hunks)
  • backend/models/settings.ts (2 hunks)
  • backend/models/travel.ts (4 hunks)
  • backend/pdf/advance.ts (0 hunks)
  • backend/pdf/expenseReport.ts (0 hunks)
  • backend/pdf/healthCareCost.ts (0 hunks)
  • backend/pdf/helper.ts (2 hunks)
  • backend/pdf/printer.ts (1 hunks)
  • backend/pdf/travel.ts (0 hunks)
  • common/locales/de.json (3 hunks)
  • common/locales/en.json (2 hunks)
  • common/types.ts (5 hunks)
  • frontend/src/components/expenseReport/forms/ExpenseReportForm.vue (1 hunks)
  • frontend/src/components/travel/forms/TravelApplyForm.vue (1 hunks)
💤 Files with no reviewable changes (4)
  • backend/pdf/advance.ts
  • backend/pdf/healthCareCost.ts
  • backend/pdf/expenseReport.ts
  • backend/pdf/travel.ts
✅ Files skipped from review due to trivial changes (3)
  • backend/data/settings.json
  • frontend/src/components/expenseReport/forms/ExpenseReportForm.vue
  • frontend/src/components/travel/forms/TravelApplyForm.vue
🧰 Additional context used
🪛 Biome (1.9.4)
backend/models/helper.ts

[error] 38-38: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

backend/models/travel.ts

[error] 194-194: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 197-197: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

backend/factory.ts

[error] 31-31: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

backend/pdf/printer.ts

[error] 87-87: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 722-722: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🔇 Additional comments (23)
common/locales/en.json (2)

176-176: New Label Addition: "advanceFromEmployer"
A new label "advanceFromEmployer": "Advance From Employer" has been added under the "labels" section. This enhances clarity by clearly distinguishing the advance that comes from the employer. Verify that all UI components (e.g., ExpenseReportForm.vue and TravelApplyForm.vue) reference this new key correctly.


578-580: Report String Update for Approved Advances
The report string key in the "report" section has been updated from the former "approvedXY" to "approvedXonYbyZ" with the value "Advance in the amount of {X} approved by {Z} on {Y}." This change improves the clarity by including the advance amount, the approver, and the date. Please ensure that all components using this key are updated to supply parameters in the correct order.

common/locales/de.json (3)

87-87: Update "lastPlaceOfWork" Description
The description for "lastPlaceOfWork" in the "info" section has been updated to:
"Wähle hier deinen letzten Tätigkeitsort aus, dieser ist maßgebend für die Pauschalen deiner Rückreise."
This change shifts the context to better reflect return travel expense scenarios. Ensure that the front-end displays this new description as intended.


177-177: New Label Addition: "advanceFromEmployer"
A new key "advanceFromEmployer": "Vorschuss vom Arbeitgeber" has been introduced in the "labels" section. This mirrors the English version and enhances context specificity for employer advances. Verify that all instances where this label is used are correctly updated in the UI.


579-580: Report String Update for Approved Advances
The string for approved advances in the "report" section is updated to
"approvedXonYbyZ": "Vorschuss in Höhe von {X} am {Y} durch {Z} genehmigt."
This reformulation clarifies the details by explicitly stating the amount, date, and approver. Please confirm that the placeholder ordering in both the English and German versions remains consistent and that the associated code correctly handles these variables.

backend/pdf/helper.ts (4)

2-2: All good here.

No issues spotted in this import statement.


4-4: Importing reportPrinter looks fine.

Thanks for consolidating the report generation in a single location.


44-60: Centralized PDF generation approach is clear.

Great job consolidating the logic into reportPrinter.print(report, lng). This helps maintain DRY principles throughout the codebase.


200-212: Verify merging of receipt maps for potential key collisions.

When combining multiple receipt maps into a single object, ensure there are no unintended overwrites of receipts with the same ID or other conflicts. If you need further help verifying the correctness, let me know.

backend/pdf/printer.ts (2)

1-35: Imports and basic setup look correct.

No immediate concerns. If needed, confirm that your pdf-lib version is compatible with these features.


36-83: Interfaces definition is well-structured.

They provide clear type information and will help maintain clarity as the codebase grows.

backend/factory.ts (1)

1-7: Initial imports look good.

No concerns about the usage of pdf-lib and internal modules here.

backend/controller/types.ts (1)

28-30: LGTM! Interface updates align with logging enhancements.

The addition of 'log' to the omitted properties in both interfaces is consistent with the logging capabilities being added across models.

Also applies to: 32-36

backend/models/settings.ts (1)

22-22: LGTM! Consistent settings update across services.

The addition ensures that both the travel calculator and report printer are updated with the latest distance refund settings.

Also applies to: 128-130

backend/models/expenseReport.ts (2)

26-26: LGTM! Log field added to schema.

The log field is properly initialized using the logObject helper with expenseReportStates.


61-61: LGTM! Comprehensive population of log editors.

The populate function correctly includes all state log editors using array mapping.

backend/models/healthCareCost.ts (1)

11-11: LGTM! The logging implementation enhances state tracking.

The changes introduce a robust logging mechanism that tracks state transitions with timestamps and editor information, which is valuable for audit trails and debugging.

Also applies to: 35-35, 72-72, 102-102

backend/migrations.ts (1)

137-155: LGTM! The migration properly handles historical data.

The migration correctly:

  • Iterates through non-historic reports
  • Constructs log entries from history data
  • Uses appropriate MongoDB operations for updates
backend/models/travel.ts (1)

21-21: LGTM! The logging implementation enhances state tracking.

The changes introduce a robust logging mechanism that tracks state transitions with timestamps and editor information.

Also applies to: 60-60, 151-151

backend/controller/expenseReportController.ts (1)

5-5: LGTM! The report generation is properly consolidated.

The changes consistently replace the old report generation with the new centralized reportPrinter.print method.

Also applies to: 143-143, 278-278, 307-307

common/types.ts (1)

375-380: LGTM! Well-designed logging type.

The generic Log type effectively tracks state changes with proper type safety and essential metadata (date and editor).

backend/controller/healthCareCostController.ts (1)

150-154: LGTM! Consistent report generation refactoring.

The replacement of legacy report generation with reportPrinter.print is consistent across all controllers while maintaining proper error handling and response headers.

Also applies to: 325-326, 374-375, 415-416

backend/controller/travelController.ts (1)

222-223: LGTM! Unified report generation implementation.

The migration to reportPrinter.print is consistently applied across all travel-related controllers, maintaining the same behavior and error handling patterns.

Also applies to: 283-284, 451-452, 504-505

Comment on lines 185 to 204
this.log[this.state] = { date: new Date(), editor: this.editor }

if (this.state === 'approved') {
// move vehicle registration of owner as receipt to 'ownCar' stages
const receipts = []
for (const stage of this.stages) {
if (stage.transport.type == 'ownCar') {
if (receipts.length == 0) {
const owner = await User.findOne({ _id: this.owner._id }).lean()
if (owner && owner.vehicleRegistration) {
for (const vr of owner.vehicleRegistration) {
const doc = await DocumentFile.findOne({ _id: vr._id }).lean()
delete (doc as unknown as any)._id
receipts.push(await DocumentFile.create(doc))
}
stage.cost.receipts = receipts
}
}
stage.cost.receipts = receipts
}

break
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve vehicle registration handling.

The current implementation has several potential issues:

  1. Unsafe delete operation
  2. Potential memory leak from not cleaning up old receipts
  3. Missing error handling for document creation

Apply this diff to improve the implementation:

 if (this.state === 'approved') {
   // move vehicle registration of owner as receipt to 'ownCar' stages
   const receipts = []
   for (const stage of this.stages) {
     if (stage.transport.type == 'ownCar') {
       if (receipts.length == 0) {
         const owner = await User.findOne({ _id: this.owner._id }).lean()
-        if (owner && owner.vehicleRegistration) {
+        if (owner?.vehicleRegistration) {
           for (const vr of owner.vehicleRegistration) {
             const doc = await DocumentFile.findOne({ _id: vr._id }).lean()
-            delete (doc as unknown as any)._id
+            const { _id, ...docWithoutId } = doc as unknown as any
             try {
-              receipts.push(await DocumentFile.create(doc))
+              receipts.push(await DocumentFile.create(docWithoutId))
+            } catch (error) {
+              console.error('Failed to create document:', error)
+              // Clean up any receipts created so far
+              for (const receipt of receipts) {
+                await DocumentFile.deleteOne({ _id: receipt._id })
+              }
+              throw error
+            }
           }
         }
       }
       stage.cost.receipts = receipts
     }
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.log[this.state] = { date: new Date(), editor: this.editor }
if (this.state === 'approved') {
// move vehicle registration of owner as receipt to 'ownCar' stages
const receipts = []
for (const stage of this.stages) {
if (stage.transport.type == 'ownCar') {
if (receipts.length == 0) {
const owner = await User.findOne({ _id: this.owner._id }).lean()
if (owner && owner.vehicleRegistration) {
for (const vr of owner.vehicleRegistration) {
const doc = await DocumentFile.findOne({ _id: vr._id }).lean()
delete (doc as unknown as any)._id
receipts.push(await DocumentFile.create(doc))
}
stage.cost.receipts = receipts
}
}
stage.cost.receipts = receipts
}
break
}
this.log[this.state] = { date: new Date(), editor: this.editor }
if (this.state === 'approved') {
// move vehicle registration of owner as receipt to 'ownCar' stages
const receipts = []
for (const stage of this.stages) {
if (stage.transport.type == 'ownCar') {
if (receipts.length == 0) {
const owner = await User.findOne({ _id: this.owner._id }).lean()
if (owner?.vehicleRegistration) {
for (const vr of owner.vehicleRegistration) {
const doc = await DocumentFile.findOne({ _id: vr._id }).lean()
const { _id, ...docWithoutId } = doc as unknown as any
try {
receipts.push(await DocumentFile.create(docWithoutId))
} catch (error) {
console.error('Failed to create document:', error)
// Clean up any receipts created so far
for (const receipt of receipts) {
await DocumentFile.deleteOne({ _id: receipt._id })
}
throw error
}
}
}
}
stage.cost.receipts = receipts
}
}
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 194-194: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 197-197: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

@david-loe david-loe merged commit 8d06922 into main Feb 18, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add line breaks to long report names in pdf
1 participant