Initial stage
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
PORT=3000
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_NAME=check_list
|
||||
DB_USER=check_list_user
|
||||
DB_PASSWORD=check_list_password
|
||||
DB_CONNECTION_LIMIT=5
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
.env
|
||||
dist/
|
||||
@@ -0,0 +1,724 @@
|
||||
Project Document
|
||||
Check List – Hybrid Initial Solution for Quality Check Reports
|
||||
|
||||
1. Document Information
|
||||
|
||||
Project Name: Check List
|
||||
Document Type: Initial Project Documentation / Concept Specification
|
||||
Version: 1.0
|
||||
Date: 09 April 2026
|
||||
Project Phase: Initial Implementation (Hybrid model between Option 2 and Option 3)
|
||||
|
||||
2. Executive Summary
|
||||
|
||||
The purpose of the Check List project is to replace the current Excel-based quality check reporting process with a modern digital solution that supports structured report creation, image attachments, offline work, and standardized export.
|
||||
|
||||
The proposed initial implementation will follow a hybrid approach between a fully local web solution and a server-connected application.
|
||||
|
||||
In this phase:
|
||||
- Node.js and MariaDB available on the server side will be used to centrally manage:
|
||||
- checklist templates,
|
||||
- report structure,
|
||||
- required fields,
|
||||
- validation rules,
|
||||
- lookup values,
|
||||
- image processing rules,
|
||||
- template versions.
|
||||
- The client-side web application will be responsible for:
|
||||
- loading and caching templates,
|
||||
- creating reports offline,
|
||||
- saving reports locally as drafts,
|
||||
- switching between multiple reports,
|
||||
- attaching and resizing images directly in the browser,
|
||||
- renaming images automatically,
|
||||
- generating the final output as a ZIP file containing:
|
||||
- an Excel report file,
|
||||
- a folder with attached images.
|
||||
|
||||
At this stage, the final reports will not yet be stored centrally in the database.
|
||||
Instead, the database will serve as a template and configuration repository, while the final report remains a file-based deliverable.
|
||||
|
||||
This approach provides:
|
||||
- a practical and low-risk first implementation,
|
||||
- offline usability,
|
||||
- centralized management of checklist definitions,
|
||||
- and a clear migration path toward future server-side report synchronization.
|
||||
|
||||
3. Project Background
|
||||
|
||||
The current reporting process is based on Excel files used to create quality check lists and reports. This creates a number of operational and technical limitations, including:
|
||||
- manual preparation of reports,
|
||||
- inconsistent structure between reports,
|
||||
- difficulty enforcing required fields,
|
||||
- inefficient handling of images,
|
||||
- lack of standardized image naming,
|
||||
- dependency on Excel files and manual file handling,
|
||||
- limited support for mobile devices,
|
||||
- no controlled offline workflow,
|
||||
- difficult future integration with central systems.
|
||||
|
||||
The new solution is intended to improve data consistency, user convenience, and long-term maintainability by introducing a controlled digital workflow.
|
||||
|
||||
4. Project Objective
|
||||
|
||||
The objective of the project is to implement a digital reporting solution that replaces Excel-based checklist reporting with a structured web application that works across platforms, supports offline work, and produces a standardized ZIP-based report package.
|
||||
|
||||
The initial version of the solution must:
|
||||
1. allow users to create quality check reports in a web application,
|
||||
2. support Windows, Android, and iOS devices,
|
||||
3. allow use without permanent internet access,
|
||||
4. provide centrally managed checklist templates from the server,
|
||||
5. allow users to save reports as drafts and continue them later,
|
||||
6. allow switching between multiple reports,
|
||||
7. support image attachment with automatic resizing/compression in the browser,
|
||||
8. automatically rename images based on report naming rules,
|
||||
9. generate the final report as a ZIP package with Excel file and images,
|
||||
10. prepare the solution architecture for future server-side report synchronization.
|
||||
|
||||
5. Scope of the Initial Implementation
|
||||
|
||||
5.1 In Scope
|
||||
|
||||
The following functions are included in the initial project scope:
|
||||
|
||||
Server-side scope
|
||||
- central storage of checklist templates,
|
||||
- central storage of required field definitions,
|
||||
- central storage of validation rules,
|
||||
- central storage of lookup values / dropdown lists,
|
||||
- central storage of image handling rules,
|
||||
- template version management,
|
||||
- API for providing templates and configuration to the web application.
|
||||
|
||||
Client-side scope
|
||||
- responsive web application,
|
||||
- dynamic rendering of checklists based on templates,
|
||||
- local storage of report drafts,
|
||||
- local storage of image attachments,
|
||||
- report editing in offline mode,
|
||||
- report auto-save,
|
||||
- support for multiple local reports,
|
||||
- switching between reports within the application,
|
||||
- browser-based image resizing/compression,
|
||||
- automatic image renaming,
|
||||
- local ZIP file generation,
|
||||
- local Excel file generation,
|
||||
- local export of finished reports.
|
||||
|
||||
5.2 Out of Scope (Initial Phase)
|
||||
|
||||
The following items are intentionally excluded from the first implementation phase:
|
||||
- central storage of completed reports in the database,
|
||||
- full backend synchronization of report data,
|
||||
- server-side image upload for completed reports,
|
||||
- conflict handling between multiple users/devices,
|
||||
- server-side draft storage,
|
||||
- advanced reporting/analytics dashboards,
|
||||
- workflow approvals,
|
||||
- ERP/QMS integrations,
|
||||
- embedding images directly inside Excel (unless later required),
|
||||
- native Android/iOS applications.
|
||||
|
||||
These items can be considered in later project phases.
|
||||
|
||||
6. Proposed Solution Overview
|
||||
|
||||
The recommended initial solution is a hybrid offline-first web application.
|
||||
|
||||
Core principle:
|
||||
- Templates and configuration are centrally managed on the server
|
||||
- Reports are created and stored locally in the browser
|
||||
- Final output is exported as ZIP
|
||||
|
||||
This model combines the most important business and technical benefits:
|
||||
- centralized control over checklist content,
|
||||
- simpler initial implementation than full synchronization,
|
||||
- full support for offline report creation,
|
||||
- broad device compatibility,
|
||||
- structured handling of images,
|
||||
- future readiness for backend expansion.
|
||||
|
||||
7. Business Benefits
|
||||
|
||||
The proposed solution provides the following business benefits:
|
||||
|
||||
7.1 Standardization
|
||||
- consistent report structure,
|
||||
- consistent validation rules,
|
||||
- controlled mandatory fields,
|
||||
- unified image naming convention.
|
||||
|
||||
7.2 Improved usability
|
||||
- easier report creation compared to manual Excel editing,
|
||||
- better experience on mobile devices,
|
||||
- faster attachment handling,
|
||||
- draft-based workflow.
|
||||
|
||||
7.3 Offline capability
|
||||
- users can continue work even without internet access,
|
||||
- templates can be loaded earlier and used later offline.
|
||||
|
||||
7.4 Central governance
|
||||
- checklist templates are managed from one place,
|
||||
- changes in required fields do not require front-end redesign,
|
||||
- image limitations are centrally configurable.
|
||||
|
||||
7.5 Lower implementation risk
|
||||
- final reports remain file-based in the first phase,
|
||||
- backend complexity is limited,
|
||||
- architecture remains ready for later expansion.
|
||||
|
||||
8. User Groups
|
||||
|
||||
The initial solution is intended for users involved in quality check and inspection reporting, for example:
|
||||
- quality inspectors,
|
||||
- production or warehouse staff performing checks,
|
||||
- incoming inspection personnel,
|
||||
- mobile users working on shop floor or at supplier/customer location,
|
||||
- administrators responsible for checklist configuration.
|
||||
|
||||
9. Functional Requirements
|
||||
|
||||
9.1 Template Management
|
||||
|
||||
Checklist templates must be centrally stored on the server and provided to the web application through an API.
|
||||
|
||||
Each template should be able to define:
|
||||
- template identifier,
|
||||
- template name,
|
||||
- version,
|
||||
- sections/groups,
|
||||
- field labels,
|
||||
- field types,
|
||||
- required fields,
|
||||
- default values,
|
||||
- validation rules,
|
||||
- optional comments,
|
||||
- image requirements,
|
||||
- export metadata.
|
||||
|
||||
The application must support the use of different template versions.
|
||||
|
||||
Template versioning rule
|
||||
- Existing draft reports must remain bound to the template version under which they were created.
|
||||
- New reports should use the newest active version of the selected template.
|
||||
|
||||
9.2 Report Creation
|
||||
|
||||
The user must be able to create a new report based on a selected checklist template.
|
||||
|
||||
A report should include:
|
||||
- report header information,
|
||||
- checklist items,
|
||||
- comments,
|
||||
- attachments,
|
||||
- report status,
|
||||
- metadata (created date, updated date, template version, etc.).
|
||||
|
||||
Possible field types include:
|
||||
- text,
|
||||
- number,
|
||||
- date,
|
||||
- checkbox / boolean,
|
||||
- dropdown / lookup list,
|
||||
- pass/fail choice,
|
||||
- comments,
|
||||
- attachment-required items.
|
||||
|
||||
9.3 Draft Save and Continue Later
|
||||
|
||||
The application must allow the user to save incomplete reports locally as drafts.
|
||||
|
||||
The user must be able to:
|
||||
- save a report as draft,
|
||||
- leave the report,
|
||||
- return later,
|
||||
- continue editing from the last saved state.
|
||||
|
||||
Draft saving should occur:
|
||||
- manually via a save function,
|
||||
- automatically during editing,
|
||||
- when switching between reports,
|
||||
- when adding/removing attachments.
|
||||
|
||||
Draft reports must remain accessible from a report list/dashboard.
|
||||
|
||||
9.4 Switching Between Reports
|
||||
|
||||
The user must be able to work with multiple reports within the same application.
|
||||
|
||||
The application must provide a report dashboard or report manager where the user can:
|
||||
- see all locally stored reports,
|
||||
- filter reports by status,
|
||||
- open a selected report,
|
||||
- continue editing,
|
||||
- duplicate a report,
|
||||
- archive or delete a report,
|
||||
- export a completed report.
|
||||
|
||||
When switching from one report to another:
|
||||
- the current report must be saved automatically,
|
||||
- the selected report must be loaded from local storage.
|
||||
|
||||
9.5 Report Statuses
|
||||
|
||||
The system should support at least the following statuses:
|
||||
- Draft
|
||||
- In Progress
|
||||
- Ready for Export
|
||||
- Exported
|
||||
- Archived
|
||||
|
||||
These statuses should be visible in the report list.
|
||||
|
||||
Future phases may introduce additional synchronization-related statuses, but they are not required in the initial version.
|
||||
|
||||
9.6 Offline Mode
|
||||
|
||||
The application must support offline work after templates and configuration have been loaded.
|
||||
|
||||
Offline mode must allow:
|
||||
- opening cached templates,
|
||||
- creating new reports,
|
||||
- editing existing drafts,
|
||||
- attaching images,
|
||||
- saving report progress locally,
|
||||
- generating ZIP output locally.
|
||||
|
||||
If internet access is unavailable, the application should continue using the latest cached template data.
|
||||
|
||||
9.7 Image Attachment Handling
|
||||
|
||||
The system must support attaching images to reports.
|
||||
|
||||
Images may be added from:
|
||||
- file picker,
|
||||
- mobile device gallery,
|
||||
- mobile camera (if browser/device supports direct capture).
|
||||
|
||||
The application must validate image files against centrally defined rules such as:
|
||||
- allowed file types,
|
||||
- maximum file size,
|
||||
- maximum dimensions,
|
||||
- output quality settings.
|
||||
|
||||
9.8 Browser-Based Image Resizing
|
||||
|
||||
Image optimization must be performed directly in the web browser on the client side.
|
||||
|
||||
When an image is attached, the application must:
|
||||
1. read the image locally,
|
||||
2. validate file type, size, and dimensions,
|
||||
3. resize/compress the image if it exceeds configured limits,
|
||||
4. store the optimized version locally,
|
||||
5. show a preview,
|
||||
6. associate the optimized image with the current report.
|
||||
|
||||
This functionality is required to:
|
||||
- reduce storage usage,
|
||||
- standardize attachment size,
|
||||
- improve performance,
|
||||
- maintain offline capability,
|
||||
- prepare for future upload optimization.
|
||||
|
||||
The system should support either:
|
||||
- automatic resizing,
|
||||
- warning + resizing,
|
||||
- or blocking oversized files,
|
||||
|
||||
depending on centrally configured rules.
|
||||
|
||||
For the initial implementation, the recommended behavior is:
|
||||
- automatic optimization with user notification.
|
||||
|
||||
9.9 Automatic Image Renaming
|
||||
|
||||
The application must automatically rename attached image files according to a structured naming convention.
|
||||
|
||||
The naming convention should be centrally configurable and may include:
|
||||
- report number,
|
||||
- section code,
|
||||
- sequence number,
|
||||
- date or other metadata if required.
|
||||
|
||||
Example naming structure:
|
||||
<ReportNumber>_<SectionCode>_<Sequence>.jpg
|
||||
|
||||
Both the original filename and generated filename should be tracked in metadata.
|
||||
|
||||
9.10 Validation Rules
|
||||
|
||||
The application must support validation based on centrally managed rules.
|
||||
|
||||
Validation may include:
|
||||
- required fields,
|
||||
- allowed values,
|
||||
- numeric ranges,
|
||||
- image required for selected checklist item,
|
||||
- minimum/maximum number of attachments,
|
||||
- checklist completeness.
|
||||
|
||||
Validation must distinguish between:
|
||||
- draft save (allowed even if incomplete),
|
||||
- ready for export (only allowed if validation passes).
|
||||
|
||||
9.11 Export to ZIP
|
||||
|
||||
The final report must be generated locally as a ZIP archive.
|
||||
|
||||
The ZIP file should contain:
|
||||
- one Excel file,
|
||||
- one image folder containing all attached images.
|
||||
|
||||
Recommended ZIP structure
|
||||
<ReportNumber>.zip
|
||||
├── <ReportNumber>.xlsx
|
||||
└── images/
|
||||
├── <image1>.jpg
|
||||
├── <image2>.jpg
|
||||
└── ...
|
||||
|
||||
The Excel file must include:
|
||||
- report header,
|
||||
- checklist answers,
|
||||
- comments,
|
||||
- image file references,
|
||||
- template version,
|
||||
- export timestamp.
|
||||
|
||||
For the first version, it is recommended that:
|
||||
- images remain as separate files in the ZIP package,
|
||||
- images are not embedded into the Excel file unless explicitly required later.
|
||||
|
||||
10. Non-Functional Requirements
|
||||
|
||||
10.1 Platform Compatibility
|
||||
The application must operate on:
|
||||
- Windows browsers,
|
||||
- Android browsers,
|
||||
- iOS browsers.
|
||||
|
||||
A responsive layout is required.
|
||||
|
||||
10.2 Performance
|
||||
The solution should perform adequately on standard office devices and mobile devices.
|
||||
|
||||
Special attention should be given to:
|
||||
- image processing time,
|
||||
- local storage performance,
|
||||
- report loading speed,
|
||||
- ZIP generation performance.
|
||||
|
||||
10.3 Usability
|
||||
The interface should be simple and optimized for operational use.
|
||||
|
||||
The system should:
|
||||
- minimize manual steps,
|
||||
- provide visible save status,
|
||||
- provide clear validation messages,
|
||||
- support touch-based interaction,
|
||||
- reduce risk of data loss.
|
||||
|
||||
10.4 Maintainability
|
||||
The solution should be modular and designed for future expansion.
|
||||
|
||||
The frontend should separate:
|
||||
- UI logic,
|
||||
- template rendering,
|
||||
- local data storage,
|
||||
- image processing,
|
||||
- export logic,
|
||||
- future synchronization logic.
|
||||
|
||||
10.5 Data Integrity
|
||||
The system must:
|
||||
- auto-save regularly,
|
||||
- preserve locally stored reports,
|
||||
- bind reports to template versions,
|
||||
- avoid corruption caused by later template changes.
|
||||
|
||||
10.6 Security
|
||||
In the initial phase, security requirements may be lighter than in a fully centralized solution, but should still include:
|
||||
- secure API communication when online,
|
||||
- controlled access to template management,
|
||||
- safe handling of local report data,
|
||||
- user/session identification if introduced.
|
||||
|
||||
11. Technical Architecture
|
||||
|
||||
11.1 General Architecture
|
||||
|
||||
The recommended architecture for the initial implementation is:
|
||||
|
||||
Server Side
|
||||
- Node.js application
|
||||
- MariaDB database
|
||||
- REST API for templates and configuration
|
||||
|
||||
Client Side
|
||||
- browser-based web application
|
||||
- local storage for reports and attachments
|
||||
- local report generation and export
|
||||
|
||||
11.2 Server Responsibilities
|
||||
|
||||
The server will manage:
|
||||
- checklist templates,
|
||||
- template versions,
|
||||
- field definitions,
|
||||
- required field rules,
|
||||
- lookup data,
|
||||
- image processing rules,
|
||||
- configuration data.
|
||||
|
||||
At this stage, the server will not store final report data.
|
||||
|
||||
11.3 Client Responsibilities
|
||||
|
||||
The client application will manage:
|
||||
- template download and caching,
|
||||
- form rendering,
|
||||
- local report editing,
|
||||
- draft storage,
|
||||
- image resizing,
|
||||
- image previews,
|
||||
- automatic renaming,
|
||||
- Excel generation,
|
||||
- ZIP generation.
|
||||
|
||||
12. Recommended Technology Stack
|
||||
|
||||
12.1 Frontend
|
||||
Recommended:
|
||||
- React for the web application,
|
||||
- responsive UI framework or custom responsive design,
|
||||
- IndexedDB for local offline storage,
|
||||
- Excel generation library,
|
||||
- ZIP generation library,
|
||||
- browser Canvas API (or equivalent browser-side image processing approach).
|
||||
|
||||
12.2 Backend
|
||||
Recommended:
|
||||
- Node.js
|
||||
- REST API architecture
|
||||
|
||||
12.3 Database
|
||||
Recommended:
|
||||
- MariaDB
|
||||
|
||||
13. Data Storage Concept
|
||||
|
||||
13.1 Server-side Data
|
||||
Stored in MariaDB:
|
||||
- templates,
|
||||
- template sections,
|
||||
- template fields,
|
||||
- validation rules,
|
||||
- lookups,
|
||||
- image settings,
|
||||
- export settings,
|
||||
- template version data.
|
||||
|
||||
13.2 Client-side Data
|
||||
Stored locally in the browser:
|
||||
- downloaded templates (cached),
|
||||
- draft reports,
|
||||
- report metadata,
|
||||
- checklist answers,
|
||||
- attachment metadata,
|
||||
- optimized image files.
|
||||
|
||||
Recommended local storage technology
|
||||
Use IndexedDB rather than basic browser local storage.
|
||||
|
||||
Reason:
|
||||
- better support for structured data,
|
||||
- support for binary image data,
|
||||
- better capacity and performance,
|
||||
- better fit for multiple drafts and attachments.
|
||||
|
||||
14. Suggested Logical Data Model
|
||||
|
||||
14.1 Template-related entities (server side)
|
||||
Possible logical entities include:
|
||||
- Templates
|
||||
- Template Sections
|
||||
- Template Fields
|
||||
- Lookup Values
|
||||
- Template Settings
|
||||
- Template Versions
|
||||
|
||||
These should support dynamic form rendering.
|
||||
|
||||
14.2 Report-related entities (client side)
|
||||
Each local report should contain at least:
|
||||
- local report ID,
|
||||
- report number or temporary identifier,
|
||||
- template ID,
|
||||
- template version,
|
||||
- report status,
|
||||
- header data,
|
||||
- checklist values,
|
||||
- comments,
|
||||
- attachment metadata,
|
||||
- created date,
|
||||
- last update date.
|
||||
|
||||
Each attachment should contain:
|
||||
- internal attachment ID,
|
||||
- original filename,
|
||||
- generated filename,
|
||||
- MIME type,
|
||||
- file size,
|
||||
- dimensions,
|
||||
- relation to report and field/section.
|
||||
|
||||
15. API Scope for Initial Phase
|
||||
|
||||
The initial backend API can be intentionally limited.
|
||||
|
||||
Suggested API scope:
|
||||
- get available templates,
|
||||
- get template details,
|
||||
- get template version,
|
||||
- get lookup data,
|
||||
- get image/configuration settings,
|
||||
- optional application version/configuration endpoint,
|
||||
- optional authentication endpoints.
|
||||
|
||||
Report upload API is not required in the initial phase.
|
||||
|
||||
16. User Workflow
|
||||
|
||||
16.1 Online startup scenario
|
||||
1. User opens the application.
|
||||
2. Application loads latest templates and configuration from the server.
|
||||
3. Templates are cached locally.
|
||||
4. User can start creating reports.
|
||||
|
||||
16.2 Offline work scenario
|
||||
1. User opens the application without internet access.
|
||||
2. Application uses last cached templates.
|
||||
3. User creates or continues reports.
|
||||
4. Images are processed locally.
|
||||
5. Drafts are saved locally.
|
||||
6. User exports a ZIP file when the report is finished.
|
||||
|
||||
16.3 Report editing scenario
|
||||
1. User creates or opens a report.
|
||||
2. User fills checklist fields.
|
||||
3. User adds comments and images.
|
||||
4. Application auto-saves progress.
|
||||
5. User may leave report as draft or continue later.
|
||||
6. User marks report as ready.
|
||||
7. Application validates data.
|
||||
8. User exports ZIP file.
|
||||
|
||||
17. Risks and Constraints
|
||||
|
||||
17.1 Local Storage Dependency
|
||||
Because reports are stored locally in the first phase, there is a dependency on browser/device storage.
|
||||
|
||||
Risk:
|
||||
- local drafts may be lost if storage is cleared.
|
||||
|
||||
Mitigation:
|
||||
- auto-save,
|
||||
- visible draft management,
|
||||
- encourage timely export/archiving,
|
||||
- consider backup/import in later phase.
|
||||
|
||||
17.2 Browser Storage Limits
|
||||
Images and multiple drafts can consume significant space.
|
||||
|
||||
Mitigation:
|
||||
- automatic image compression,
|
||||
- optimized image limits,
|
||||
- archive/delete completed reports,
|
||||
- monitor practical storage capacity during pilot.
|
||||
|
||||
17.3 Template Change During Active Reports
|
||||
Changing a template could affect reports already started.
|
||||
|
||||
Mitigation:
|
||||
- enforce template versioning,
|
||||
- bind each report to the template version it started with.
|
||||
|
||||
17.4 No Central Report Repository Yet
|
||||
The initial phase does not yet provide central report storage or reporting history.
|
||||
|
||||
Mitigation:
|
||||
- keep ZIP export as the official output,
|
||||
- plan future extension to server-side synchronization.
|
||||
|
||||
18. Future Expansion Path
|
||||
|
||||
The proposed architecture is intentionally designed to support future transition toward a more advanced server-connected model.
|
||||
|
||||
Future phases may include:
|
||||
- upload of completed reports to backend,
|
||||
- server-side report repository,
|
||||
- attachment upload,
|
||||
- synchronization of offline-created reports,
|
||||
- user roles and permissions,
|
||||
- dashboards and analytics,
|
||||
- integration with other systems,
|
||||
- central audit trail.
|
||||
|
||||
Because templates are already server-managed in the initial phase, future expansion should require fewer architectural changes.
|
||||
|
||||
19. Recommended Implementation Phases
|
||||
|
||||
Phase 1 – Hybrid Initial Solution
|
||||
- template/configuration management on server,
|
||||
- local report creation,
|
||||
- offline-capable report editing,
|
||||
- draft save and continue later,
|
||||
- switching between multiple reports,
|
||||
- browser-side image resizing,
|
||||
- automatic image renaming,
|
||||
- ZIP export.
|
||||
|
||||
Phase 2 – Extended Hybrid
|
||||
- optional user authentication,
|
||||
- optional upload of ZIP packages,
|
||||
- central administration UI for templates,
|
||||
- export history,
|
||||
- advanced template/version control.
|
||||
|
||||
Phase 3 – Full Server-Based Reporting
|
||||
- full report synchronization,
|
||||
- central report database,
|
||||
- attachment upload,
|
||||
- online/offline sync handling,
|
||||
- reporting dashboard and analytics.
|
||||
|
||||
20. Final Recommendation
|
||||
|
||||
The recommended starting point for the Check List project is a hybrid initial solution in which:
|
||||
- Node.js + MariaDB are used from the beginning for central checklist/template management,
|
||||
- the web application works across Windows, Android, and iOS,
|
||||
- the application supports offline work after templates are loaded,
|
||||
- reports are stored locally as drafts,
|
||||
- the user can switch between reports and continue later,
|
||||
- images are processed directly in the browser,
|
||||
- and the final report is exported as a ZIP package with Excel and images.
|
||||
|
||||
This approach provides the best balance between:
|
||||
- implementation speed,
|
||||
- operational usefulness,
|
||||
- centralized control,
|
||||
- offline support,
|
||||
- and future scalability.
|
||||
|
||||
21. Proposed Next Step
|
||||
|
||||
The next recommended activity is to prepare a detailed implementation specification covering:
|
||||
1. screen-by-screen user flow,
|
||||
2. template JSON structure,
|
||||
3. local report data model,
|
||||
4. API endpoint specification,
|
||||
5. MariaDB schema proposal,
|
||||
6. image resizing rules,
|
||||
7. ZIP/Excel export format,
|
||||
8. development backlog / user stories.
|
||||
@@ -0,0 +1,274 @@
|
||||
# Check List Initial Solution Proposal
|
||||
|
||||
## 1. Recommended Starting Point
|
||||
|
||||
The strongest initial implementation is an offline-first web application with a thin server-backed configuration layer.
|
||||
|
||||
This matches the source document's core constraint set:
|
||||
- reports must work without permanent internet access,
|
||||
- templates must be centrally controlled,
|
||||
- final output remains file-based,
|
||||
- the architecture must be extendable toward future synchronization.
|
||||
|
||||
## 2. Proposed Architecture
|
||||
|
||||
### Client application
|
||||
|
||||
Build a responsive Progressive Web App using:
|
||||
- React
|
||||
- TypeScript
|
||||
- IndexedDB for offline persistence
|
||||
- a service worker for application asset caching
|
||||
- browser-based image processing using Canvas or createImageBitmap
|
||||
- XLSX generation library
|
||||
- ZIP generation library
|
||||
|
||||
Main client modules:
|
||||
- template cache module
|
||||
- dynamic form renderer
|
||||
- report draft repository
|
||||
- image processing pipeline
|
||||
- validation engine
|
||||
- Excel export mapper
|
||||
- ZIP packaging module
|
||||
- sync-ready integration layer placeholder
|
||||
|
||||
### Server application
|
||||
|
||||
Build a small REST API using:
|
||||
- Node.js
|
||||
- Express or Fastify
|
||||
- MariaDB
|
||||
|
||||
Main server responsibilities:
|
||||
- template CRUD
|
||||
- template versioning
|
||||
- lookup management
|
||||
- validation rule delivery
|
||||
- image rule delivery
|
||||
- export configuration delivery
|
||||
- optional authentication later
|
||||
|
||||
The server should not accept completed reports in phase 1.
|
||||
|
||||
## 3. Why This Design Fits
|
||||
|
||||
This design solves the main business problems without introducing early complexity:
|
||||
- central governance is handled by the backend,
|
||||
- offline draft work is handled entirely on the client,
|
||||
- images are optimized before storage/export,
|
||||
- ZIP export preserves compatibility with the current file-based process,
|
||||
- future synchronization can be added without replacing the client-side report model.
|
||||
|
||||
## 4. Initial Technical Blueprint
|
||||
|
||||
### Frontend structure
|
||||
|
||||
Suggested feature-oriented structure:
|
||||
|
||||
```text
|
||||
src/
|
||||
app/
|
||||
features/templates/
|
||||
features/reports/
|
||||
features/images/
|
||||
features/export/
|
||||
features/validation/
|
||||
features/dashboard/
|
||||
shared/ui/
|
||||
shared/lib/
|
||||
shared/storage/
|
||||
shared/api/
|
||||
```
|
||||
|
||||
Key implementation decisions:
|
||||
- use template-driven rendering instead of hardcoded forms,
|
||||
- keep report state normalized by report ID,
|
||||
- store attachments separately from answer objects in IndexedDB,
|
||||
- version templates immutably once published,
|
||||
- separate draft validation from export validation.
|
||||
|
||||
### Backend structure
|
||||
|
||||
Suggested backend domains:
|
||||
- templates
|
||||
- templateVersions
|
||||
- fields
|
||||
- lookups
|
||||
- validationRules
|
||||
- imageRules
|
||||
- exportSettings
|
||||
|
||||
Recommended principle:
|
||||
the API should deliver a fully materialized template definition for a chosen version so the client can render forms without additional server joins during report editing.
|
||||
|
||||
## 5. Minimal Phase 1 Data Model
|
||||
|
||||
### Server-side entities
|
||||
|
||||
Recommended MariaDB tables:
|
||||
- templates
|
||||
- template_versions
|
||||
- template_sections
|
||||
- template_fields
|
||||
- field_validation_rules
|
||||
- lookup_sets
|
||||
- lookup_values
|
||||
- image_rules
|
||||
- export_profiles
|
||||
|
||||
Important rules:
|
||||
- only one active version per template for new report creation,
|
||||
- old versions remain readable for existing drafts,
|
||||
- template JSON snapshots can be generated and cached for fast client download.
|
||||
|
||||
### Client-side entities
|
||||
|
||||
Recommended IndexedDB stores:
|
||||
- templatesCache
|
||||
- reports
|
||||
- reportAnswers
|
||||
- attachments
|
||||
- appConfig
|
||||
|
||||
Example report aggregate:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "local-report-uuid",
|
||||
"reportNumber": "QC-2026-0001",
|
||||
"templateId": "incoming-inspection",
|
||||
"templateVersion": 3,
|
||||
"status": "in_progress",
|
||||
"header": {
|
||||
"supplier": "ACME",
|
||||
"inspectionDate": "2026-04-09"
|
||||
},
|
||||
"createdAt": "2026-04-09T09:00:00Z",
|
||||
"updatedAt": "2026-04-09T10:15:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Example attachment metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "attachment-uuid",
|
||||
"reportId": "local-report-uuid",
|
||||
"fieldId": "damage-photo",
|
||||
"originalFilename": "IMG_0412.jpg",
|
||||
"generatedFilename": "QC-2026-0001_SEC01_001.jpg",
|
||||
"mimeType": "image/jpeg",
|
||||
"width": 1600,
|
||||
"height": 1200,
|
||||
"sizeBytes": 245312
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Recommended API Surface
|
||||
|
||||
Initial REST endpoints:
|
||||
- `GET /api/templates`
|
||||
- `GET /api/templates/:templateId`
|
||||
- `GET /api/templates/:templateId/versions/:version`
|
||||
- `GET /api/lookups`
|
||||
- `GET /api/config/image-rules`
|
||||
- `GET /api/config/export`
|
||||
- `GET /api/app-config`
|
||||
|
||||
Optional later endpoints:
|
||||
- `POST /api/auth/login`
|
||||
- `POST /api/report-uploads`
|
||||
|
||||
API response strategy:
|
||||
- return explicit version metadata with every template,
|
||||
- include cache timestamps or ETags,
|
||||
- support incremental refresh later if template volume grows.
|
||||
|
||||
## 7. Core User Flow
|
||||
|
||||
Phase 1 flow should be:
|
||||
1. User opens the app online.
|
||||
2. App downloads active templates and configuration.
|
||||
3. Data is cached locally.
|
||||
4. User creates or resumes a report.
|
||||
5. Report is auto-saved into IndexedDB.
|
||||
6. Images are validated, optimized, renamed, and stored locally.
|
||||
7. User marks the report ready for export.
|
||||
8. Full validation runs.
|
||||
9. App generates XLSX.
|
||||
10. App packages XLSX and images into a ZIP and downloads it locally.
|
||||
|
||||
## 8. Delivery Plan
|
||||
|
||||
### Iteration 1
|
||||
- define template JSON contract,
|
||||
- define MariaDB schema,
|
||||
- implement template read API,
|
||||
- build application shell and offline cache,
|
||||
- build report dashboard and draft persistence.
|
||||
|
||||
### Iteration 2
|
||||
- implement dynamic checklist renderer,
|
||||
- implement draft save and resume,
|
||||
- implement report switching and status management,
|
||||
- implement export-readiness validation.
|
||||
|
||||
### Iteration 3
|
||||
- implement image attach, preview, compression, and renaming,
|
||||
- implement XLSX mapping,
|
||||
- implement ZIP export,
|
||||
- run pilot tests on Windows, Android, and iOS.
|
||||
|
||||
### Iteration 4
|
||||
- harden error handling,
|
||||
- tune storage usage,
|
||||
- add admin-facing template maintenance workflow,
|
||||
- prepare phase 2 synchronization extension points.
|
||||
|
||||
## 9. Open Gaps That Still Need Definition
|
||||
|
||||
The source document is strong at the concept level, but these items still need explicit specification before implementation starts:
|
||||
- exact template JSON schema,
|
||||
- exact report numbering strategy,
|
||||
- exact Excel layout and formatting rules,
|
||||
- rule language for conditional validation,
|
||||
- whether PWA installation is required or optional,
|
||||
- retention and deletion rules for local drafts,
|
||||
- browser support baseline and test matrix,
|
||||
- authentication requirements for template administration.
|
||||
|
||||
## 10. Main Risks and Mitigations
|
||||
|
||||
### Risk: local draft loss
|
||||
Mitigation: auto-save, visible save state, export reminders, later import/export backup feature.
|
||||
|
||||
### Risk: storage pressure from images
|
||||
Mitigation: enforce compression rules, cap attachment count where needed, show storage usage warnings.
|
||||
|
||||
### Risk: template drift
|
||||
Mitigation: immutable template versions and version-bound drafts.
|
||||
|
||||
### Risk: export mismatch with current Excel expectations
|
||||
Mitigation: validate the XLSX output format early using sample reports before full UI completion.
|
||||
|
||||
## 11. Final Recommendation
|
||||
|
||||
Proceed with a phase 1 implementation based on:
|
||||
- React + TypeScript PWA frontend,
|
||||
- IndexedDB local persistence,
|
||||
- Node.js REST backend,
|
||||
- MariaDB template repository,
|
||||
- local XLSX and ZIP export.
|
||||
|
||||
This is the lowest-risk path that still satisfies the documented requirements and preserves a clean path to later server-side synchronization.
|
||||
|
||||
## 12. Best Immediate Next Steps
|
||||
|
||||
The next concrete deliverables should be:
|
||||
1. template JSON schema,
|
||||
2. MariaDB schema draft,
|
||||
3. API contract draft,
|
||||
4. report and attachment IndexedDB schema,
|
||||
5. one sample export template in XLSX format,
|
||||
6. a first implementation backlog split into frontend and backend work items.
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# Check List Proof of Concept
|
||||
|
||||
This repository contains a minimal proof-of-concept backend for the Check List hybrid reporting solution described in the project documentation.
|
||||
|
||||
## What is included
|
||||
|
||||
- Node.js REST API for template and configuration delivery
|
||||
- MariaDB schema for phase 1 configuration data
|
||||
- seed data with one sample inspection checklist template
|
||||
- lookup values, image policy, and export profile
|
||||
- setup instructions for local development
|
||||
|
||||
## Scope of this PoC
|
||||
|
||||
Included:
|
||||
- template list endpoint
|
||||
- active template endpoint
|
||||
- specific template version endpoint
|
||||
- lookup endpoints
|
||||
- image rule endpoint
|
||||
- export profile endpoint
|
||||
- generic application config endpoint
|
||||
- MariaDB schema and seed data
|
||||
|
||||
Not included:
|
||||
- report upload
|
||||
- authentication
|
||||
- admin UI
|
||||
- report draft storage backend
|
||||
- XLSX or ZIP generation
|
||||
- client-side offline application
|
||||
|
||||
The PoC keeps template content inside a JSON column to reduce initial complexity and speed up delivery. This is deliberate for phase 1 proof-of-concept work.
|
||||
|
||||
## Project structure
|
||||
|
||||
```text
|
||||
.
|
||||
├── package.json
|
||||
├── sql/
|
||||
│ ├── schema.sql
|
||||
│ └── seed.sql
|
||||
└── src/
|
||||
├── app.js
|
||||
├── server.js
|
||||
├── config/
|
||||
├── db/
|
||||
├── middleware/
|
||||
├── routes/
|
||||
├── services/
|
||||
└── utils/
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- MariaDB 10.6+
|
||||
|
||||
## Setup
|
||||
|
||||
1. Copy `.env.example` to `.env` and adjust the database credentials.
|
||||
2. Create the schema:
|
||||
|
||||
```sql
|
||||
SOURCE sql/schema.sql;
|
||||
```
|
||||
|
||||
3. Seed the sample data:
|
||||
|
||||
```sql
|
||||
SOURCE sql/seed.sql;
|
||||
```
|
||||
|
||||
4. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
5. Start the API:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## API endpoints
|
||||
|
||||
### Service health
|
||||
|
||||
`GET /api/health`
|
||||
|
||||
### Templates
|
||||
|
||||
- `GET /api/templates`
|
||||
- `GET /api/templates/incoming-inspection`
|
||||
- `GET /api/templates/incoming-inspection/versions/1`
|
||||
|
||||
### Lookups
|
||||
|
||||
- `GET /api/lookups`
|
||||
- `GET /api/lookups/pass-fail`
|
||||
|
||||
### Configuration
|
||||
|
||||
- `GET /api/config/image-rules`
|
||||
- `GET /api/config/export`
|
||||
- `GET /api/config/app-config`
|
||||
|
||||
## Example response
|
||||
|
||||
`GET /api/templates/incoming-inspection`
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "incoming-inspection",
|
||||
"name": "Incoming Inspection Checklist",
|
||||
"description": "PoC template for supplier or incoming goods quality inspection.",
|
||||
"version": 1,
|
||||
"status": "active",
|
||||
"publishedAt": "2026-04-09T10:00:00.000Z",
|
||||
"definition": {
|
||||
"templateId": "incoming-inspection",
|
||||
"templateName": "Incoming Inspection Checklist",
|
||||
"version": 1,
|
||||
"sections": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Recommended next step after this PoC
|
||||
|
||||
The next logical implementation layer is the client application that:
|
||||
- caches templates in IndexedDB,
|
||||
- renders forms dynamically from `definition`,
|
||||
- stores local drafts and image metadata,
|
||||
- applies validation rules before export,
|
||||
- generates XLSX and ZIP locally.
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "check-list-poc-api",
|
||||
"version": "0.1.0",
|
||||
"description": "Proof-of-concept backend for the Check List hybrid quality reporting solution.",
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "node --watch src/server.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.2",
|
||||
"mariadb": "^3.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
CREATE DATABASE IF NOT EXISTS check_list CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE check_list;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS templates (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_templates_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS template_versions (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
template_id BIGINT UNSIGNED NOT NULL,
|
||||
version_number INT NOT NULL,
|
||||
status ENUM('draft', 'active', 'retired') NOT NULL DEFAULT 'draft',
|
||||
definition_json JSON NOT NULL,
|
||||
published_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_template_version (template_id, version_number),
|
||||
KEY idx_template_versions_template_status (template_id, status),
|
||||
CONSTRAINT fk_template_versions_template
|
||||
FOREIGN KEY (template_id) REFERENCES templates (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lookup_sets (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_lookup_sets_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lookup_values (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
lookup_set_id BIGINT UNSIGNED NOT NULL,
|
||||
value VARCHAR(100) NOT NULL,
|
||||
label VARCHAR(200) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
is_default TINYINT(1) NOT NULL DEFAULT 0,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_lookup_value (lookup_set_id, value),
|
||||
KEY idx_lookup_values_lookup_set (lookup_set_id),
|
||||
CONSTRAINT fk_lookup_values_lookup_set
|
||||
FOREIGN KEY (lookup_set_id) REFERENCES lookup_sets (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS image_rules (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
allowed_mime_types_json JSON NOT NULL,
|
||||
max_file_size_bytes INT UNSIGNED NOT NULL,
|
||||
max_width_px INT UNSIGNED NOT NULL,
|
||||
max_height_px INT UNSIGNED NOT NULL,
|
||||
jpeg_quality INT UNSIGNED NOT NULL,
|
||||
oversize_behavior ENUM('auto_optimize', 'warn_then_optimize', 'block') NOT NULL DEFAULT 'auto_optimize',
|
||||
max_attachments_per_field INT UNSIGNED NOT NULL DEFAULT 5,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_image_rules_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS export_profiles (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
zip_image_dir VARCHAR(100) NOT NULL DEFAULT 'images',
|
||||
excel_sheet_name VARCHAR(100) NOT NULL DEFAULT 'Checklist',
|
||||
include_template_version TINYINT(1) NOT NULL DEFAULT 1,
|
||||
include_export_timestamp TINYINT(1) NOT NULL DEFAULT 1,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_export_profiles_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_config (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
config_key VARCHAR(100) NOT NULL,
|
||||
config_value_json JSON NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_app_config_key (config_key)
|
||||
);
|
||||
@@ -0,0 +1,220 @@
|
||||
USE check_list;
|
||||
|
||||
INSERT INTO templates (code, name, description)
|
||||
VALUES (
|
||||
'incoming-inspection',
|
||||
'Incoming Inspection Checklist',
|
||||
'PoC template for supplier or incoming goods quality inspection.'
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
description = VALUES(description);
|
||||
|
||||
SET @template_id = (SELECT id FROM templates WHERE code = 'incoming-inspection');
|
||||
|
||||
INSERT INTO template_versions (
|
||||
template_id,
|
||||
version_number,
|
||||
status,
|
||||
definition_json,
|
||||
published_at
|
||||
)
|
||||
VALUES (
|
||||
@template_id,
|
||||
1,
|
||||
'active',
|
||||
'{
|
||||
"templateId": "incoming-inspection",
|
||||
"templateName": "Incoming Inspection Checklist",
|
||||
"version": 1,
|
||||
"reportNumberPattern": "QC-{yyyy}-{seq:4}",
|
||||
"exportProfileCode": "default-report-export",
|
||||
"imageRuleCode": "standard-mobile-images",
|
||||
"sections": [
|
||||
{
|
||||
"id": "header",
|
||||
"title": "Report Header",
|
||||
"type": "group",
|
||||
"fields": [
|
||||
{
|
||||
"id": "reportNumber",
|
||||
"label": "Report Number",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"readOnly": false
|
||||
},
|
||||
{
|
||||
"id": "inspectionDate",
|
||||
"label": "Inspection Date",
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "supplierName",
|
||||
"label": "Supplier",
|
||||
"type": "text",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "batchNumber",
|
||||
"label": "Batch Number",
|
||||
"type": "text",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "check-items",
|
||||
"title": "Inspection Items",
|
||||
"type": "group",
|
||||
"fields": [
|
||||
{
|
||||
"id": "packagingCondition",
|
||||
"label": "Packaging Condition",
|
||||
"type": "lookup",
|
||||
"lookupCode": "pass-fail",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "labelCheck",
|
||||
"label": "Label Verification",
|
||||
"type": "lookup",
|
||||
"lookupCode": "pass-fail",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "quantityVerified",
|
||||
"label": "Quantity Verified",
|
||||
"type": "number",
|
||||
"required": true,
|
||||
"validation": {
|
||||
"min": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "damageFound",
|
||||
"label": "Visible Damage Found",
|
||||
"type": "checkbox",
|
||||
"required": false,
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "damagePhoto",
|
||||
"label": "Damage Photo",
|
||||
"type": "attachment",
|
||||
"requiredWhen": {
|
||||
"field": "damageFound",
|
||||
"equals": true
|
||||
},
|
||||
"maxAttachments": 3
|
||||
},
|
||||
{
|
||||
"id": "inspectorComment",
|
||||
"label": "Inspector Comment",
|
||||
"type": "comment",
|
||||
"required": false,
|
||||
"maxLength": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}',
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
status = VALUES(status),
|
||||
definition_json = VALUES(definition_json),
|
||||
published_at = VALUES(published_at);
|
||||
|
||||
INSERT INTO lookup_sets (code, name)
|
||||
VALUES
|
||||
('pass-fail', 'Pass/Fail'),
|
||||
('draft-status', 'Draft Status')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name);
|
||||
|
||||
SET @pass_fail_id = (SELECT id FROM lookup_sets WHERE code = 'pass-fail');
|
||||
SET @draft_status_id = (SELECT id FROM lookup_sets WHERE code = 'draft-status');
|
||||
|
||||
INSERT INTO lookup_values (lookup_set_id, value, label, sort_order, is_default)
|
||||
VALUES
|
||||
(@pass_fail_id, 'pass', 'Pass', 1, 1),
|
||||
(@pass_fail_id, 'fail', 'Fail', 2, 0),
|
||||
(@draft_status_id, 'draft', 'Draft', 1, 1),
|
||||
(@draft_status_id, 'in_progress', 'In Progress', 2, 0),
|
||||
(@draft_status_id, 'ready_for_export', 'Ready for Export', 3, 0),
|
||||
(@draft_status_id, 'exported', 'Exported', 4, 0),
|
||||
(@draft_status_id, 'archived', 'Archived', 5, 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
label = VALUES(label),
|
||||
sort_order = VALUES(sort_order),
|
||||
is_default = VALUES(is_default);
|
||||
|
||||
INSERT INTO image_rules (
|
||||
code,
|
||||
name,
|
||||
allowed_mime_types_json,
|
||||
max_file_size_bytes,
|
||||
max_width_px,
|
||||
max_height_px,
|
||||
jpeg_quality,
|
||||
oversize_behavior,
|
||||
max_attachments_per_field,
|
||||
is_active
|
||||
)
|
||||
VALUES (
|
||||
'standard-mobile-images',
|
||||
'Standard Mobile Image Policy',
|
||||
'["image/jpeg", "image/png", "image/webp"]',
|
||||
5242880,
|
||||
1920,
|
||||
1920,
|
||||
82,
|
||||
'auto_optimize',
|
||||
5,
|
||||
1
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
allowed_mime_types_json = VALUES(allowed_mime_types_json),
|
||||
max_file_size_bytes = VALUES(max_file_size_bytes),
|
||||
max_width_px = VALUES(max_width_px),
|
||||
max_height_px = VALUES(max_height_px),
|
||||
jpeg_quality = VALUES(jpeg_quality),
|
||||
oversize_behavior = VALUES(oversize_behavior),
|
||||
max_attachments_per_field = VALUES(max_attachments_per_field),
|
||||
is_active = VALUES(is_active);
|
||||
|
||||
INSERT INTO export_profiles (
|
||||
code,
|
||||
name,
|
||||
zip_image_dir,
|
||||
excel_sheet_name,
|
||||
include_template_version,
|
||||
include_export_timestamp,
|
||||
is_active
|
||||
)
|
||||
VALUES (
|
||||
'default-report-export',
|
||||
'Default Report Export',
|
||||
'images',
|
||||
'Checklist',
|
||||
1,
|
||||
1,
|
||||
1
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
zip_image_dir = VALUES(zip_image_dir),
|
||||
excel_sheet_name = VALUES(excel_sheet_name),
|
||||
include_template_version = VALUES(include_template_version),
|
||||
include_export_timestamp = VALUES(include_export_timestamp),
|
||||
is_active = VALUES(is_active);
|
||||
|
||||
INSERT INTO app_config (config_key, config_value_json)
|
||||
VALUES
|
||||
('autosave', '{"enabled": true, "intervalSeconds": 20}'),
|
||||
('offlineCache', '{"templateTtlHours": 24, "refreshOnStartup": true}'),
|
||||
('reportStatuses', '["draft", "in_progress", "ready_for_export", "exported", "archived"]')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
config_value_json = VALUES(config_value_json);
|
||||
@@ -0,0 +1,31 @@
|
||||
import cors from 'cors';
|
||||
import express from 'express';
|
||||
|
||||
import { errorHandler, notFoundHandler } from './middleware/errorHandler.js';
|
||||
import configRoutes from './routes/configRoutes.js';
|
||||
import healthRoutes from './routes/healthRoutes.js';
|
||||
import lookupRoutes from './routes/lookupRoutes.js';
|
||||
import templateRoutes from './routes/templateRoutes.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
app.get('/', (_req, res) => {
|
||||
res.json({
|
||||
service: 'check-list-poc-api',
|
||||
version: '0.1.0',
|
||||
description: 'PoC API for template and configuration delivery.'
|
||||
});
|
||||
});
|
||||
|
||||
app.use('/api/health', healthRoutes);
|
||||
app.use('/api/templates', templateRoutes);
|
||||
app.use('/api/lookups', lookupRoutes);
|
||||
app.use('/api/config', configRoutes);
|
||||
|
||||
app.use(notFoundHandler);
|
||||
app.use(errorHandler);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,23 @@
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const requiredKeys = ['DB_HOST', 'DB_PORT', 'DB_NAME', 'DB_USER', 'DB_PASSWORD'];
|
||||
|
||||
for (const key of requiredKeys) {
|
||||
if (!process.env[key]) {
|
||||
throw new Error(`Missing required environment variable: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const env = {
|
||||
port: Number(process.env.PORT || 3000),
|
||||
db: {
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
database: process.env.DB_NAME,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
connectionLimit: Number(process.env.DB_CONNECTION_LIMIT || 5)
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import mariadb from 'mariadb';
|
||||
|
||||
import { env } from '../config/env.js';
|
||||
|
||||
const pool = mariadb.createPool({
|
||||
host: env.db.host,
|
||||
port: env.db.port,
|
||||
database: env.db.database,
|
||||
user: env.db.user,
|
||||
password: env.db.password,
|
||||
connectionLimit: env.db.connectionLimit,
|
||||
bigIntAsNumber: true
|
||||
});
|
||||
|
||||
export async function query(sql, params = []) {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
connection = await pool.getConnection();
|
||||
return await connection.query(sql, params);
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function closePool() {
|
||||
await pool.end();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export function notFoundHandler(_req, res) {
|
||||
res.status(404).json({ message: 'Route not found.' });
|
||||
}
|
||||
|
||||
export function errorHandler(error, _req, res, _next) {
|
||||
const statusCode = error.statusCode || 500;
|
||||
|
||||
if (statusCode >= 500) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
res.status(statusCode).json({
|
||||
message: error.message || 'Unexpected server error.'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
import {
|
||||
getAppConfig,
|
||||
getExportProfile,
|
||||
getImageRules
|
||||
} from '../services/configService.js';
|
||||
import { asyncHandler } from '../utils/asyncHandler.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/image-rules',
|
||||
asyncHandler(async (_req, res) => {
|
||||
const imageRules = await getImageRules();
|
||||
|
||||
if (!imageRules) {
|
||||
return res.status(404).json({ message: 'Image rules not found.' });
|
||||
}
|
||||
|
||||
res.json(imageRules);
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/export',
|
||||
asyncHandler(async (_req, res) => {
|
||||
const exportProfile = await getExportProfile();
|
||||
|
||||
if (!exportProfile) {
|
||||
return res.status(404).json({ message: 'Export profile not found.' });
|
||||
}
|
||||
|
||||
res.json(exportProfile);
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/app-config',
|
||||
asyncHandler(async (_req, res) => {
|
||||
const config = await getAppConfig();
|
||||
res.json({ items: config });
|
||||
})
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
import { query } from '../db/pool.js';
|
||||
import { asyncHandler } from '../utils/asyncHandler.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (_req, res) => {
|
||||
await query('SELECT 1 AS ok');
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
service: 'check-list-poc-api',
|
||||
database: 'connected'
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
import { getLookup, listLookups } from '../services/lookupService.js';
|
||||
import { asyncHandler } from '../utils/asyncHandler.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (_req, res) => {
|
||||
const lookups = await listLookups();
|
||||
res.json({ items: lookups });
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:lookupCode',
|
||||
asyncHandler(async (req, res) => {
|
||||
const lookup = await getLookup(req.params.lookupCode);
|
||||
|
||||
if (!lookup) {
|
||||
return res.status(404).json({ message: 'Lookup not found.' });
|
||||
}
|
||||
|
||||
return res.json(lookup);
|
||||
})
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
import {
|
||||
getActiveTemplate,
|
||||
getTemplateVersion,
|
||||
listTemplates
|
||||
} from '../services/templateService.js';
|
||||
import { asyncHandler } from '../utils/asyncHandler.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (_req, res) => {
|
||||
const templates = await listTemplates();
|
||||
res.json({ items: templates });
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:templateCode',
|
||||
asyncHandler(async (req, res) => {
|
||||
const template = await getActiveTemplate(req.params.templateCode);
|
||||
|
||||
if (!template) {
|
||||
return res.status(404).json({ message: 'Template not found.' });
|
||||
}
|
||||
|
||||
return res.json(template);
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:templateCode/versions/:versionNumber',
|
||||
asyncHandler(async (req, res) => {
|
||||
const template = await getTemplateVersion(
|
||||
req.params.templateCode,
|
||||
req.params.versionNumber
|
||||
);
|
||||
|
||||
if (!template) {
|
||||
return res.status(404).json({ message: 'Template version not found.' });
|
||||
}
|
||||
|
||||
return res.json(template);
|
||||
})
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,29 @@
|
||||
import app from './app.js';
|
||||
import { env } from './config/env.js';
|
||||
import { closePool, query } from './db/pool.js';
|
||||
|
||||
async function startServer() {
|
||||
await query('SELECT 1 AS ok');
|
||||
|
||||
const server = app.listen(env.port, () => {
|
||||
console.log(`Check List PoC API listening on port ${env.port}`);
|
||||
});
|
||||
|
||||
async function shutdown(signal) {
|
||||
console.log(`Received ${signal}, shutting down...`);
|
||||
server.close(async () => {
|
||||
await closePool();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => void shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
||||
}
|
||||
|
||||
startServer().catch(async (error) => {
|
||||
console.error('Failed to start server');
|
||||
console.error(error);
|
||||
await closePool();
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { query } from '../db/pool.js';
|
||||
import { parseJsonColumn } from '../utils/json.js';
|
||||
|
||||
export async function getImageRules() {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
code,
|
||||
name,
|
||||
allowed_mime_types_json AS allowedMimeTypes,
|
||||
max_file_size_bytes AS maxFileSizeBytes,
|
||||
max_width_px AS maxWidthPx,
|
||||
max_height_px AS maxHeightPx,
|
||||
jpeg_quality AS jpegQuality,
|
||||
oversize_behavior AS oversizeBehavior,
|
||||
max_attachments_per_field AS maxAttachmentsPerField
|
||||
FROM image_rules
|
||||
WHERE is_active = 1
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`
|
||||
);
|
||||
|
||||
if (!rows.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...rows[0],
|
||||
allowedMimeTypes: parseJsonColumn(rows[0].allowedMimeTypes, [])
|
||||
};
|
||||
}
|
||||
|
||||
export async function getExportProfile() {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
code,
|
||||
name,
|
||||
zip_image_dir AS zipImageDir,
|
||||
excel_sheet_name AS excelSheetName,
|
||||
include_template_version AS includeTemplateVersion,
|
||||
include_export_timestamp AS includeExportTimestamp
|
||||
FROM export_profiles
|
||||
WHERE is_active = 1
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
`
|
||||
);
|
||||
|
||||
return rows.length ? rows[0] : null;
|
||||
}
|
||||
|
||||
export async function getAppConfig() {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
config_key AS configKey,
|
||||
config_value_json AS configValue
|
||||
FROM app_config
|
||||
ORDER BY config_key ASC
|
||||
`
|
||||
);
|
||||
|
||||
return rows.map((row) => ({
|
||||
key: row.configKey,
|
||||
value: parseJsonColumn(row.configValue)
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { query } from '../db/pool.js';
|
||||
|
||||
function groupLookups(rows) {
|
||||
const lookups = new Map();
|
||||
|
||||
for (const row of rows) {
|
||||
if (!lookups.has(row.lookupCode)) {
|
||||
lookups.set(row.lookupCode, {
|
||||
code: row.lookupCode,
|
||||
name: row.lookupName,
|
||||
values: []
|
||||
});
|
||||
}
|
||||
|
||||
if (row.value !== null) {
|
||||
lookups.get(row.lookupCode).values.push({
|
||||
value: row.value,
|
||||
label: row.label,
|
||||
sortOrder: row.sortOrder,
|
||||
isDefault: Boolean(row.isDefault)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(lookups.values());
|
||||
}
|
||||
|
||||
export async function listLookups() {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
ls.code AS lookupCode,
|
||||
ls.name AS lookupName,
|
||||
lv.value,
|
||||
lv.label,
|
||||
lv.sort_order AS sortOrder,
|
||||
lv.is_default AS isDefault
|
||||
FROM lookup_sets ls
|
||||
LEFT JOIN lookup_values lv
|
||||
ON lv.lookup_set_id = ls.id
|
||||
AND lv.is_active = 1
|
||||
WHERE ls.is_active = 1
|
||||
ORDER BY ls.code ASC, lv.sort_order ASC, lv.id ASC
|
||||
`
|
||||
);
|
||||
|
||||
return groupLookups(rows);
|
||||
}
|
||||
|
||||
export async function getLookup(lookupCode) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
ls.code AS lookupCode,
|
||||
ls.name AS lookupName,
|
||||
lv.value,
|
||||
lv.label,
|
||||
lv.sort_order AS sortOrder,
|
||||
lv.is_default AS isDefault
|
||||
FROM lookup_sets ls
|
||||
LEFT JOIN lookup_values lv
|
||||
ON lv.lookup_set_id = ls.id
|
||||
AND lv.is_active = 1
|
||||
WHERE ls.code = ?
|
||||
AND ls.is_active = 1
|
||||
ORDER BY lv.sort_order ASC, lv.id ASC
|
||||
`,
|
||||
[lookupCode]
|
||||
);
|
||||
|
||||
if (!rows.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return groupLookups(rows)[0];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { query } from '../db/pool.js';
|
||||
import { parseJsonColumn } from '../utils/json.js';
|
||||
|
||||
function mapTemplateRow(row) {
|
||||
return {
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
version: row.versionNumber,
|
||||
status: row.status,
|
||||
publishedAt: row.publishedAt,
|
||||
definition: parseJsonColumn(row.definitionJson)
|
||||
};
|
||||
}
|
||||
|
||||
export async function listTemplates() {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.code,
|
||||
t.name,
|
||||
t.description,
|
||||
tv.version_number AS versionNumber,
|
||||
tv.status,
|
||||
tv.published_at AS publishedAt
|
||||
FROM templates t
|
||||
INNER JOIN template_versions tv
|
||||
ON tv.template_id = t.id
|
||||
AND tv.status = 'active'
|
||||
ORDER BY t.name ASC
|
||||
`
|
||||
);
|
||||
|
||||
return rows.map((row) => ({
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
activeVersion: row.versionNumber,
|
||||
publishedAt: row.publishedAt
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getActiveTemplate(templateCode) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.code,
|
||||
t.name,
|
||||
t.description,
|
||||
tv.version_number AS versionNumber,
|
||||
tv.status,
|
||||
tv.published_at AS publishedAt,
|
||||
tv.definition_json AS definitionJson
|
||||
FROM templates t
|
||||
INNER JOIN template_versions tv
|
||||
ON tv.template_id = t.id
|
||||
AND tv.status = 'active'
|
||||
WHERE t.code = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[templateCode]
|
||||
);
|
||||
|
||||
return rows.length ? mapTemplateRow(rows[0]) : null;
|
||||
}
|
||||
|
||||
export async function getTemplateVersion(templateCode, versionNumber) {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.code,
|
||||
t.name,
|
||||
t.description,
|
||||
tv.version_number AS versionNumber,
|
||||
tv.status,
|
||||
tv.published_at AS publishedAt,
|
||||
tv.definition_json AS definitionJson
|
||||
FROM templates t
|
||||
INNER JOIN template_versions tv
|
||||
ON tv.template_id = t.id
|
||||
WHERE t.code = ?
|
||||
AND tv.version_number = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
[templateCode, Number(versionNumber)]
|
||||
);
|
||||
|
||||
return rows.length ? mapTemplateRow(rows[0]) : null;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export function asyncHandler(handler) {
|
||||
return async function wrappedHandler(req, res, next) {
|
||||
try {
|
||||
await handler(req, res, next);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export function parseJsonColumn(value, fallback = null) {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user