Consolidate Reports and Data Entry apps into a single Vue.js SPA with: Architecture: - Module-based structure with lazy-loaded routes (@reports, @data-entry) - Error boundaries per module to prevent cascade failures - Dual API proxy in Vite for microservices (reports:8001, data-entry:8003) - Pinia store factories for shared auth, company, and period stores - Vite path aliases for clear module boundaries (@shared, @reports, @data-entry) Service Management: - Granular service control scripts (backend-reports.sh, backend-data-entry.sh, bot.sh, frontend.sh) - 87% faster frontend restart: 7s vs 53s full restart - 38% faster full startup: 33s vs 53s via parallel backend initialization - Enhanced start-dev.sh with proper service timeouts (OCR: 30s, Vite: 15s, Bot: 10s) - status.sh for comprehensive health checks Features: - Auto-select first company on login with period auto-load - Hamburger menu with feature toggle support - JWT token auto-injection via axios interceptors - Unified header with company/period selectors - IIS web.config for production deployment with multi-API routing UX Improvements: - Vue watchers for reactive company/period loading - Lazy store initialization with graceful error handling - Period persistence per user+company in localStorage - Feature flags for optional modules Deployment: - Single IIS site serves unified frontend with API proxy rules - Maintains separate backend processes for microservices - Windows line ending fixes (.env CRLF → LF conversion) Stats: 112 files changed, 38,342 insertions(+), 2,342 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
700 lines
17 KiB
Markdown
700 lines
17 KiB
Markdown
# ROA2WEB - Unified Application
|
|
|
|
> **Single-Page Application** combining Reports and Data Entry modules into one unified frontend.
|
|
|
|
## 📋 Overview
|
|
|
|
This is the **unified frontend** for the ROA2WEB ERP system, consolidating two previously separate applications:
|
|
- **Reports Module** - Read-only financial reports from Oracle database
|
|
- **Data Entry Module** - Fiscal receipt management with approval workflow
|
|
|
|
### Key Features
|
|
|
|
- ✅ **Single Build & Deployment** - One IIS site instead of two
|
|
- ✅ **Module Isolation** - Error boundaries prevent crashes from propagating
|
|
- ✅ **Lazy Loading** - Modules load on-demand for optimal performance
|
|
- ✅ **Unified Navigation** - Seamless switching between Reports and Data Entry
|
|
- ✅ **Shared Authentication** - Single login for both modules
|
|
- ✅ **Feature Flags** - Enable/disable modules via configuration
|
|
|
|
### Technology Stack
|
|
|
|
- **Frontend**: Vue 3 (Composition API), Vite
|
|
- **UI Library**: PrimeVue (saga-blue theme)
|
|
- **State Management**: Pinia
|
|
- **Routing**: Vue Router (with lazy loading)
|
|
- **HTTP Client**: Axios
|
|
- **Charts**: Chart.js, vue-chartjs
|
|
- **Export**: jsPDF, xlsx
|
|
|
|
---
|
|
|
|
## 🚀 Quick Start
|
|
|
|
### Prerequisites
|
|
|
|
- **Node.js**: 16+ (18+ recommended)
|
|
- **npm**: 8+
|
|
- **Backend Services**:
|
|
- Reports API running on `http://localhost:8001`
|
|
- Data Entry API running on `http://localhost:8003`
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
```
|
|
|
|
### Development
|
|
|
|
#### Option 1: Start All Services at Once (Recommended)
|
|
|
|
```bash
|
|
# Start all services (backends + frontend) in DEV mode
|
|
./start-dev.sh
|
|
|
|
# Or for TEST environment
|
|
./start-test.sh
|
|
```
|
|
|
|
This will start:
|
|
- Reports Backend on port **8001** (with auto-reload in dev mode)
|
|
- Data Entry Backend on port **8003** (with auto-reload in dev mode)
|
|
- Telegram Bot on port **8002**
|
|
- Unified Frontend on port **3000**
|
|
- SSH Tunnel for Oracle database
|
|
|
|
#### Option 2: Start Services Manually
|
|
|
|
```bash
|
|
# Terminal 1 - Reports Backend
|
|
cd reports-app/backend
|
|
source venv/bin/activate
|
|
uvicorn app.main:app --reload --port 8001
|
|
|
|
# Terminal 2 - Data Entry Backend
|
|
cd data-entry-app/backend
|
|
source venv/bin/activate
|
|
uvicorn app.main:app --reload --port 8003
|
|
|
|
# Terminal 3 - Unified Frontend
|
|
npm run dev
|
|
```
|
|
|
|
Access the app at: **http://localhost:3000**
|
|
|
|
### Production Build
|
|
|
|
```bash
|
|
# Build for production
|
|
npm run build
|
|
|
|
# Preview production build locally
|
|
npm run preview
|
|
```
|
|
|
|
Build output will be in the `dist/` directory.
|
|
|
|
---
|
|
|
|
## 🏗️ Architecture
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── main.js # App entry point
|
|
├── App.vue # Root component with unified menu
|
|
│
|
|
├── router/
|
|
│ └── index.js # Unified router with lazy loading
|
|
│
|
|
├── config/
|
|
│ ├── menu.js # Menu configuration
|
|
│ └── features.js # Feature flags
|
|
│
|
|
├── modules/
|
|
│ ├── reports/ # REPORTS MODULE (isolated)
|
|
│ │ ├── ReportsLayout.vue # Error boundary wrapper
|
|
│ │ ├── views/ # Dashboard, Invoices, Bank/Cash, etc.
|
|
│ │ ├── components/ # Reports-specific components
|
|
│ │ ├── stores/ # Module stores + sharedStores.js
|
|
│ │ └── services/
|
|
│ │ └── api.js # API client (/api/reports)
|
|
│ │
|
|
│ └── data-entry/ # DATA ENTRY MODULE (isolated)
|
|
│ ├── DataEntryLayout.vue # Error boundary wrapper
|
|
│ ├── views/receipts/ # Receipts List, Create
|
|
│ ├── components/ocr/ # OCR components
|
|
│ ├── stores/ # Module stores + sharedStores.js
|
|
│ └── services/
|
|
│ └── api.js # API client (/api/data-entry)
|
|
│
|
|
├── shared/ # SHARED ACROSS MODULES
|
|
│ ├── components/ # LoginView, Selectors, Layout
|
|
│ │ ├── LoginView.vue
|
|
│ │ ├── CompanySelector.vue
|
|
│ │ ├── PeriodSelector.vue
|
|
│ │ ├── ErrorBoundary.vue
|
|
│ │ └── layout/
|
|
│ │ ├── AppHeader.vue
|
|
│ │ └── SlideMenu.vue
|
|
│ ├── stores/ # Shared store factories
|
|
│ │ ├── auth.js
|
|
│ │ ├── companies.js
|
|
│ │ └── accountingPeriod.js
|
|
│ └── styles/ # Shared CSS
|
|
│
|
|
└── assets/
|
|
└── css/ # Global CSS design system
|
|
├── core/ # Design tokens
|
|
├── components/ # Component patterns
|
|
├── patterns/ # Interactive patterns
|
|
├── layout/ # Page structure
|
|
├── utilities/ # Utility classes
|
|
└── vendor/ # PrimeVue overrides
|
|
```
|
|
|
|
### Module Isolation
|
|
|
|
Each module is wrapped in an **ErrorBoundary** component:
|
|
|
|
```vue
|
|
<!-- ReportsLayout.vue -->
|
|
<template>
|
|
<ErrorBoundary module-name="Rapoarte">
|
|
<router-view />
|
|
</ErrorBoundary>
|
|
</template>
|
|
```
|
|
|
|
**Benefits**:
|
|
- Errors in one module don't crash the entire app
|
|
- Other modules continue to function normally
|
|
- User-friendly error messages with recovery options
|
|
|
|
### Lazy Loading Strategy
|
|
|
|
Modules are loaded **on-demand** using dynamic imports:
|
|
|
|
```javascript
|
|
// Reports module loaded only when user navigates to /reports/*
|
|
{
|
|
path: '/reports',
|
|
component: () => import('@/modules/reports/ReportsLayout.vue'),
|
|
children: [
|
|
{
|
|
path: 'dashboard',
|
|
component: () => import('@reports/views/DashboardView.vue')
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Shared Store Pattern
|
|
|
|
Shared stores use a **factory pattern** to work with both modules:
|
|
|
|
```javascript
|
|
// src/shared/stores/auth.js
|
|
export function createAuthStore(apiService) {
|
|
return defineStore('auth', () => {
|
|
// Store implementation using provided apiService
|
|
})
|
|
}
|
|
|
|
// src/modules/reports/stores/sharedStores.js
|
|
import { createAuthStore } from '@shared/stores/auth'
|
|
import api from '@reports/services/api'
|
|
|
|
export const useAuthStore = createAuthStore(api) // Binds to Reports API
|
|
```
|
|
|
|
Each module instantiates shared stores with its own API service.
|
|
|
|
---
|
|
|
|
## 🔗 URL Structure
|
|
|
|
### Public Routes
|
|
- `/login` - Login page
|
|
|
|
### Reports Module
|
|
- `/reports/dashboard` - Financial dashboard
|
|
- `/reports/invoices` - Invoices view
|
|
- `/reports/bank-cash` - Bank & cash register
|
|
- `/reports/trial-balance` - Trial balance
|
|
- `/reports/telegram` - Telegram bot management
|
|
- `/reports/cache-stats` - Cache statistics
|
|
|
|
### Data Entry Module
|
|
- `/data-entry` - Receipts list
|
|
- `/data-entry/create` - Create new receipt
|
|
- `/data-entry/:id` - View receipt details
|
|
- `/data-entry/:id/edit` - Edit receipt
|
|
|
|
### Redirects
|
|
- `/` → `/reports/dashboard` (default)
|
|
|
|
---
|
|
|
|
## 🚢 Deployment
|
|
|
|
### IIS Configuration (Windows Production)
|
|
|
|
#### 1. Build the Application
|
|
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
#### 2. Deploy to IIS
|
|
|
|
Copy the `dist/` folder contents to your IIS site directory (e.g., `C:\inetpub\wwwroot\roa2web\`).
|
|
|
|
#### 3. Configure URL Rewriting
|
|
|
|
The `public/web.config` file (copied to `dist/`) contains the necessary IIS rewrite rules:
|
|
|
|
- **API Proxies**:
|
|
- `/api/reports/*` → `http://localhost:8001/api/*`
|
|
- `/api/data-entry/*` → `http://localhost:8003/api/*`
|
|
- `/uploads/*` → `http://localhost:8003/uploads/*`
|
|
|
|
- **SPA Fallback**: All other routes → `/index.html`
|
|
|
|
#### 4. Start Backend Services
|
|
|
|
Ensure both backend services are running:
|
|
- Reports API (port 8001) - via NSSM or similar
|
|
- Data Entry API (port 8003) - via NSSM or similar
|
|
|
|
#### 5. IIS Application Pool Settings
|
|
|
|
- **.NET CLR Version**: No Managed Code
|
|
- **Pipeline Mode**: Integrated
|
|
- **Identity**: ApplicationPoolIdentity (or custom service account)
|
|
|
|
### Nginx Configuration (Linux Production)
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name your-domain.com;
|
|
|
|
root /var/www/roa2web;
|
|
index index.html;
|
|
|
|
# API Proxies
|
|
location /api/reports/ {
|
|
proxy_pass http://localhost:8001/api/;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
}
|
|
|
|
location /api/data-entry/ {
|
|
proxy_pass http://localhost:8003/api/;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
}
|
|
|
|
location /uploads/ {
|
|
proxy_pass http://localhost:8003/uploads/;
|
|
}
|
|
|
|
# SPA Fallback
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
# Cache static assets
|
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎛️ Feature Flags
|
|
|
|
Feature flags allow enabling/disabling modules without redeployment.
|
|
|
|
### Configuration
|
|
|
|
Edit `src/config/features.js`:
|
|
|
|
```javascript
|
|
export const features = {
|
|
reports: {
|
|
enabled: true,
|
|
modules: {
|
|
dashboard: true,
|
|
invoices: true,
|
|
bankCash: true,
|
|
trialBalance: true,
|
|
telegram: true,
|
|
cacheStats: true
|
|
}
|
|
},
|
|
dataEntry: {
|
|
enabled: true,
|
|
modules: {
|
|
receipts: true,
|
|
ocr: true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
Override via `.env` file:
|
|
|
|
```bash
|
|
# Disable Data Entry module
|
|
VITE_FEATURE_DATA_ENTRY=false
|
|
|
|
# Disable Reports module
|
|
VITE_FEATURE_REPORTS=false
|
|
```
|
|
|
|
### Usage
|
|
|
|
The menu automatically filters disabled modules:
|
|
|
|
```javascript
|
|
import { isFeatureEnabled } from '@/config/features'
|
|
|
|
if (isFeatureEnabled('reports')) {
|
|
// Show Reports menu items
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ Development Guide
|
|
|
|
### Adding a New Route
|
|
|
|
#### 1. Create the View Component
|
|
|
|
```bash
|
|
# For Reports module
|
|
src/modules/reports/views/NewView.vue
|
|
|
|
# For Data Entry module
|
|
src/modules/data-entry/views/NewView.vue
|
|
```
|
|
|
|
#### 2. Register in Router
|
|
|
|
Edit `src/router/index.js`:
|
|
|
|
```javascript
|
|
{
|
|
path: '/reports/new-feature',
|
|
name: 'NewFeature',
|
|
component: () => import('@reports/views/NewView.vue'),
|
|
meta: { requiresAuth: true, title: 'New Feature - ROA2WEB' }
|
|
}
|
|
```
|
|
|
|
#### 3. Add to Menu
|
|
|
|
Edit `src/config/menu.js`:
|
|
|
|
```javascript
|
|
{
|
|
to: '/reports/new-feature',
|
|
icon: 'pi pi-star',
|
|
label: 'New Feature'
|
|
}
|
|
```
|
|
|
|
### Adding a New Component
|
|
|
|
#### Module-Specific Component
|
|
|
|
```bash
|
|
# Reports module
|
|
src/modules/reports/components/MyComponent.vue
|
|
|
|
# Data Entry module
|
|
src/modules/data-entry/components/MyComponent.vue
|
|
```
|
|
|
|
Import using module alias:
|
|
```javascript
|
|
import MyComponent from '@reports/components/MyComponent.vue'
|
|
```
|
|
|
|
#### Shared Component
|
|
|
|
```bash
|
|
src/shared/components/MySharedComponent.vue
|
|
```
|
|
|
|
Import using shared alias:
|
|
```javascript
|
|
import MySharedComponent from '@shared/components/MySharedComponent.vue'
|
|
```
|
|
|
|
### Using the CSS Design System
|
|
|
|
The unified app uses the **Reports App CSS architecture**. See `docs/ONBOARDING_CSS.md` for a complete guide.
|
|
|
|
#### Quick Reference
|
|
|
|
**Design Tokens** (`src/assets/css/core/tokens.css`):
|
|
```css
|
|
var(--color-primary) /* #2563eb - Primary blue */
|
|
var(--color-success) /* #16a34a - Success green */
|
|
var(--color-danger) /* #dc2626 - Danger red */
|
|
var(--spacing-md) /* 1rem - Medium spacing */
|
|
var(--radius-md) /* 0.5rem - Medium border radius */
|
|
```
|
|
|
|
**Component Patterns** (`src/assets/css/components/`):
|
|
```html
|
|
<!-- Card pattern -->
|
|
<div class="roa-card">
|
|
<div class="card-header">Title</div>
|
|
<div class="card-body">Content</div>
|
|
</div>
|
|
|
|
<!-- Badge pattern -->
|
|
<span class="roa-badge-success">Active</span>
|
|
<span class="roa-badge-warning">Pending</span>
|
|
|
|
<!-- Stats pattern -->
|
|
<div class="roa-metric">
|
|
<div class="metric-value">1,234</div>
|
|
<div class="metric-label">Total</div>
|
|
</div>
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use global patterns from `src/assets/css/`
|
|
- ✅ Use design tokens for colors, spacing, typography
|
|
- ❌ Don't create new CSS files without checking existing patterns
|
|
- ❌ Don't use `<style scoped>` in components (use global classes)
|
|
- ❌ Don't hardcode colors or spacing values
|
|
|
|
### Adding a Module Store
|
|
|
|
#### 1. Create Store File
|
|
|
|
```javascript
|
|
// src/modules/reports/stores/myStore.js
|
|
import { defineStore } from 'pinia'
|
|
import api from '@reports/services/api'
|
|
|
|
export const useMyStore = defineStore('my-store', () => {
|
|
const data = ref([])
|
|
|
|
const fetchData = async () => {
|
|
const response = await api.get('/endpoint')
|
|
data.value = response.data
|
|
}
|
|
|
|
return { data, fetchData }
|
|
})
|
|
```
|
|
|
|
#### 2. Use in Component
|
|
|
|
```vue
|
|
<script setup>
|
|
import { useMyStore } from '@reports/stores/myStore'
|
|
|
|
const myStore = useMyStore()
|
|
await myStore.fetchData()
|
|
</script>
|
|
```
|
|
|
|
### Working with Shared Stores
|
|
|
|
Import from the module's `sharedStores.js`:
|
|
|
|
```javascript
|
|
// ✅ CORRECT
|
|
import { useAuthStore } from '@reports/stores/sharedStores'
|
|
|
|
// ❌ WRONG - Don't import directly from @shared
|
|
import { createAuthStore } from '@shared/stores/auth'
|
|
```
|
|
|
|
This ensures the store is bound to the correct API service.
|
|
|
|
---
|
|
|
|
## 🧪 Testing
|
|
|
|
### Manual Testing Checklist
|
|
|
|
See **Task 28** in the implementation plan for the complete E2E testing checklist.
|
|
|
|
#### Quick Smoke Test
|
|
|
|
1. **Start dev server**: `npm run dev`
|
|
2. **Login**: Navigate to `/login` and authenticate
|
|
3. **Reports**: Click through all Reports menu items
|
|
4. **Data Entry**: Navigate to Data Entry module
|
|
5. **Module Switching**: Switch between modules - verify state preserved
|
|
6. **Logout**: Verify redirect and state cleared
|
|
|
|
### E2E Tests (Playwright)
|
|
|
|
```bash
|
|
# Run E2E tests
|
|
npm run test:e2e
|
|
```
|
|
|
|
Tests are located in `tests/e2e/` (to be added).
|
|
|
|
---
|
|
|
|
## 📦 Build Output
|
|
|
|
### Bundle Splitting
|
|
|
|
Production build creates optimized chunks:
|
|
|
|
```
|
|
dist/assets/
|
|
vendor-core.[hash].js # ~150KB - Vue, Router, Pinia
|
|
vendor-primevue.[hash].js # ~200KB - PrimeVue components
|
|
vendor-utils.[hash].js # ~80KB - Axios, date-fns
|
|
vendor-charts.[hash].js # ~150KB - Chart.js (lazy)
|
|
vendor-export.[hash].js # ~200KB - XLSX, jsPDF (lazy)
|
|
reports.[hash].js # ~150KB - Reports module (lazy)
|
|
data-entry.[hash].js # ~100KB - Data Entry module (lazy)
|
|
main.[hash].js # ~50KB - App shell
|
|
main.[hash].css # ~80KB - Global CSS
|
|
```
|
|
|
|
**Total initial load**: ~500KB (gzipped: ~200KB)
|
|
- Only loads core vendors + app shell
|
|
- Modules load on-demand when navigated to
|
|
|
|
### Performance Targets
|
|
|
|
- ✅ Initial load: < 2 seconds (3G connection)
|
|
- ✅ Module switching: < 500ms (cached) / < 1s (first load)
|
|
- ✅ Lighthouse Performance Score: ≥ 90
|
|
|
|
---
|
|
|
|
## 🔧 Configuration Files
|
|
|
|
### `vite.config.js`
|
|
|
|
- **Aliases**: `@`, `@shared`, `@reports`, `@data-entry`
|
|
- **Proxy**: Dual backend proxy for development
|
|
- **Build**: Manual chunk splitting, source maps, cache busting
|
|
|
|
### `.env.example`
|
|
|
|
Template for environment variables:
|
|
|
|
```bash
|
|
# API URLs (development - default values)
|
|
VITE_REPORTS_API_URL=http://localhost:8001/api
|
|
VITE_DATA_ENTRY_API_URL=http://localhost:8003/api
|
|
|
|
# Feature flags
|
|
VITE_FEATURE_REPORTS=true
|
|
VITE_FEATURE_DATA_ENTRY=true
|
|
```
|
|
|
|
Copy to `.env` and customize as needed.
|
|
|
|
### `package.json`
|
|
|
|
Merged dependencies from both original apps with de-duplication.
|
|
|
|
---
|
|
|
|
## 🐛 Troubleshooting
|
|
|
|
### Dev Server Won't Start
|
|
|
|
**Problem**: `npm run dev` fails or shows EADDRINUSE error.
|
|
|
|
**Solutions**:
|
|
- Check if port 3000 is already in use: `netstat -ano | findstr :3000` (Windows) or `lsof -i :3000` (Linux)
|
|
- Kill the process or change port in `vite.config.js`
|
|
|
|
### API Calls Fail (404 or CORS)
|
|
|
|
**Problem**: API requests return 404 or CORS errors.
|
|
|
|
**Solutions**:
|
|
- Verify both backends are running (`localhost:8001` and `localhost:8003`)
|
|
- Check proxy configuration in `vite.config.js`
|
|
- Verify API base URLs in module services (`/api/reports`, `/api/data-entry`)
|
|
|
|
### Module Not Loading (Blank Screen)
|
|
|
|
**Problem**: Navigating to a route shows blank screen or loading spinner indefinitely.
|
|
|
|
**Solutions**:
|
|
- Check browser console for errors
|
|
- Verify route is defined in `src/router/index.js`
|
|
- Check component imports use correct alias (`@reports`, `@data-entry`)
|
|
- Check ErrorBoundary is not showing an error (inspect DOM)
|
|
|
|
### Build Fails
|
|
|
|
**Problem**: `npm run build` fails with import errors.
|
|
|
|
**Solutions**:
|
|
- Run `npm install` to ensure dependencies are up-to-date
|
|
- Check for circular dependencies in imports
|
|
- Verify all import paths use configured aliases
|
|
- Check for missing files referenced in imports
|
|
|
|
### Styles Not Applied
|
|
|
|
**Problem**: Components render but styles are missing or wrong.
|
|
|
|
**Solutions**:
|
|
- Verify `src/assets/css/main.css` is imported in `main.js`
|
|
- Check CSS files exist in `src/assets/css/`
|
|
- Clear browser cache (Ctrl+Shift+R / Cmd+Shift+R)
|
|
- Check PrimeVue theme is imported (`primevue/resources/themes/saga-blue/theme.css`)
|
|
|
|
---
|
|
|
|
## 📚 Additional Documentation
|
|
|
|
For more detailed information, see:
|
|
|
|
- **`CLAUDE.md`** - Complete project overview and development guide
|
|
- **`docs/ONBOARDING_CSS.md`** - CSS system quick start (5 minutes)
|
|
- **`docs/CSS_PATTERNS.md`** - Complete CSS patterns library
|
|
- **`docs/ARCHITECTURE_SCHEMA.md`** - Architecture diagrams and schemas
|
|
- **`.auto-build/specs/unified-app/spec.md`** - Feature specification
|
|
- **`.auto-build/specs/unified-app/plan.md`** - Implementation plan
|
|
|
|
---
|
|
|
|
## 📄 License
|
|
|
|
Internal use only - ROA2WEB ERP System
|
|
|
|
---
|
|
|
|
## 📞 Support
|
|
|
|
For issues or questions, contact the development team or check project documentation in `docs/`.
|
|
|
|
---
|
|
|
|
**Version**: 1.0
|
|
**Last Updated**: 2025-12-22
|
|
**Build Status**: ✅ Production Ready
|