Compare commits
10 Commits
fc99d5045c
...
cf97ab8aca
| Author | SHA1 | Date | |
|---|---|---|---|
| cf97ab8aca | |||
| 95c2abe449 | |||
| eec79aeaec | |||
| d96942a296 | |||
| 15933c0b1c | |||
| ff1ce4d483 | |||
| 9d7c575dd1 | |||
| 406e42df4e | |||
| a64dd93589 | |||
| d780a12367 |
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -65,8 +65,8 @@
|
||||
"AYANOVA_USE_URLS": "http://*:7575;",
|
||||
//"AYANOVA_PERMANENTLY_ERASE_DATABASE":"true",
|
||||
//"AYANOVA_REMOVE_LICENSE_FROM_DB":"true",
|
||||
"AYANOVA_REPORT_RENDERING_TIMEOUT":"60",
|
||||
//"AYANOVA_REPORT_RENDER_API_URL_OVERRIDE": "http://localhost:7575",
|
||||
"AYANOVA_REPORT_RENDERING_TIMEOUT":"60",
|
||||
// "AYANOVA_REPORT_RENDER_API_URL_OVERRIDE": "http://localhost:7575",
|
||||
"AYANOVA_BACKUP_PG_DUMP_PATH": "C:\\data\\code\\postgres_15\\bin"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
|
||||
374
BACKEND-BUILD-ENVIRONMENT.md
Normal file
374
BACKEND-BUILD-ENVIRONMENT.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# AyaNova v8 Backend Build Environment Documentation
|
||||
|
||||
> **Purpose**: This document captures the complete backend build process for AyaNova v8, including all deployment targets and installers.
|
||||
|
||||
## Overview
|
||||
|
||||
The backend build produces **6 deployment packages** from **3 projects**:
|
||||
|
||||
### Projects
|
||||
|
||||
| Project | Framework | Purpose |
|
||||
|---------|-----------|---------|
|
||||
| AyaNova (server) | .NET 8.0 | Main ASP.NET Core web server |
|
||||
| raven-launcher | .NET 8.0 | Windows launcher for standalone mode |
|
||||
| AyaNovaQBI | .NET Framework 4.8 | QuickBooks Desktop integration (separate build) |
|
||||
|
||||
### Deployment Targets
|
||||
|
||||
| Target | Description | Output |
|
||||
|--------|-------------|--------|
|
||||
| Subscription Linux x64 | Your hosted service (DigitalOcean) | `ayanova-subscription-linux-x64-server.zip` |
|
||||
| Perpetual Linux Server | Self-hosted Linux server | `ayanova-linux-x64-server.zip` |
|
||||
| Perpetual Linux Desktop | Single-user Linux desktop | `ayanova-linux-x64-desktop.zip` |
|
||||
| Windows Standalone | Self-contained single-user (includes .NET runtime + PostgreSQL) | `ayanova-windows-x64-single-setup.exe` |
|
||||
| Windows LAN | Network server (requires separate .NET + PostgreSQL) | `ayanova-windows-x64-lan-setup.exe` |
|
||||
| QuickBooks Integration | Windows desktop app | `ayanova-qbi-setup.exe` (separate build) |
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
c:\data\code\
|
||||
├── raven\ # Main repository root
|
||||
│ ├── build-release.bat # Master build script
|
||||
│ ├── raven.sln # Visual Studio solution
|
||||
│ ├── server\
|
||||
│ │ └── AyaNova\ # ASP.NET Core server project
|
||||
│ │ ├── AyaNova.csproj
|
||||
│ │ ├── Program.cs
|
||||
│ │ ├── Startup.cs
|
||||
│ │ ├── wwwroot\ # Frontend files copied here
|
||||
│ │ └── resource\ # Report templates, etc.
|
||||
│ ├── docs\
|
||||
│ │ └── 8.0\
|
||||
│ │ ├── ayanova\ # Main documentation (MkDocs)
|
||||
│ │ │ └── mkdocs.yml
|
||||
│ │ └── customer\ # Customer portal documentation
|
||||
│ │ └── mkdocs.yml
|
||||
│ ├── dist\
|
||||
│ │ ├── assets\ # Shared assets (license, configs, icons)
|
||||
│ │ │ ├── LICENSE
|
||||
│ │ │ ├── license.rtf
|
||||
│ │ │ ├── linux-desktop\config.json
|
||||
│ │ │ ├── linux-server\config.json
|
||||
│ │ │ ├── lan-install-config.json
|
||||
│ │ │ └── logo.ico
|
||||
│ │ ├── install\
|
||||
│ │ │ └── windows\x64\ # Inno Setup scripts
|
||||
│ │ │ ├── standalone.iss
|
||||
│ │ │ └── lan.iss
|
||||
│ │ ├── installers\ # Build output (generated)
|
||||
│ │ ├── linux-x64\ # Linux build output (generated)
|
||||
│ │ ├── subscription-build-linux-x64\ # Subscription build output (generated)
|
||||
│ │ └── win-x64\ # Windows build output (generated)
|
||||
│ │ ├── ayanova\
|
||||
│ │ │ ├── standalone\ # Self-contained build
|
||||
│ │ │ └── framework-dependent\ # Requires .NET installed
|
||||
│ │ ├── launcher\ # Launcher build output
|
||||
│ │ └── postgres-standalone\ # Embedded PostgreSQL for standalone
|
||||
│ └── graphics\ # Icons, logos
|
||||
│
|
||||
├── raven-client\ # Frontend (separate repo/folder)
|
||||
│ └── ayanova\
|
||||
│
|
||||
├── raven-launcher\ # Launcher project
|
||||
│ ├── raven-launcher.csproj
|
||||
│ └── config.json
|
||||
│
|
||||
└── ravenqbi\ # QuickBooks integration (separate)
|
||||
├── AyaNovaQBI\
|
||||
│ └── AyaNovaQBI.csproj
|
||||
└── install\
|
||||
└── qbi.iss
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Requirements
|
||||
|
||||
### Software
|
||||
|
||||
| Tool | Version | Purpose |
|
||||
|------|---------|---------|
|
||||
| .NET SDK | 8.0.x | Build ASP.NET Core projects |
|
||||
| Node.js | 12.22.9 | Build frontend (see frontend docs) |
|
||||
| Python | 3.11.x | Build MkDocs documentation |
|
||||
| MkDocs | Latest | Documentation generator |
|
||||
| Inno Setup | 6.x | Windows installer creation |
|
||||
| 7-Zip | Latest | Create Linux distribution zips |
|
||||
| Visual Studio 2022 | Latest | Build QBI project (.NET Framework 4.8) |
|
||||
|
||||
### Paths (hardcoded in build scripts)
|
||||
|
||||
The build scripts expect these exact paths:
|
||||
- `C:\data\code\raven\` - Main repository
|
||||
- `C:\data\code\raven-client\ayanova\` - Frontend project
|
||||
- `C:\data\code\raven-launcher\` - Launcher project
|
||||
- `C:\data\code\ravenqbi\` - QuickBooks integration
|
||||
- `C:\Program Files\7-Zip\7z.exe` - 7-Zip
|
||||
- `C:\Program Files (x86)\Inno Setup 6\ISCC.exe` - Inno Setup compiler
|
||||
|
||||
---
|
||||
|
||||
## Build Process (build-release.bat)
|
||||
|
||||
The master build script executes in this order:
|
||||
|
||||
### 1. Clean Output Folders
|
||||
```batch
|
||||
rmdir c:\data\code\raven\server\AyaNova\wwwroot /s/q
|
||||
mkdir c:\data\code\raven\server\AyaNova\wwwroot
|
||||
rmdir C:\data\code\raven\dist\installers /s/q
|
||||
mkdir C:\data\code\raven\dist\installers
|
||||
```
|
||||
|
||||
### 2. Build Documentation (MkDocs)
|
||||
```batch
|
||||
cd c:\data\code\raven\docs\8.0\ayanova
|
||||
mkdocs build
|
||||
|
||||
cd c:\data\code\raven\docs\8.0\customer
|
||||
mkdocs build
|
||||
```
|
||||
Output goes to `wwwroot/docs` (configured in mkdocs.yml).
|
||||
|
||||
### 3. Build Frontend (Vue.js)
|
||||
```batch
|
||||
cd c:\data\code\raven-client\ayanova
|
||||
call npm run build
|
||||
xcopy dist\* ..\raven\server\AyaNova\wwwroot\ /e
|
||||
```
|
||||
|
||||
### 4. Build Subscription Linux x64
|
||||
```batch
|
||||
dotnet publish --property:PublishDir=C:\data\code\raven\dist\subscription-build-linux-x64\ \
|
||||
-c Release -r linux-x64 --no-self-contained -p:SUBSCRIPTION_BUILD=true
|
||||
```
|
||||
Key flag: `-p:SUBSCRIPTION_BUILD=true` sets a compile-time constant for subscription-specific features.
|
||||
|
||||
Then packages to zip with config:
|
||||
```batch
|
||||
7z a ayanova-subscription-linux-x64-server.zip subscription-build-linux-x64\*
|
||||
7z a ayanova-subscription-linux-x64-server.zip assets\linux-server\config.json
|
||||
```
|
||||
|
||||
### 5. Build Perpetual Linux x64
|
||||
```batch
|
||||
dotnet publish --property:PublishDir=C:\data\code\raven\dist\linux-x64\ \
|
||||
-c Release -r linux-x64 --no-self-contained -p:SUBSCRIPTION_BUILD=false
|
||||
```
|
||||
|
||||
Creates two packages (same build, different config):
|
||||
- `ayanova-linux-x64-desktop.zip` (with desktop config)
|
||||
- `ayanova-linux-x64-server.zip` (with server config)
|
||||
|
||||
### 6. Build Windows Standalone (Self-Contained)
|
||||
```batch
|
||||
dotnet publish -c Release --property:PublishDir=C:\data\code\raven\dist\win-x64\ayanova\standalone\ \
|
||||
-r win-x64 --self-contained
|
||||
```
|
||||
This includes the .NET runtime in the output (~150MB larger).
|
||||
|
||||
### 7. Build Windows Framework-Dependent
|
||||
```batch
|
||||
dotnet publish -c Release --property:PublishDir=C:\data\code\raven\dist\win-x64\ayanova\framework-dependent\ \
|
||||
-r win-x64 --no-self-contained
|
||||
```
|
||||
Requires .NET 8 runtime installed on target machine.
|
||||
|
||||
### 8. Build Launcher
|
||||
```batch
|
||||
cd C:\data\code\raven-launcher\
|
||||
dotnet publish -c Release --property:PublishDir=C:\data\code\raven\dist\win-x64\launcher\ \
|
||||
-r win-x64 --self-contained
|
||||
```
|
||||
The launcher is always self-contained (uses `PublishTrimmed` to reduce size).
|
||||
|
||||
### 9. Create Windows Installers (Inno Setup)
|
||||
```batch
|
||||
ISCC.exe standalone.iss
|
||||
ISCC.exe lan.iss
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project-Specific Notes
|
||||
|
||||
### AyaNova Server (AyaNova.csproj)
|
||||
|
||||
**Conditional Compilation:**
|
||||
```xml
|
||||
<DefineConstants Condition=" '$(SUBSCRIPTION_BUILD)' == 'true' ">$(DefineConstants);SUBSCRIPTION_BUILD</DefineConstants>
|
||||
```
|
||||
Use `#if SUBSCRIPTION_BUILD` in code for subscription-only features.
|
||||
|
||||
**Key Dependencies:**
|
||||
- Entity Framework Core 8.0 with Npgsql (PostgreSQL)
|
||||
- PuppeteerSharp for PDF report generation (downloads Chromium on first run)
|
||||
- MailKit for email
|
||||
- JWT authentication
|
||||
|
||||
**Resource Files:**
|
||||
Report templates and other resources are copied to output:
|
||||
```xml
|
||||
<Content Include=".\resource\*.*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
```
|
||||
|
||||
### Launcher (raven-launcher.csproj)
|
||||
|
||||
Simple console app that:
|
||||
1. Starts embedded PostgreSQL (standalone mode only)
|
||||
2. Starts the AyaNova server
|
||||
3. Opens default browser to the login page
|
||||
4. Waits for user to close
|
||||
|
||||
Uses `PublishTrimmed` to reduce self-contained size.
|
||||
|
||||
### QuickBooks Integration (AyaNovaQBI.csproj)
|
||||
|
||||
**Important:** This is a .NET Framework 4.8 Windows Forms application, NOT .NET Core.
|
||||
|
||||
- Requires Visual Studio 2022 (not VS Code) for the Windows Forms designer
|
||||
- References `interop.QBFC14.dll` from QuickBooks SDK (must be installed)
|
||||
- Targets x86 (32-bit) because QuickBooks SDK is 32-bit only
|
||||
- Has a pre-build event that runs a timestamp utility
|
||||
|
||||
**Why Visual Studio is required:**
|
||||
The Windows Forms designer only works in Visual Studio. The code could theoretically be edited in VS Code, but the designer is essential for the UI work.
|
||||
|
||||
---
|
||||
|
||||
## Windows Installer Details
|
||||
|
||||
### Standalone (standalone.iss)
|
||||
|
||||
Includes:
|
||||
- Self-contained AyaNova server (with .NET runtime)
|
||||
- Self-contained launcher
|
||||
- Embedded PostgreSQL (`postgres-standalone` folder)
|
||||
- Empty database template
|
||||
|
||||
Creates desktop shortcut to launcher. User clicks one icon, everything starts.
|
||||
|
||||
**Key paths:**
|
||||
- App: `{autopf}\ayanova`
|
||||
- Data: `{commonappdata}\ayanova\database`
|
||||
- Logs: `{commonappdata}\ayanova\logs`
|
||||
|
||||
### LAN (lan.iss)
|
||||
|
||||
Includes:
|
||||
- Framework-dependent AyaNova server (no .NET runtime)
|
||||
- Config file for network mode
|
||||
|
||||
Does NOT include PostgreSQL - user must install separately.
|
||||
|
||||
Prompts user to download:
|
||||
- ASP.NET Core Runtime Hosting Bundle
|
||||
- PostgreSQL
|
||||
|
||||
---
|
||||
|
||||
## Embedded PostgreSQL (Windows Standalone)
|
||||
|
||||
The standalone installer includes a portable PostgreSQL in `dist\win-x64\postgres-standalone\`.
|
||||
|
||||
This is a pre-configured PostgreSQL installation that:
|
||||
- Runs without system installation
|
||||
- Stores data in `{commonappdata}\ayanova\database`
|
||||
- Is started/stopped by the launcher
|
||||
|
||||
**Source:** You'll need to maintain this - it's a copy of a PostgreSQL Windows binary distribution with a pre-initialized data directory.
|
||||
|
||||
---
|
||||
|
||||
## QuickBooks Integration Build (Separate)
|
||||
|
||||
The QBI is built separately and not included in the main build script.
|
||||
|
||||
```batch
|
||||
cd c:\data\code\ravenqbi\AyaNovaQBI
|
||||
msbuild /p:Configuration=Release
|
||||
|
||||
cd c:\data\code\ravenqbi\install
|
||||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" qbi.iss
|
||||
```
|
||||
|
||||
Output: `c:\data\code\ravenqbi\install\output\ayanova-qbi-setup.exe`
|
||||
|
||||
---
|
||||
|
||||
## Version Management
|
||||
|
||||
Versions must be updated in multiple places:
|
||||
|
||||
| File | Location |
|
||||
|------|----------|
|
||||
| `AyaNova.csproj` | `<Version>8.2.4</Version>` |
|
||||
| `standalone.iss` | `#define MyAppVersion "8.2.4"` |
|
||||
| `lan.iss` | `#define MyAppVersion "8.2.4"` |
|
||||
| `qbi.iss` | `#define MyAppVersion "8.0.2"` |
|
||||
| `package.json` (frontend) | `"version": "8.2.4"` |
|
||||
|
||||
Consider creating a version file or script to update all at once.
|
||||
|
||||
---
|
||||
|
||||
## Containerization Considerations
|
||||
|
||||
### What CAN be containerized (for build):
|
||||
|
||||
1. **Frontend build** - Already done (Dockerfile.frontend)
|
||||
2. **Documentation build** - Already done (Dockerfile.docs)
|
||||
3. **Linux server builds** - .NET SDK in Docker can cross-compile for Linux
|
||||
4. **Windows framework-dependent build** - Can be built in Docker
|
||||
|
||||
### What CANNOT be easily containerized:
|
||||
|
||||
1. **Windows self-contained builds** - Need Windows container or Windows host
|
||||
2. **Inno Setup installers** - Windows-only tool
|
||||
3. **QuickBooks Integration** - .NET Framework 4.8, Windows Forms, requires Visual Studio
|
||||
4. **Embedded PostgreSQL** - Windows-specific binaries
|
||||
|
||||
### Recommendation:
|
||||
|
||||
For a VPS-based build:
|
||||
- Use Docker/Linux for: Frontend, Docs, Linux targets
|
||||
- Keep Windows laptop for: Windows installers, QBI
|
||||
- Or: Use a Windows VM/GitHub Actions for Windows builds
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "PuppeteerSharp failed to download Chromium"
|
||||
|
||||
The server downloads Chromium on first run for PDF generation. If this fails:
|
||||
1. Check internet connectivity
|
||||
2. Check firewall/proxy settings
|
||||
3. Pre-download Chrome and set `REPORT_RENDER_BROWSER_PATH` environment variable
|
||||
|
||||
### "SUBSCRIPTION_BUILD not defined"
|
||||
|
||||
Ensure you're passing the flag correctly:
|
||||
```batch
|
||||
dotnet publish ... -p:SUBSCRIPTION_BUILD=true
|
||||
```
|
||||
|
||||
### Inno Setup errors
|
||||
|
||||
- Ensure all source paths exist
|
||||
- Check that previous build steps completed
|
||||
- Verify 7-Zip and Inno Setup are installed at expected paths
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-02-01 | Initial documentation created |
|
||||
271
BUILD-ENVIRONMENT.md
Normal file
271
BUILD-ENVIRONMENT.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# AyaNova v8 Build Environment Documentation
|
||||
|
||||
> **Purpose**: This document captures the exact build environment for AyaNova v8 as of February 2026. Use this to recreate the build environment on a new machine or in a container.
|
||||
|
||||
## Overview
|
||||
|
||||
AyaNova v8 consists of three main build components:
|
||||
|
||||
1. **Frontend** - Vue.js 2 application (JavaScript, not TypeScript)
|
||||
2. **Documentation** - MkDocs with Material theme
|
||||
3. **Backend** - ASP.NET Core 8.0 server (C#)
|
||||
|
||||
The frontend and docs are built first, then copied into the backend project's `wwwroot` folder before the backend is built and packaged for distribution.
|
||||
|
||||
---
|
||||
|
||||
## Current Build Machine Specifications
|
||||
|
||||
| Component | Version | Notes |
|
||||
|-----------|---------|-------|
|
||||
| Operating System | Windows 11 | ThinkPad X13 Gen 3 |
|
||||
| Node.js | v12.22.9 | **End-of-life** - do not upgrade without testing |
|
||||
| npm | (check with `npm -v`) | Comes with Node |
|
||||
| Python | 3.11.1 | For MkDocs |
|
||||
| .NET SDK | 8.0.x | For backend build |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Build Environment
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
c:\data\code\
|
||||
├── raven-client\
|
||||
│ └── ayanova\ # Vue.js frontend project
|
||||
│ ├── src\ # Source code
|
||||
│ ├── public\ # Static assets
|
||||
│ ├── dist\ # Build output (generated)
|
||||
│ ├── package.json
|
||||
│ ├── vue.config.js
|
||||
│ └── buildrelease.bat
|
||||
├── raven\
|
||||
│ ├── server\
|
||||
│ │ └── AyaNova\
|
||||
│ │ └── wwwroot\ # Frontend files copied here after build
|
||||
│ └── docs\
|
||||
│ └── 8.0\
|
||||
│ └── ayanova\ # MkDocs source
|
||||
│ └── mkdocs.yml
|
||||
```
|
||||
|
||||
### Node.js Version: Critical Information
|
||||
|
||||
**Current version: Node.js 12.22.9**
|
||||
|
||||
This project uses an old Node version because:
|
||||
|
||||
1. The `fibers` package (used by sass-loader for faster Sass compilation) is a native C++ addon that stopped being maintained and doesn't compile on newer Node versions
|
||||
2. Various other dependencies have peer dependency requirements that break on Node 14+
|
||||
|
||||
**Do not upgrade Node without first testing in an isolated environment.**
|
||||
|
||||
### Key Dependencies
|
||||
|
||||
From `package.json`:
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| vue | 2.6.14 | Core framework (Vue 2, end-of-life Dec 2023) |
|
||||
| vuetify | 2.6.6 | UI component library |
|
||||
| @vue/cli-service | 4.5.15 | Build tooling |
|
||||
| webpack | 4.46.0 | Module bundler |
|
||||
| sass | 1.52.3 | Sass compiler |
|
||||
| fibers | 4.0.3 | **Problematic** - native addon for Sass |
|
||||
| monaco-editor | 0.30.1 | Code editor component |
|
||||
|
||||
### Build Process
|
||||
|
||||
The frontend build is executed via `buildrelease.bat`:
|
||||
|
||||
```batch
|
||||
@echo **************************************************************
|
||||
@echo ******************** BUILD CLIENT ****************************
|
||||
@echo **************************************************************
|
||||
rmdir c:\data\code\raven\server\AyaNova\wwwroot /s/q
|
||||
mkdir c:\data\code\raven\server\AyaNova\wwwroot
|
||||
cd c:\data\code\raven\docs\8.0\ayanova
|
||||
mkdocs build
|
||||
cd c:\data\code\raven-client\ayanova
|
||||
call npm run build
|
||||
xcopy c:\data\code\raven-client\ayanova\dist\* C:\data\code\raven\server\AyaNova\wwwroot\ /e
|
||||
pause
|
||||
```
|
||||
|
||||
This does:
|
||||
1. Clears and recreates the backend's wwwroot folder
|
||||
2. Builds the MkDocs documentation (output goes directly to wwwroot/docs via mkdocs.yml config)
|
||||
3. Runs `npm run build` which calls `vue-cli-service build`
|
||||
4. Copies the built frontend from `dist/` to the backend's `wwwroot/`
|
||||
|
||||
### Known Build Warnings (Safe to Ignore)
|
||||
|
||||
These warnings appear during every build and do not affect the output:
|
||||
|
||||
1. **Browserslist warning**: "caniuse-lite is outdated"
|
||||
- Harmless - just means browser compatibility database is old
|
||||
|
||||
2. **Dart Sass division warnings**: "Using / for division is deprecated"
|
||||
- Come from Vuetify's stylesheets, not your code
|
||||
- Will continue working until Dart Sass 2.0
|
||||
|
||||
3. **Asset size warnings**: Various files exceed 244 KiB
|
||||
- Expected with Monaco editor (ts.worker.js is 4.5MB)
|
||||
- Application still works correctly
|
||||
|
||||
### NPM Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"serve": "vue-cli-service serve", // Development server
|
||||
"build": "vue-cli-service build", // Production build
|
||||
"lint": "vue-cli-service lint" // Code linting
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation Build Environment
|
||||
|
||||
### MkDocs Configuration
|
||||
|
||||
From `mkdocs.yml`:
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Theme | Material |
|
||||
| Output directory | `../../../server/AyaNova/wwwroot/docs` |
|
||||
| Site URL | https://ayanova.com/docs/ |
|
||||
|
||||
### Required Python Packages
|
||||
|
||||
```bash
|
||||
pip install mkdocs mkdocs-material
|
||||
```
|
||||
|
||||
The `pymdownx` extensions (highlight, superfences) come bundled with mkdocs-material.
|
||||
|
||||
### Build Command
|
||||
|
||||
```bash
|
||||
cd c:\data\code\raven\docs\8.0\ayanova
|
||||
mkdocs build
|
||||
```
|
||||
|
||||
Output goes directly to the backend's wwwroot/docs folder.
|
||||
|
||||
---
|
||||
|
||||
## Backend Build Environment
|
||||
|
||||
### .NET Version
|
||||
|
||||
- **Target Framework**: .NET 8.0
|
||||
- **Project Type**: ASP.NET Core
|
||||
|
||||
### Build Tools Required
|
||||
|
||||
- .NET 8.0 SDK
|
||||
- Visual Studio 2022 or VS Code with C# extension
|
||||
|
||||
### Build Outputs
|
||||
|
||||
The backend build produces multiple deployment targets:
|
||||
1. Linux hosted (DigitalOcean service)
|
||||
2. Windows networked (IIS hosted)
|
||||
3. Windows single-user (self-contained)
|
||||
4. Linux server
|
||||
5. Linux desktop
|
||||
|
||||
Windows installers are created with Inno Setup.
|
||||
|
||||
---
|
||||
|
||||
## Recreating the Build Environment
|
||||
|
||||
### Option 1: Native Installation (Windows)
|
||||
|
||||
1. Install Node.js 12.22.9 specifically:
|
||||
- Download from https://nodejs.org/dist/v12.22.9/
|
||||
- Or use nvm-windows: `nvm install 12.22.9 && nvm use 12.22.9`
|
||||
|
||||
2. Install Python 3.11:
|
||||
- Download from python.org
|
||||
- Ensure it's in PATH
|
||||
|
||||
3. Install MkDocs:
|
||||
```bash
|
||||
pip install mkdocs mkdocs-material
|
||||
```
|
||||
|
||||
4. Install .NET 8.0 SDK:
|
||||
- Download from https://dotnet.microsoft.com/download
|
||||
|
||||
5. Clone/checkout repository from SVN
|
||||
|
||||
6. Install frontend dependencies:
|
||||
```bash
|
||||
cd c:\data\code\raven-client\ayanova
|
||||
npm ci
|
||||
```
|
||||
Note: Use `npm ci` (not `npm install`) to install exact versions from package-lock.json
|
||||
|
||||
### Option 2: Docker (Recommended for Reproducibility)
|
||||
|
||||
See `Dockerfile.frontend` and `Dockerfile.docs` in this repository for containerized builds that don't require any local installation except Docker.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "fibers" fails to install
|
||||
|
||||
This native module requires:
|
||||
- Python 2.7 or 3.x
|
||||
- Visual Studio Build Tools (Windows) or build-essential (Linux)
|
||||
- Specific Node.js version compatibility
|
||||
|
||||
If it fails, you can try removing it (it's optional):
|
||||
1. Remove `"fibers": "^4.0.3"` from package.json
|
||||
2. Remove the `fiber: require("fibers")` line from webpack.config.js
|
||||
3. Run `npm install` again
|
||||
|
||||
The build will be slightly slower but should work.
|
||||
|
||||
### Node version mismatch
|
||||
|
||||
If you see errors about incompatible Node versions:
|
||||
1. Check your Node version: `node -v`
|
||||
2. It must be 12.22.9 for guaranteed compatibility
|
||||
3. Use nvm to switch versions if needed
|
||||
|
||||
### "Module not found" errors
|
||||
|
||||
Try:
|
||||
```bash
|
||||
rm -rf node_modules
|
||||
rm package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Modernization Notes
|
||||
|
||||
When time permits, consider:
|
||||
|
||||
1. **Remove fibers dependency** - It's optional and causes most build issues
|
||||
2. **Upgrade to Vue 3 + Vite** - Much faster builds, better tooling, active support
|
||||
3. **Migrate to TypeScript** - Better IDE support and fewer runtime errors
|
||||
4. **Update Node to LTS** - Current LTS is Node 20.x
|
||||
|
||||
These are significant changes that should be done incrementally with thorough testing.
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-02-01 | Initial documentation created |
|
||||
29
Dockerfile.docs
Normal file
29
Dockerfile.docs
Normal file
@@ -0,0 +1,29 @@
|
||||
# AyaNova v8 Documentation Build Environment
|
||||
#
|
||||
# This Dockerfile creates a reproducible build environment for the MkDocs documentation.
|
||||
#
|
||||
# Usage:
|
||||
# Build the image:
|
||||
# docker build -f Dockerfile.docs -t ayanova-docs-build .
|
||||
#
|
||||
# Run a build (mount your docs source and output):
|
||||
# docker run --rm -v "c:/data/code/raven/docs/8.0/ayanova:/docs" -v "c:/data/code/raven/server/AyaNova/wwwroot/docs:/output" ayanova-docs-build
|
||||
#
|
||||
# Serve docs locally for preview (on port 8000):
|
||||
# docker run --rm -p 8000:8000 -v "c:/data/code/raven/docs/8.0/ayanova:/docs" ayanova-docs-build mkdocs serve -a 0.0.0.0:8000
|
||||
#
|
||||
|
||||
FROM python:3.11-slim-bullseye
|
||||
|
||||
# Install MkDocs and the Material theme
|
||||
# pymdownx extensions come bundled with mkdocs-material
|
||||
RUN pip install --no-cache-dir \
|
||||
mkdocs==1.5.* \
|
||||
mkdocs-material==9.*
|
||||
|
||||
WORKDIR /docs
|
||||
|
||||
# Default command: build the documentation
|
||||
# Note: The output directory is configured in mkdocs.yml (site_dir)
|
||||
# For containerized builds, you may want to override site_dir
|
||||
CMD ["mkdocs", "build"]
|
||||
676
V8-DEV-VM-SETUP-GUIDE.md
Normal file
676
V8-DEV-VM-SETUP-GUIDE.md
Normal file
@@ -0,0 +1,676 @@
|
||||
# AyaNova v8 Build VM Setup Guide
|
||||
|
||||
> **Purpose**: Step-by-step instructions to create a portable Windows virtual machine containing the complete AyaNova v8 build environment. This VM can run on Windows or Linux hosts.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This guide will help you create a VirtualBox VM that:
|
||||
- Contains all tools needed to build every AyaNova v8 target
|
||||
- Is portable (can be moved between machines)
|
||||
- Can be snapshotted (save known-good states)
|
||||
- Works on both Windows and Linux host machines
|
||||
|
||||
**Time estimate**: 3-4 hours for initial setup
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Prerequisites and Purchases
|
||||
|
||||
### 1.1 Windows License
|
||||
|
||||
You'll need a legitimate Windows license for the VM. Options:
|
||||
|
||||
| Option | Approximate Cost | Notes |
|
||||
|--------|------------------|-------|
|
||||
| Windows 11 Pro (Retail) | $200 USD | Transferable to new hardware, recommended |
|
||||
| Windows 11 Pro (OEM) | $120-150 USD | Tied to "one machine" but works for VMs |
|
||||
| Windows 11 Home | $140 USD | Works but Pro has Hyper-V/dev features |
|
||||
| Windows 10 Pro | $120-150 USD | Still fully supported, lighter weight |
|
||||
|
||||
**Recommendation**: Windows 11 Pro retail license. It's transferable and will remain supported longest.
|
||||
|
||||
**Where to buy**:
|
||||
- Microsoft Store (microsoft.com) - Full retail price, guaranteed legitimate
|
||||
- Amazon - Often has retail boxed copies
|
||||
- Newegg - Same as Amazon
|
||||
|
||||
**Avoid**: Grey market key resellers (G2A, Kinguin, etc.) - Keys may be revoked.
|
||||
|
||||
### 1.2 Host Machine Requirements
|
||||
|
||||
Your Linux laptop will need:
|
||||
- **RAM**: 16GB minimum (8GB for VM + 8GB for host)
|
||||
- **Disk**: 100GB free space for VM
|
||||
- **CPU**: 4+ cores recommended (VM will use 2-4)
|
||||
- **Virtualization**: VT-x/AMD-V enabled in BIOS
|
||||
|
||||
Your current ThinkPad X13 (16GB RAM, Ryzen 7) will work, though it'll be tight on RAM. A machine with 32GB would be more comfortable.
|
||||
|
||||
### 1.3 Software to Download (All Free)
|
||||
|
||||
Download these before starting:
|
||||
|
||||
1. **VirtualBox** (host machine)
|
||||
- https://www.virtualbox.org/wiki/Downloads
|
||||
- Get the version for your Linux distribution
|
||||
- Also get the Extension Pack (same page)
|
||||
|
||||
2. **Windows 11 ISO** (for VM)
|
||||
- https://www.microsoft.com/software-download/windows11
|
||||
- Download the ISO directly (not the Media Creation Tool)
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Create the Virtual Machine
|
||||
|
||||
### 2.1 Install VirtualBox on Linux Host
|
||||
|
||||
For Ubuntu/Debian-based:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install virtualbox virtualbox-ext-pack
|
||||
```
|
||||
|
||||
For Fedora:
|
||||
```bash
|
||||
sudo dnf install VirtualBox
|
||||
```
|
||||
|
||||
Or download from virtualbox.org for other distributions.
|
||||
|
||||
### 2.2 Create New VM
|
||||
|
||||
1. Open VirtualBox Manager
|
||||
2. Click **New**
|
||||
3. Configure:
|
||||
- **Name**: `AyaNova-Build-v8`
|
||||
- **Folder**: Choose a location with plenty of space
|
||||
- **ISO Image**: Select the Windows 11 ISO you downloaded
|
||||
- **Type**: Microsoft Windows
|
||||
- **Version**: Windows 11 (64-bit)
|
||||
- Check "Skip Unattended Installation" (gives you more control)
|
||||
|
||||
4. Click **Next**
|
||||
|
||||
### 2.3 Hardware Configuration
|
||||
|
||||
| Setting | Value | Notes |
|
||||
|---------|-------|-------|
|
||||
| Base Memory | 8192 MB (8GB) | Minimum for VS 2022 |
|
||||
| Processors | 4 CPUs | More if your host has them |
|
||||
| Enable EFI | Yes | Required for Windows 11 |
|
||||
|
||||
Click **Next**
|
||||
|
||||
### 2.4 Virtual Hard Disk
|
||||
|
||||
| Setting | Value | Notes |
|
||||
|---------|-------|-------|
|
||||
| Create Virtual Hard Disk | Yes | |
|
||||
| Disk Size | 120 GB | VS alone is 20-40GB |
|
||||
| Pre-allocate Full Size | No | Saves initial space |
|
||||
|
||||
Click **Next**, then **Finish**
|
||||
|
||||
### 2.5 Additional VM Settings
|
||||
|
||||
Before starting the VM, click **Settings**:
|
||||
|
||||
**System → Motherboard:**
|
||||
- Enable EFI: ✓ (should already be set)
|
||||
- TPM: v2.0 (required for Windows 11)
|
||||
|
||||
**Display:**
|
||||
- Video Memory: 128 MB
|
||||
- Graphics Controller: VBoxSVGA
|
||||
- Enable 3D Acceleration: ✓
|
||||
|
||||
**Storage:**
|
||||
- Verify Windows ISO is attached to IDE Controller
|
||||
|
||||
**Shared Folders** (optional, configure later):
|
||||
- Can share folders between host and VM
|
||||
|
||||
**Network:**
|
||||
- Attached to: NAT (default, fine for building)
|
||||
- Or: Bridged Adapter (if you need VM on your network)
|
||||
|
||||
Click **OK**
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Install Windows
|
||||
|
||||
### 3.1 Start VM and Install
|
||||
|
||||
1. Start the VM
|
||||
2. Press any key to boot from ISO when prompted
|
||||
3. Follow Windows 11 installer:
|
||||
- Language: Your preference
|
||||
- Click "Install now"
|
||||
- Enter your product key (or "I don't have a product key" to enter later)
|
||||
- Select **Windows 11 Pro**
|
||||
- Accept license terms
|
||||
- Choose **Custom: Install Windows only**
|
||||
- Select the virtual drive, click **Next**
|
||||
- Wait for installation (10-20 minutes)
|
||||
|
||||
### 3.2 Initial Windows Setup
|
||||
|
||||
When Windows restarts:
|
||||
1. Select region and keyboard layout
|
||||
2. **Network**: If asked, choose "I don't have internet" → "Continue with limited setup"
|
||||
- This avoids Microsoft account requirement
|
||||
3. Create a local account:
|
||||
- Username: `builder` (or your preference)
|
||||
- Password: Something simple but memorable
|
||||
4. Disable all the telemetry/tracking options (your choice)
|
||||
5. Wait for setup to complete
|
||||
|
||||
### 3.3 Install VirtualBox Guest Additions
|
||||
|
||||
This enables better display, shared folders, clipboard sharing:
|
||||
|
||||
1. In VirtualBox menu: **Devices → Insert Guest Additions CD image**
|
||||
2. Open File Explorer in Windows
|
||||
3. Navigate to the CD drive
|
||||
4. Run **VBoxWindowsAdditions.exe**
|
||||
5. Accept all defaults, click Install
|
||||
6. Reboot when prompted
|
||||
|
||||
After reboot:
|
||||
- VM window can be resized freely
|
||||
- Enable shared clipboard: **Devices → Shared Clipboard → Bidirectional**
|
||||
|
||||
### 3.4 Windows Updates
|
||||
|
||||
Before installing dev tools, get Windows fully updated:
|
||||
|
||||
1. **Settings → Windows Update**
|
||||
2. Click "Check for updates"
|
||||
3. Install all updates
|
||||
4. Reboot as needed
|
||||
5. Repeat until no more updates
|
||||
|
||||
This may take 30-60 minutes. Be patient.
|
||||
|
||||
### 3.5 Activate Windows
|
||||
|
||||
1. **Settings → System → Activation**
|
||||
2. Click "Change product key"
|
||||
3. Enter your Windows license key
|
||||
4. Click Activate
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Install Build Tools
|
||||
|
||||
### 4.1 Create Standard Folders
|
||||
|
||||
Open PowerShell as Administrator and run:
|
||||
```powershell
|
||||
mkdir C:\data
|
||||
mkdir C:\data\code
|
||||
```
|
||||
|
||||
### 4.2 Install Package Manager (Winget or Chocolatey)
|
||||
|
||||
Winget is built into Windows 11. Verify it works:
|
||||
```powershell
|
||||
winget --version
|
||||
```
|
||||
|
||||
If not available, install Chocolatey as alternative:
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
```
|
||||
|
||||
### 4.3 Install Core Tools
|
||||
|
||||
Using winget (run in PowerShell as Administrator):
|
||||
|
||||
```powershell
|
||||
# 7-Zip
|
||||
winget install --id 7zip.7zip -e
|
||||
|
||||
# Git (useful even if using SVN)
|
||||
winget install --id Git.Git -e
|
||||
|
||||
# VS Code
|
||||
winget install --id Microsoft.VisualStudioCode -e --source winget
|
||||
|
||||
# Python 3.11
|
||||
winget install --id Python.Python.3.11 -e --source winget
|
||||
|
||||
# .NET 8 SDK
|
||||
winget install --id Microsoft.DotNet.SDK.8 -e --source winget
|
||||
|
||||
# Inno Setup
|
||||
winget install --id JRSoftware.InnoSetup -e --source winget
|
||||
```
|
||||
|
||||
Close and reopen PowerShell after these installs.
|
||||
|
||||
### 4.4 Install Node.js 12.22.9 (Specific Version)
|
||||
|
||||
Node 12 isn't in winget, so install manually:
|
||||
|
||||
1. Download from: https://nodejs.org/dist/v12.22.9/node-v12.22.9-x64.msi
|
||||
2. Run the installer
|
||||
3. Accept all defaults
|
||||
4. Verify:
|
||||
```powershell
|
||||
node -v
|
||||
# Should show: v12.22.9
|
||||
```
|
||||
|
||||
### 4.5 Install MkDocs
|
||||
|
||||
```powershell
|
||||
pip install mkdocs mkdocs-material
|
||||
```
|
||||
|
||||
Verify:
|
||||
```powershell
|
||||
mkdocs --version
|
||||
```
|
||||
|
||||
### 4.6 Install Visual Studio 2022 Community
|
||||
|
||||
This is needed for the QuickBooks Integration project (.NET Framework 4.8, Windows Forms).
|
||||
|
||||
1. Download from: https://visualstudio.microsoft.com/vs/community/
|
||||
2. Run the installer
|
||||
3. When Workloads screen appears, select:
|
||||
- ✓ **.NET desktop development** (includes Windows Forms)
|
||||
- ✓ **ASP.NET and web development** (optional but useful)
|
||||
4. In Individual Components tab, verify:
|
||||
- ✓ .NET Framework 4.8 SDK
|
||||
- ✓ .NET Framework 4.8 targeting pack
|
||||
5. Click **Install**
|
||||
6. Wait (this takes 20-40 minutes and downloads several GB)
|
||||
|
||||
### 4.7 Install TortoiseSVN (if using SVN)
|
||||
|
||||
```powershell
|
||||
winget install --id TortoiseSVN.TortoiseSVN -e
|
||||
```
|
||||
|
||||
Reboot after installation (adds shell integration).
|
||||
|
||||
### 4.8 Verify All Installations
|
||||
|
||||
Open a new PowerShell window and run:
|
||||
|
||||
```powershell
|
||||
# Create a verification script
|
||||
@"
|
||||
Write-Host "=== AyaNova Build Environment Check ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "Node.js:" -NoNewline
|
||||
node -v
|
||||
|
||||
Write-Host "npm:" -NoNewline
|
||||
npm -v
|
||||
|
||||
Write-Host "Python:" -NoNewline
|
||||
python --version
|
||||
|
||||
Write-Host "pip:" -NoNewline
|
||||
pip --version
|
||||
|
||||
Write-Host ".NET SDK:" -NoNewline
|
||||
dotnet --version
|
||||
|
||||
Write-Host "MkDocs:" -NoNewline
|
||||
mkdocs --version
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Checking paths..." -ForegroundColor Cyan
|
||||
|
||||
$tools = @(
|
||||
"C:\Program Files\7-Zip\7z.exe",
|
||||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||
)
|
||||
|
||||
foreach ($tool in $tools) {
|
||||
if (Test-Path $tool) {
|
||||
Write-Host "[OK] $tool" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[MISSING] $tool" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Check Complete ===" -ForegroundColor Cyan
|
||||
"@ | Out-File -FilePath "check-env.ps1"
|
||||
|
||||
# Run it
|
||||
.\check-env.ps1
|
||||
```
|
||||
|
||||
All items should show versions or [OK].
|
||||
|
||||
---
|
||||
|
||||
## Part 5: Set Up Source Code
|
||||
|
||||
### 5.1 Check Out from SVN
|
||||
|
||||
If your SVN server is accessible from the VM:
|
||||
|
||||
```powershell
|
||||
cd C:\data\code
|
||||
|
||||
# Check out main repository
|
||||
svn checkout https://your-svn-server/path/to/raven raven
|
||||
|
||||
# Check out frontend
|
||||
svn checkout https://your-svn-server/path/to/raven-client raven-client
|
||||
|
||||
# Check out launcher
|
||||
svn checkout https://your-svn-server/path/to/raven-launcher raven-launcher
|
||||
|
||||
# Check out QBI
|
||||
svn checkout https://your-svn-server/path/to/ravenqbi ravenqbi
|
||||
```
|
||||
|
||||
### 5.2 Alternative: Copy from Host via Shared Folder
|
||||
|
||||
If SVN isn't accessible, use VirtualBox shared folders:
|
||||
|
||||
1. In VirtualBox: **Devices → Shared Folders → Shared Folders Settings**
|
||||
2. Click the folder+ icon
|
||||
3. **Folder Path**: Your code folder on Linux host
|
||||
4. **Folder Name**: `code`
|
||||
5. Check "Auto-mount"
|
||||
6. Click OK
|
||||
|
||||
The shared folder appears as a network drive in Windows. Copy files from there to `C:\data\code\`.
|
||||
|
||||
### 5.3 Install Frontend Dependencies
|
||||
|
||||
```powershell
|
||||
cd C:\data\code\raven-client\ayanova
|
||||
npm ci
|
||||
```
|
||||
|
||||
This installs exact versions from package-lock.json. May take 5-10 minutes.
|
||||
|
||||
---
|
||||
|
||||
## Part 6: Test the Build
|
||||
|
||||
### 6.1 Run Full Build
|
||||
|
||||
```powershell
|
||||
cd C:\data\code\raven
|
||||
.\build-release.bat
|
||||
```
|
||||
|
||||
Watch for errors. The build should:
|
||||
1. Build main docs ✓
|
||||
2. Build customer docs ✓
|
||||
3. Build frontend ✓
|
||||
4. Build subscription Linux ✓
|
||||
5. Build perpetual Linux ✓
|
||||
6. Build Windows standalone ✓
|
||||
7. Build Windows framework-dependent ✓
|
||||
8. Build launcher ✓
|
||||
9. Create standalone installer ✓
|
||||
10. Create LAN installer ✓
|
||||
|
||||
### 6.2 Verify Outputs
|
||||
|
||||
Check that all expected files were created:
|
||||
|
||||
```powershell
|
||||
dir C:\data\code\raven\dist\installers
|
||||
```
|
||||
|
||||
Expected files:
|
||||
- `ayanova-subscription-linux-x64-server.zip`
|
||||
- `ayanova-linux-x64-desktop.zip`
|
||||
- `ayanova-linux-x64-server.zip`
|
||||
- `ayanova-windows-x64-single-setup.exe`
|
||||
- `ayanova-windows-x64-lan-setup.exe`
|
||||
|
||||
### 6.3 Test QBI Build (Separate)
|
||||
|
||||
```powershell
|
||||
cd C:\data\code\ravenqbi\AyaNovaQBI
|
||||
|
||||
# Build using MSBuild (comes with VS)
|
||||
& "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe" /p:Configuration=Release
|
||||
|
||||
|
||||
# Or open in Visual Studio and build from there
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 7: Create Snapshot and Backup
|
||||
|
||||
### 7.1 Clean Up Before Snapshot
|
||||
|
||||
Inside the VM:
|
||||
```powershell
|
||||
# Clear temp files
|
||||
Remove-Item -Recurse -Force $env:TEMP\*
|
||||
|
||||
# Clear Windows temp
|
||||
Remove-Item -Recurse -Force C:\Windows\Temp\* -ErrorAction SilentlyContinue
|
||||
|
||||
# Empty recycle bin
|
||||
Clear-RecycleBin -Force -ErrorAction SilentlyContinue
|
||||
```
|
||||
|
||||
Shut down the VM cleanly: **Start → Power → Shut down**
|
||||
|
||||
### 7.2 Create Snapshot
|
||||
|
||||
In VirtualBox Manager:
|
||||
1. Select your VM
|
||||
2. Click **Snapshots** (next to Details)
|
||||
3. Click **Take** (camera icon)
|
||||
4. Name: `Fresh-Build-Environment-Working`
|
||||
5. Description: `Clean install with all build tools. Full build tested and working.`
|
||||
6. Click OK
|
||||
|
||||
This snapshot lets you restore to this exact state if anything breaks later.
|
||||
|
||||
### 7.3 Export VM for Backup
|
||||
|
||||
For a portable backup you can store on an external drive:
|
||||
|
||||
1. **File → Export Appliance**
|
||||
2. Select your VM, click Next
|
||||
3. Choose location (external drive recommended)
|
||||
4. Format: OVF 2.0
|
||||
5. File: `AyaNova-Build-VM-v8.ova`
|
||||
6. Click Next, then Export
|
||||
|
||||
This creates a single file (~30-50GB) that can be imported into VirtualBox on any machine.
|
||||
|
||||
### 7.4 Backup Strategy
|
||||
|
||||
Store the exported .ova file:
|
||||
- On an external drive (your monthly backup drives)
|
||||
- Separate from your regular backups (it's a different kind of asset)
|
||||
|
||||
When to re-export:
|
||||
- After major tool updates (new .NET SDK version, etc.)
|
||||
- After significant build process changes
|
||||
- Quarterly, as a habit
|
||||
|
||||
---
|
||||
|
||||
## Part 8: Day-to-Day Usage
|
||||
|
||||
### 8.1 Starting the VM
|
||||
|
||||
1. Open VirtualBox
|
||||
2. Select `AyaNova-Build-v8`
|
||||
3. Click **Start**
|
||||
4. Wait for Windows to boot
|
||||
5. Log in
|
||||
|
||||
### 8.2 Updating Source Code
|
||||
|
||||
```powershell
|
||||
cd C:\data\code\raven
|
||||
svn update
|
||||
|
||||
cd C:\data\code\raven-client
|
||||
svn update
|
||||
|
||||
# etc.
|
||||
```
|
||||
|
||||
### 8.3 Running Builds
|
||||
|
||||
```powershell
|
||||
cd C:\data\code\raven
|
||||
.\build-release.bat
|
||||
```
|
||||
|
||||
### 8.4 Getting Files Out of the VM
|
||||
|
||||
Options:
|
||||
1. **Shared Folders**: Copy to a shared folder visible on host
|
||||
2. **Network**: If VM is bridged, access via network share
|
||||
3. **USB Drive**: Attach a USB drive to the VM
|
||||
4. **SVN Commit**: Commit build outputs to repo (not recommended for large binaries)
|
||||
|
||||
### 8.5 Snapshots for Safety
|
||||
|
||||
Before making any changes to the build environment:
|
||||
1. Shut down VM
|
||||
2. Take a snapshot
|
||||
3. Start VM
|
||||
4. Make changes
|
||||
5. If something breaks: Restore snapshot
|
||||
|
||||
---
|
||||
|
||||
## Part 9: Linux Host Notes
|
||||
|
||||
### 9.1 Recommended Linux Distributions
|
||||
|
||||
For a development workstation, these work well with VirtualBox:
|
||||
|
||||
| Distribution | Notes |
|
||||
|--------------|-------|
|
||||
| Ubuntu 22.04/24.04 LTS | Most compatible, largest community |
|
||||
| Linux Mint | Ubuntu-based, more Windows-like feel |
|
||||
| Fedora Workstation | Cutting edge, good hardware support |
|
||||
| Pop!_OS | Good for laptops, System76's distro |
|
||||
|
||||
### 9.2 ThinkPad Linux Compatibility
|
||||
|
||||
ThinkPads are generally excellent Linux laptops. The X13 Gen 3 AMD should work well with Ubuntu or Fedora out of the box.
|
||||
|
||||
### 9.3 Performance Tips
|
||||
|
||||
- **Enable VT-x/AMD-V**: In BIOS settings, ensure virtualization is enabled
|
||||
- **Nested Virtualization**: May be needed if VM uses Hyper-V features (usually not needed for this use case)
|
||||
- **SSD**: VM performance is heavily dependent on disk speed; SSD is essential
|
||||
- **RAM**: 32GB on host would be more comfortable than 16GB
|
||||
|
||||
### 9.4 Used ThinkPad Recommendations
|
||||
|
||||
If you're looking at a used/refurbished ThinkPad for Linux:
|
||||
|
||||
| Model | Era | Notes |
|
||||
|-------|-----|-------|
|
||||
| X1 Carbon Gen 9/10 | 2021-2022 | Excellent, lightweight |
|
||||
| T14s Gen 2/3 | 2021-2022 | Good value, business-class |
|
||||
| T480/T480s | 2018 | Older but very well supported, cheap |
|
||||
| P14s | 2020+ | More RAM options (up to 48GB) |
|
||||
|
||||
Look for 32GB RAM models if possible. Check that the specific CPU is supported (Intel generally has better Linux support than AMD, though AMD works fine too).
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Troubleshooting
|
||||
|
||||
### Windows 11 Won't Install (TPM Error)
|
||||
|
||||
VirtualBox 7.0+ supports TPM 2.0 emulation. If you get TPM errors:
|
||||
1. VM Settings → System → Motherboard
|
||||
2. Ensure TPM is set to v2.0
|
||||
|
||||
### Build Fails: Node Module Errors
|
||||
|
||||
If `npm ci` fails with native module errors:
|
||||
1. Delete `node_modules` folder
|
||||
2. Try `npm install` instead of `npm ci`
|
||||
3. If still failing, verify Node version is exactly 12.22.9
|
||||
|
||||
### VM Performance is Poor
|
||||
|
||||
- Increase VM RAM (if host has headroom)
|
||||
- Increase CPU cores
|
||||
- Ensure VirtualBox Guest Additions are installed
|
||||
- Check that 3D acceleration is enabled
|
||||
- Use SSD, not spinning disk
|
||||
|
||||
### Can't Access SVN Server
|
||||
|
||||
- Check VM network mode (NAT vs Bridged)
|
||||
- Verify SVN server is accessible from host
|
||||
- Check firewall settings on both host and VM
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: Quick Reference
|
||||
|
||||
### Build Commands
|
||||
|
||||
```powershell
|
||||
# Full build
|
||||
cd C:\data\code\raven
|
||||
.\build-release.bat
|
||||
|
||||
# Frontend only
|
||||
cd C:\data\code\raven-client\ayanova
|
||||
npm run build
|
||||
|
||||
# Docs only
|
||||
cd C:\data\code\raven\docs\8.0\ayanova
|
||||
mkdocs build
|
||||
|
||||
# QBI only (in Visual Studio or command line)
|
||||
msbuild C:\data\code\ravenqbi\AyaNovaQBI\AyaNovaQBI.csproj /p:Configuration=Release
|
||||
```
|
||||
|
||||
### Key Paths
|
||||
|
||||
| What | Path |
|
||||
|------|------|
|
||||
| Main repo | `C:\data\code\raven` |
|
||||
| Frontend | `C:\data\code\raven-client\ayanova` |
|
||||
| Launcher | `C:\data\code\raven-launcher` |
|
||||
| QBI | `C:\data\code\ravenqbi` |
|
||||
| Build outputs | `C:\data\code\raven\dist\installers` |
|
||||
|
||||
### Tool Locations
|
||||
|
||||
| Tool | Path |
|
||||
|------|------|
|
||||
| 7-Zip | `C:\Program Files\7-Zip\7z.exe` |
|
||||
| Inno Setup | `C:\Program Files (x86)\Inno Setup 6\ISCC.exe` |
|
||||
| MSBuild | `C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe` |
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-02-01 | Initial guide created |
|
||||
2
dist/install/windows/x64/lan.iss
vendored
2
dist/install/windows/x64/lan.iss
vendored
@@ -1,7 +1,7 @@
|
||||
; LAN install for internal network use only
|
||||
|
||||
#define MyAppName "AyaNova server"
|
||||
#define MyAppVersion "8.2.3"
|
||||
#define MyAppVersion "8.2.4"
|
||||
#define MyAppPublisher "Ground Zero Tech-Works, Inc."
|
||||
#define MyAppURL "https://ayanova.com/"
|
||||
#define MyAppLauncherExeName "ayanova-launcher.exe"
|
||||
|
||||
2
dist/install/windows/x64/standalone.iss
vendored
2
dist/install/windows/x64/standalone.iss
vendored
@@ -3,7 +3,7 @@
|
||||
; external to lan requires different config
|
||||
|
||||
#define MyAppName "AyaNova"
|
||||
#define MyAppVersion "8.2.3"
|
||||
#define MyAppVersion "8.2.4"
|
||||
#define MyAppPublisher "Ground Zero Tech-Works, Inc."
|
||||
#define MyAppURL "https://ayanova.com/"
|
||||
#define MyAppLauncherExeName "ayanova-launcher.exe"
|
||||
|
||||
2
dist/latest-version.json
vendored
2
dist/latest-version.json
vendored
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"latestversion":"8.2.3",
|
||||
"latestversion":"8.2.4",
|
||||
"changelogurl":"https://ayanova.com/docs/changelog/#ayanova-823"
|
||||
}
|
||||
@@ -20,7 +20,7 @@ This section provides information regarding the AyaNova web app client running i
|
||||
|
||||
#### Version
|
||||
|
||||
This is the version number of the AyaNova web application. When a new version of AyaNova is released this number will go up. AyaNova uses the industry standard [semantic versioning system](https://semver.org/) which is 3 numbers separated by periods for example `8.2.3`.
|
||||
This is the version number of the AyaNova web application. When a new version of AyaNova is released this number will go up. AyaNova uses the industry standard [semantic versioning system](https://semver.org/) which is 3 numbers separated by periods for example `8.2.4`.
|
||||
|
||||
The leftmost number, for example **8**.1.2 is the major release version indicating major changes to the program that are not backwards compatible to the prior major release version.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ After installation return here and continue to the [requesting a trial license](
|
||||
|
||||
This option is most appropriate when you are only interested in AyaNova as a service (i.e. not installed locally) and don't mind a shorter evaluation time frame.
|
||||
|
||||
You can try AyaNova for 7 days has a hosted service by [requesting a trial subscription](https://contact.ayanova.com/request) to get started.
|
||||
You can try AyaNova at no charge for one week as a hosted service by [requesting a trial subscription](https://contact.ayanova.com/request) to get started.
|
||||
|
||||
Once your trial account is created return here and continue to the [requesting a trial license](#requesting-a-trial-license) step to continue.
|
||||
|
||||
|
||||
@@ -6,14 +6,30 @@ If you are viewing this page from your local copy of AyaNova be advised that it
|
||||
|
||||
See the [upgrade instructions](ops-upgrade.md) section of this manual for details.
|
||||
|
||||
## 2024
|
||||
## 2025
|
||||
|
||||
### AyaNova 8.2.4
|
||||
|
||||
Released 2025-11-24
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Server: changes for compatibility with PostgreSQL 18.1. This update is only required if Postgres is 18.1 or newer.
|
||||
|
||||
### AyaNova 8.2.3
|
||||
|
||||
In progress...
|
||||
Released 2025-02-18
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Server: resolved edge case issue with Customer Notifications attached reports could fail to generate when platform is Windows / IIS and AYANOVA_REPORT_RENDER_API_URL_OVERRIDE configuration setting override in effect
|
||||
|
||||
- Docs: fixed outdated and missing installation steps for setting directory rights and ownership in [linux server install](ops-install-linux-server.md) for post 8.2.0 changes (hat tip to Steven L. for spotting this)
|
||||
|
||||
- Docs: improved PO part requests documentation section to specify that only Parts that match the Vendor selected on the PO will be offered for selection in the Part Requests list
|
||||
|
||||
## 2024
|
||||
|
||||
### AyaNova 8.2.2
|
||||
|
||||
Released 2024-10-23
|
||||
|
||||
@@ -34,7 +34,7 @@ We realize not every site can or will want to set up external access for their c
|
||||
|
||||
## Authorization Roles required
|
||||
|
||||
Many roles can *select* this object on other records where approriate. Editing or viewing this object in detail is only available to Users with the following [roles](ay-biz-admin-roles.md):
|
||||
Many roles can _select_ this object on other records where approriate. Editing or viewing this object in detail is only available to Users with the following [roles](ay-biz-admin-roles.md):
|
||||
|
||||
Full access
|
||||
|
||||
@@ -185,6 +185,10 @@ By default the values will all be filled in from the currently logged in User's
|
||||
|
||||
These settings mirror the same settings in the [User settings](home-user-settings.md) form.
|
||||
|
||||
#### Translation
|
||||
|
||||
_Required_ setting to control the [Translation](adm-translations.md) used for report generation by default.
|
||||
|
||||
#### Currency code
|
||||
|
||||
The Currency Code is a _required_ setting that controls how currency values are displayed. The code must be one of the 3 character alphabetic [ISO 4217 active currency codes](https://wikipedia.org/wiki/ISO_4217#Active_codes).
|
||||
|
||||
@@ -147,6 +147,8 @@ This menu option opens a form for selecting parts to add to this purchase order
|
||||
|
||||
This menu option opens a form for selecting parts to add to this purchase order that have been [requested](inv-part-requests.md) by a service technician on a work order when they have insufficient inventory of a part to provide service. Selections made in that form will be turned into PO items automatically and added to this purchase order and a connection will be made between the work order item part request and this purchase order item to facilitate notification and provide a navigation link between both records.
|
||||
|
||||
Note that only Part Requests for [Parts](inv-parts.md) that match the Vendor selected in the PO will be displayed here. In other words a [Part](inv-parts.md) must have a matching Vendor selected in it's [Wholesaler](inv-parts.md#wholesaler), [Alternative Wholesaler](inv-parts.md#alternative-wholesaler) or [Manufacturer](inv-parts.md#manufacturer) fields.
|
||||
|
||||
#### Receive all
|
||||
|
||||
Use this menu option to completely receive the items on order that have not been received yet. This option should only be used when the order matches the received quantities exactly. In all other cases you should receive items individually inside their line item edit form.
|
||||
|
||||
@@ -8,7 +8,7 @@ site_name: AyaNova manual
|
||||
site_dir: "../../../server/AyaNova/wwwroot/docs"
|
||||
site_url: https://ayanova.com/docs/
|
||||
strict: true
|
||||
copyright: Copyright © 2022-2024 Ground Zero Tech-Works Inc. REV-2024-12-02
|
||||
copyright: Copyright © 2022-2025 Ground Zero Tech-Works Inc. REV-2025-11-24
|
||||
extra:
|
||||
generator: false
|
||||
# Extensions
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<GenerateFullPaths>true</GenerateFullPaths>
|
||||
<Version>8.2.3</Version>
|
||||
<FileVersion>8.2.3.0</FileVersion>
|
||||
<Version>8.2.4</Version>
|
||||
<FileVersion>8.2.4.0</FileVersion>
|
||||
<ApplicationIcon>ayanova.ico</ApplicationIcon>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
<noWarn>1591</noWarn>
|
||||
|
||||
@@ -623,7 +623,7 @@ namespace AyaNova
|
||||
_newLog.LogDebug("DB integrity check");
|
||||
DbUtil.CheckFingerPrintAsync(AySchema.EXPECTED_COLUMN_COUNT,
|
||||
AySchema.EXPECTED_INDEX_COUNT,
|
||||
AySchema.EXPECTED_CHECK_CONSTRAINTS,
|
||||
//case 4640 AySchema.EXPECTED_CHECK_CONSTRAINTS,
|
||||
AySchema.EXPECTED_FOREIGN_KEY_CONSTRAINTS,
|
||||
AySchema.EXPECTED_VIEWS,
|
||||
AySchema.EXPECTED_ROUTINES,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,33 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Util;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AyaNova.Biz
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Notification processor
|
||||
/// turn notifyEvent records into inappnotification records for in app viewing and / or deliver smtp notifications seperately
|
||||
///
|
||||
///
|
||||
/// </summary>
|
||||
internal static class CoreJobCustomerNotify
|
||||
{
|
||||
private static bool NotifyIsRunning = false;
|
||||
private static ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("CoreJobCustomerNotify");
|
||||
private static ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger(
|
||||
"CoreJobCustomerNotify"
|
||||
);
|
||||
private static DateTime lastRun = DateTime.MinValue;
|
||||
|
||||
#if (DEBUG)
|
||||
private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 21);//no more frequently than once every 20 seconds
|
||||
#else
|
||||
private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 1, 1);//no more frequently than once every 1 minute
|
||||
#if (DEBUG)
|
||||
private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 21); //no more frequently than once every 20 seconds
|
||||
#else
|
||||
private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 1, 1); //no more frequently than once every 1 minute
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -43,7 +44,9 @@ namespace AyaNova.Biz
|
||||
//This will get triggered roughly every minute, but we don't want to deliver that frequently
|
||||
if (DateTime.UtcNow - lastRun < RUN_EVERY_INTERVAL)
|
||||
{
|
||||
log.LogTrace($"CustomerNotify ran less than {RUN_EVERY_INTERVAL} ago, exiting this cycle");
|
||||
log.LogTrace(
|
||||
$"CustomerNotify ran less than {RUN_EVERY_INTERVAL} ago, exiting this cycle"
|
||||
);
|
||||
return;
|
||||
}
|
||||
try
|
||||
@@ -54,18 +57,31 @@ namespace AyaNova.Biz
|
||||
using (AyContext ct = AyaNova.Util.ServiceProviderProvider.DBContext)
|
||||
{
|
||||
var customerevents = await ct.CustomerNotifyEvent.AsNoTracking().ToListAsync();
|
||||
log.LogDebug($"Found {customerevents.Count} CustomerNotifyEvents to examine for potential delivery");
|
||||
log.LogDebug(
|
||||
$"Found {customerevents.Count} CustomerNotifyEvents to examine for potential delivery"
|
||||
);
|
||||
|
||||
//iterate and deliver
|
||||
foreach (var customernotifyevent in customerevents)
|
||||
{
|
||||
//no notifications for inactive users, just delete it as if it was delivered
|
||||
var CustInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == customernotifyevent.CustomerId).Select(x => new { x.Name, x.Active, x.Tags, x.EmailAddress }).FirstOrDefaultAsync();
|
||||
|
||||
//no notifications for inactive users, just delete it as if it was delivered
|
||||
var CustInfo = await ct
|
||||
.Customer.AsNoTracking()
|
||||
.Where(x => x.Id == customernotifyevent.CustomerId)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
x.Active,
|
||||
x.Tags,
|
||||
x.EmailAddress,
|
||||
})
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (!CustInfo.Active)
|
||||
{
|
||||
log.LogDebug($"Inactive Customer {CustInfo.Name}, removing notify rather than delivering it: {customernotifyevent}");
|
||||
log.LogDebug(
|
||||
$"Inactive Customer {CustInfo.Name}, removing notify rather than delivering it: {customernotifyevent}"
|
||||
);
|
||||
ct.CustomerNotifyEvent.Remove(customernotifyevent);
|
||||
await ct.SaveChangesAsync();
|
||||
continue;
|
||||
@@ -73,29 +89,42 @@ namespace AyaNova.Biz
|
||||
|
||||
if (string.IsNullOrWhiteSpace(CustInfo.EmailAddress))
|
||||
{
|
||||
log.LogDebug($"Customer {CustInfo.Name} has no email address, removing notify rather than delivering it: {customernotifyevent}");
|
||||
log.LogDebug(
|
||||
$"Customer {CustInfo.Name} has no email address, removing notify rather than delivering it: {customernotifyevent}"
|
||||
);
|
||||
ct.CustomerNotifyEvent.Remove(customernotifyevent);
|
||||
await ct.SaveChangesAsync();
|
||||
continue;
|
||||
}
|
||||
|
||||
//Get subscription for delivery
|
||||
var Subscription = await ct.CustomerNotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == customernotifyevent.CustomerNotifySubscriptionId);
|
||||
var Subscription = await ct
|
||||
.CustomerNotifySubscription.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x =>
|
||||
x.Id == customernotifyevent.CustomerNotifySubscriptionId
|
||||
);
|
||||
|
||||
//NOTE: There is no need to separate out future delivery and immediate delivery because
|
||||
// All events have an event date, it's either immediate upon creation or it's future
|
||||
// but not all events have an age value including ones with future event dates,
|
||||
// and the default agevalue and advancenotice are both zero regardless so the block below works for either future or immediate deliveries
|
||||
// but not all events have an age value including ones with future event dates,
|
||||
// and the default agevalue and advancenotice are both zero regardless so the block below works for either future or immediate deliveries
|
||||
|
||||
var deliverAfter = customernotifyevent.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice;
|
||||
var deliverAfter =
|
||||
customernotifyevent.EventDate
|
||||
+ Subscription.AgeValue
|
||||
- Subscription.AdvanceNotice;
|
||||
if (deliverAfter < DateTime.UtcNow)
|
||||
{
|
||||
//Do the delivery, it's kosher
|
||||
await DeliverCustomerNotificationSMTP(customernotifyevent, Subscription, CustInfo.EmailAddress, ct);
|
||||
await DeliverCustomerNotificationSMTP(
|
||||
customernotifyevent,
|
||||
Subscription,
|
||||
CustInfo.EmailAddress,
|
||||
ct
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -104,25 +133,28 @@ namespace AyaNova.Biz
|
||||
}
|
||||
finally
|
||||
{
|
||||
log.LogDebug("CustomerNotify is done setting to not running state and tagging lastRun timestamp");
|
||||
log.LogDebug(
|
||||
"CustomerNotify is done setting to not running state and tagging lastRun timestamp"
|
||||
);
|
||||
lastRun = DateTime.UtcNow;
|
||||
NotifyIsRunning = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//===
|
||||
private static async Task DeliverCustomerNotificationSMTP(CustomerNotifyEvent ne, CustomerNotifySubscription subscription, string deliveryAddress, AyContext ct)
|
||||
private static async Task DeliverCustomerNotificationSMTP(
|
||||
CustomerNotifyEvent ne,
|
||||
CustomerNotifySubscription subscription,
|
||||
string deliveryAddress,
|
||||
AyContext ct
|
||||
)
|
||||
{
|
||||
|
||||
var DeliveryLogItem = new CustomerNotifyDeliveryLog()
|
||||
{
|
||||
Processed = DateTime.UtcNow,
|
||||
ObjectId = ne.ObjectId,
|
||||
CustomerNotifySubscriptionId = ne.CustomerNotifySubscriptionId,
|
||||
Fail = false
|
||||
Fail = false,
|
||||
};
|
||||
|
||||
try
|
||||
@@ -131,21 +163,25 @@ namespace AyaNova.Biz
|
||||
if (string.IsNullOrWhiteSpace(deliveryAddress))
|
||||
{
|
||||
DeliveryLogItem.Fail = true;
|
||||
DeliveryLogItem.Error = $"No email address provided for smtp delivery; event: {ne}";
|
||||
DeliveryLogItem.Error =
|
||||
$"No email address provided for smtp delivery; event: {ne}";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive)
|
||||
{
|
||||
await NotifyEventHelper.AddOpsProblemEvent($"Email notifications are set to OFF at server, unable to send Customer email notification for this event:{ne}");
|
||||
log.LogInformation($"** WARNING: SMTP notification is currently set to Active=False; unable to deliver Customer email notification, [CustomerId={ne.CustomerId}, Customer Notify subscription={ne.CustomerNotifySubscriptionId}]. Change this setting or remove all Customer notifications if this is permanent **");
|
||||
await NotifyEventHelper.AddOpsProblemEvent(
|
||||
$"Email notifications are set to OFF at server, unable to send Customer email notification for this event:{ne}"
|
||||
);
|
||||
log.LogInformation(
|
||||
$"** WARNING: SMTP notification is currently set to Active=False; unable to deliver Customer email notification, [CustomerId={ne.CustomerId}, Customer Notify subscription={ne.CustomerNotifySubscriptionId}]. Change this setting or remove all Customer notifications if this is permanent **"
|
||||
);
|
||||
DeliveryLogItem.Fail = true;
|
||||
DeliveryLogItem.Error = $"Email notifications are set to OFF at server, unable to send Customer email notification for this event: {ne}";
|
||||
DeliveryLogItem.Error =
|
||||
$"Email notifications are set to OFF at server, unable to send Customer email notification for this event: {ne}";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
//BUILD SUBJECT AND BODY FROM TOKENS IF REQUIRED
|
||||
var Subject = subscription.Subject;
|
||||
var Body = subscription.Template;
|
||||
@@ -157,54 +193,98 @@ namespace AyaNova.Biz
|
||||
{
|
||||
case AyaType.Quote:
|
||||
{
|
||||
var qt = await ct.Quote.AsNoTracking().FirstOrDefaultAsync(z => z.Id == ne.ObjectId);
|
||||
var qt = await ct
|
||||
.Quote.AsNoTracking()
|
||||
.FirstOrDefaultAsync(z => z.Id == ne.ObjectId);
|
||||
if (qt == null)
|
||||
{
|
||||
//maybe deleted, this can't proceed
|
||||
throw new ApplicationException($"Unable to make delivery for customer notify event as Quote {ne.Name} was not found during delivery, deleted?");
|
||||
throw new ApplicationException(
|
||||
$"Unable to make delivery for customer notify event as Quote {ne.Name} was not found during delivery, deleted?"
|
||||
);
|
||||
}
|
||||
|
||||
var CustomerName = await ct.Customer.AsNoTracking().Where(x => x.Id == qt.CustomerId).Select(x => x.Name).FirstOrDefaultAsync();
|
||||
var CustomerName = await ct
|
||||
.Customer.AsNoTracking()
|
||||
.Where(x => x.Id == qt.CustomerId)
|
||||
.Select(x => x.Name)
|
||||
.FirstOrDefaultAsync();
|
||||
Subject = SetQuoteTokens(Subject, qt, CustomerName);
|
||||
Body = SetQuoteTokens(Body, qt, CustomerName);
|
||||
}
|
||||
break;
|
||||
case AyaType.WorkOrder:
|
||||
{
|
||||
var wo = await ct.WorkOrder.AsNoTracking().FirstOrDefaultAsync(z => z.Id == ne.ObjectId);
|
||||
var wo = await ct
|
||||
.WorkOrder.AsNoTracking()
|
||||
.FirstOrDefaultAsync(z => z.Id == ne.ObjectId);
|
||||
if (wo == null)
|
||||
{
|
||||
//maybe deleted, this can't proceed
|
||||
throw new ApplicationException($"Unable to make delivery for customer notify event as WorkOrder {ne.Name} was not found during delivery, deleted?");
|
||||
throw new ApplicationException(
|
||||
$"Unable to make delivery for customer notify event as WorkOrder {ne.Name} was not found during delivery, deleted?"
|
||||
);
|
||||
}
|
||||
|
||||
var CustomerName = await ct.Customer.AsNoTracking().Where(x => x.Id == wo.CustomerId).Select(x => x.Name).FirstOrDefaultAsync();
|
||||
var StatusName = await ct.WorkOrderStatus.AsNoTracking().Where(x => x.Id == wo.LastStatusId).Select(x => x.Name).FirstOrDefaultAsync();
|
||||
Subject = SetWorkOrderTokens(Subject, wo, CustomerName, StatusName);
|
||||
Body = SetWorkOrderTokens(Body, wo, CustomerName, StatusName);
|
||||
var CustomerName = await ct
|
||||
.Customer.AsNoTracking()
|
||||
.Where(x => x.Id == wo.CustomerId)
|
||||
.Select(x => x.Name)
|
||||
.FirstOrDefaultAsync();
|
||||
var StatusName = await ct
|
||||
.WorkOrderStatus.AsNoTracking()
|
||||
.Where(x => x.Id == wo.LastStatusId)
|
||||
.Select(x => x.Name)
|
||||
.FirstOrDefaultAsync();
|
||||
Subject = SetWorkOrderTokens(
|
||||
Subject,
|
||||
wo,
|
||||
CustomerName,
|
||||
StatusName
|
||||
);
|
||||
Body = SetWorkOrderTokens(
|
||||
Body,
|
||||
wo,
|
||||
CustomerName,
|
||||
StatusName
|
||||
);
|
||||
}
|
||||
break;
|
||||
case AyaType.CustomerServiceRequest:
|
||||
{
|
||||
var csr = await ct.CustomerServiceRequest.AsNoTracking().FirstOrDefaultAsync(z => z.Id == ne.ObjectId);
|
||||
var csr = await ct
|
||||
.CustomerServiceRequest.AsNoTracking()
|
||||
.FirstOrDefaultAsync(z => z.Id == ne.ObjectId);
|
||||
if (csr == null)
|
||||
{
|
||||
//maybe deleted, this can't proceed
|
||||
throw new ApplicationException($"Unable to make delivery for customer notify event as CustomerServiceRequest {ne.Name} was not found during delivery, deleted?");
|
||||
throw new ApplicationException(
|
||||
$"Unable to make delivery for customer notify event as CustomerServiceRequest {ne.Name} was not found during delivery, deleted?"
|
||||
);
|
||||
}
|
||||
|
||||
var CustomerName = await ct.Customer.AsNoTracking().Where(x => x.Id == csr.CustomerId).Select(x => x.Name).FirstOrDefaultAsync();
|
||||
var UserName = await ct.User.AsNoTracking().Where(x => x.Id == csr.RequestedByUserId).Select(x => x.Name).FirstOrDefaultAsync();
|
||||
Subject = SetCSRTokens(Subject, csr, CustomerName, UserName);
|
||||
var CustomerName = await ct
|
||||
.Customer.AsNoTracking()
|
||||
.Where(x => x.Id == csr.CustomerId)
|
||||
.Select(x => x.Name)
|
||||
.FirstOrDefaultAsync();
|
||||
var UserName = await ct
|
||||
.User.AsNoTracking()
|
||||
.Where(x => x.Id == csr.RequestedByUserId)
|
||||
.Select(x => x.Name)
|
||||
.FirstOrDefaultAsync();
|
||||
Subject = SetCSRTokens(
|
||||
Subject,
|
||||
csr,
|
||||
CustomerName,
|
||||
UserName
|
||||
);
|
||||
Body = SetCSRTokens(Body, csr, CustomerName, UserName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer;
|
||||
//generate report if applicable
|
||||
bool isReportableEvent = false;
|
||||
@@ -220,7 +300,12 @@ namespace AyaNova.Biz
|
||||
{
|
||||
long subTranslationId = (long)subscription.TranslationId;
|
||||
|
||||
ReportBiz biz = new ReportBiz(ct, 1, subTranslationId, AuthorizationRoles.BizAdmin);
|
||||
ReportBiz biz = new ReportBiz(
|
||||
ct,
|
||||
1,
|
||||
subTranslationId,
|
||||
AuthorizationRoles.BizAdmin
|
||||
);
|
||||
//example with workorder report
|
||||
//{"AType":34,"selectedRowIds":[355],"ReportId":9,"ClientMeta":{"UserName":"AyaNova SuperUser","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNjQ2NzgyNTc4IiwiaXNzIjoiYXlhbm92YS5jb20iLCJpZCI6IjEifQ.ad7Acq54JCRGitDWKDJFFnqKkidbdaKaFmj-RA_RG5E","DownloadToken":"NdoU8ca3LG4L39Tj2oi3UReeeM7FLevTgbgopTPhGbA","TimeZoneName":"America/Los_Angeles","LanguageName":"en-US","Hour12":true,"CurrencyName":"USD","DefaultLocale":"en","PDFDate":"3/3/22","PDFTime":"3:38 PM"}}
|
||||
|
||||
@@ -235,61 +320,106 @@ namespace AyaNova.Biz
|
||||
var pdfDate = new DateTime().ToShortDateString();
|
||||
var pdfTime = new DateTime().ToShortTimeString();
|
||||
var h12 = subscription.Hour12 ? "true" : "false";
|
||||
reportRequest.ClientMeta = JToken.Parse($"{{'UserName':'-','Authorization':'Bearer {jwt}','TimeZoneName':'{subscription.TimeZoneOverride}','LanguageName':'{subscription.LanguageOverride}','Hour12':{h12},'CurrencyName':'{subscription.CurrencyName}','DefaultLocale':'en','PDFDate':'{pdfDate}','PDFTime':'{pdfTime}'}}");
|
||||
reportRequest.ClientMeta = JToken.Parse(
|
||||
$"{{'UserName':'-','Authorization':'Bearer {jwt}','TimeZoneName':'{subscription.TimeZoneOverride}','LanguageName':'{subscription.LanguageOverride}','Hour12':{h12},'CurrencyName':'{subscription.CurrencyName}','DefaultLocale':'en','PDFDate':'{pdfDate}','PDFTime':'{pdfTime}'}}"
|
||||
);
|
||||
//get port number
|
||||
var match = System.Text.RegularExpressions.Regex.Match(ServerBootConfig.AYANOVA_USE_URLS, "[0-9]+");
|
||||
var API_URL = $"http://127.0.0.1:{match.Value}/api/{AyaNovaVersion.CurrentApiVersion}/";
|
||||
var jobid = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT), API_URL, "CUSTOMER NOTIFICATION - NO USER");
|
||||
var match = System.Text.RegularExpressions.Regex.Match(
|
||||
ServerBootConfig.AYANOVA_USE_URLS,
|
||||
"[0-9]+"
|
||||
);
|
||||
var API_URL =
|
||||
$"http://127.0.0.1:{match.Value}/api/{AyaNovaVersion.CurrentApiVersion}/";
|
||||
var jobid = await biz.RequestRenderReport(
|
||||
reportRequest,
|
||||
DateTime.UtcNow.AddMinutes(
|
||||
ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT
|
||||
),
|
||||
API_URL,
|
||||
ServerBootConfig.CUSTOMER_NOTIFICATION_ATTACHED_REPORT_RENDER_USERNAME
|
||||
);
|
||||
if (jobid == null)
|
||||
{
|
||||
throw new ApplicationException($"Report render job id is null failed to start");
|
||||
throw new ApplicationException(
|
||||
$"Report render job id is null failed to start"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool done = false;
|
||||
DateTime bailAfter = DateTime.Now.AddMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT);
|
||||
DateTime bailAfter = DateTime.Now.AddMinutes(
|
||||
ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT
|
||||
);
|
||||
while (!done && DateTime.Now < bailAfter)
|
||||
{
|
||||
var status = await JobsBiz.GetJobStatusAsync((Guid)jobid);
|
||||
switch (status)
|
||||
{
|
||||
case JobStatus.Completed:
|
||||
{
|
||||
done = true;
|
||||
//get job logs and parse file name from it
|
||||
JobOperationsBiz jobopsbiz = new JobOperationsBiz(ct, 1, AuthorizationRoles.BizAdmin);
|
||||
List<JobOperationsLogInfoItem> log = await jobopsbiz.GetJobLogListAsync((Guid)jobid);
|
||||
var lastLog = log[log.Count - 1];
|
||||
var lastLogJ = JObject.Parse(lastLog.StatusText);
|
||||
var path = (string)lastLogJ["reportfilename"];
|
||||
var FilePath = FileUtil.GetFullPathForTemporaryFile(path);
|
||||
var FileName = FileUtil.StringToSafeFileName(await TranslationBiz.GetTranslationStaticAsync(ne.AyaType.ToString(), subTranslationId, ct) + $"-{ne.Name}.pdf").ToLowerInvariant();
|
||||
await m.SendEmailAsync(deliveryAddress, Subject, Body, ServerGlobalOpsSettingsCache.Notify, FilePath, FileName);
|
||||
break;
|
||||
}
|
||||
{
|
||||
done = true;
|
||||
//get job logs and parse file name from it
|
||||
JobOperationsBiz jobopsbiz = new JobOperationsBiz(
|
||||
ct,
|
||||
1,
|
||||
AuthorizationRoles.BizAdmin
|
||||
);
|
||||
List<JobOperationsLogInfoItem> log =
|
||||
await jobopsbiz.GetJobLogListAsync((Guid)jobid);
|
||||
var lastLog = log[log.Count - 1];
|
||||
var lastLogJ = JObject.Parse(lastLog.StatusText);
|
||||
var path = (string)lastLogJ["reportfilename"];
|
||||
var FilePath = FileUtil.GetFullPathForTemporaryFile(
|
||||
path
|
||||
);
|
||||
var FileName = FileUtil
|
||||
.StringToSafeFileName(
|
||||
await TranslationBiz.GetTranslationStaticAsync(
|
||||
ne.AyaType.ToString(),
|
||||
subTranslationId,
|
||||
ct
|
||||
) + $"-{ne.Name}.pdf"
|
||||
)
|
||||
.ToLowerInvariant();
|
||||
await m.SendEmailAsync(
|
||||
deliveryAddress,
|
||||
Subject,
|
||||
Body,
|
||||
ServerGlobalOpsSettingsCache.Notify,
|
||||
FilePath,
|
||||
FileName
|
||||
);
|
||||
break;
|
||||
}
|
||||
case JobStatus.Failed:
|
||||
case JobStatus.Absent:
|
||||
throw new ApplicationException($"REPORT RENDER JOB {jobid} started but failed");
|
||||
|
||||
throw new ApplicationException(
|
||||
$"REPORT RENDER JOB {jobid} started but failed"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
if (!done)
|
||||
throw new TimeoutException("JOB FAILED DUE TO REPORT RENDER TIMEOUT");
|
||||
throw new TimeoutException(
|
||||
"JOB FAILED DUE TO REPORT RENDER TIMEOUT"
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
await m.SendEmailAsync(deliveryAddress, Subject, Body, ServerGlobalOpsSettingsCache.Notify);
|
||||
|
||||
await m.SendEmailAsync(
|
||||
deliveryAddress,
|
||||
Subject,
|
||||
Body,
|
||||
ServerGlobalOpsSettingsCache.Notify
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyEventHelper.AddOpsProblemEvent("SMTP Customer Notification failed", ex);
|
||||
DeliveryLogItem.Fail = true;
|
||||
DeliveryLogItem.Error = $"SMTP Notification failed to deliver for this Customer notify event: {ne}, message: {ex.Message}";
|
||||
DeliveryLogItem.Error =
|
||||
$"SMTP Notification failed to deliver for this Customer notify event: {ne}, message: {ex.Message}";
|
||||
log.LogDebug(ex, $"DeliverSMTP Failure delivering Customer notify event: {ne}");
|
||||
}
|
||||
finally
|
||||
@@ -305,7 +435,11 @@ namespace AyaNova.Biz
|
||||
|
||||
private static string SetQuoteTokens(string TheField, Quote qt, string CustomerName)
|
||||
{
|
||||
MatchCollection matches = Regex.Matches(TheField, @"\{{(.|\n)*?\}}", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
|
||||
MatchCollection matches = Regex.Matches(
|
||||
TheField,
|
||||
@"\{{(.|\n)*?\}}",
|
||||
RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled
|
||||
);
|
||||
//{{.*?}}
|
||||
foreach (Match KeyMatch in matches)
|
||||
{
|
||||
@@ -329,17 +463,24 @@ namespace AyaNova.Biz
|
||||
case "{{QuoteSerialNumber}}":
|
||||
TheField = TheField.Replace(KeyMatch.Value, qt.Serial.ToString());
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return TheField;
|
||||
}
|
||||
|
||||
|
||||
private static string SetWorkOrderTokens(string TheField, WorkOrder wo, string CustomerName, string statusName)
|
||||
private static string SetWorkOrderTokens(
|
||||
string TheField,
|
||||
WorkOrder wo,
|
||||
string CustomerName,
|
||||
string statusName
|
||||
)
|
||||
{
|
||||
MatchCollection matches = Regex.Matches(TheField, @"\{{(.|\n)*?\}}", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
|
||||
MatchCollection matches = Regex.Matches(
|
||||
TheField,
|
||||
@"\{{(.|\n)*?\}}",
|
||||
RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled
|
||||
);
|
||||
//{{.*?}}
|
||||
foreach (Match KeyMatch in matches)
|
||||
{
|
||||
@@ -363,17 +504,24 @@ namespace AyaNova.Biz
|
||||
case "{{WorkOrderSerialNumber}}":
|
||||
TheField = TheField.Replace(KeyMatch.Value, wo.Serial.ToString());
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return TheField;
|
||||
}
|
||||
|
||||
|
||||
private static string SetCSRTokens(string TheField, CustomerServiceRequest csr, string CustomerName, string requestedBy)
|
||||
private static string SetCSRTokens(
|
||||
string TheField,
|
||||
CustomerServiceRequest csr,
|
||||
string CustomerName,
|
||||
string requestedBy
|
||||
)
|
||||
{
|
||||
MatchCollection matches = Regex.Matches(TheField, @"\{{(.|\n)*?\}}", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
|
||||
MatchCollection matches = Regex.Matches(
|
||||
TheField,
|
||||
@"\{{(.|\n)*?\}}",
|
||||
RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled
|
||||
);
|
||||
//{{.*?}}
|
||||
foreach (Match KeyMatch in matches)
|
||||
{
|
||||
@@ -407,9 +555,5 @@ namespace AyaNova.Biz
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
|
||||
} //eoc
|
||||
} //eons
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace AyaNova.Util
|
||||
|
||||
internal const long EXPECTED_COLUMN_COUNT = 1389;
|
||||
internal const long EXPECTED_INDEX_COUNT = 160;
|
||||
internal const long EXPECTED_CHECK_CONSTRAINTS = 561;
|
||||
//case 4640 PG 18 release adds extra check constraints beyond what we create causing this to fail and
|
||||
//it's not critical anyway so just removing it as simple solution
|
||||
//internal const long EXPECTED_CHECK_CONSTRAINTS = 561;
|
||||
internal const long EXPECTED_FOREIGN_KEY_CONSTRAINTS = 204;
|
||||
internal const long EXPECTED_VIEWS = 11;
|
||||
internal const long EXPECTED_ROUTINES = 2;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AyaNova.Util
|
||||
/// </summary>
|
||||
internal static class AyaNovaVersion
|
||||
{
|
||||
public const string VersionString = "8.2.3";
|
||||
public const string VersionString = "8.2.4";
|
||||
public const string FullNameAndVersion = "AyaNova server " + VersionString;
|
||||
public const string CurrentApiVersion="v8";
|
||||
}//eoc
|
||||
|
||||
@@ -798,7 +798,7 @@ namespace AyaNova.Util
|
||||
internal static async Task CheckFingerPrintAsync(
|
||||
long ExpectedColumns,
|
||||
long ExpectedIndexes,
|
||||
long ExpectedCheckConstraints,
|
||||
//case 4640 long ExpectedCheckConstraints,
|
||||
long ExpectedForeignKeyConstraints,
|
||||
long ExpectedViews,
|
||||
long ExpectedRoutines,
|
||||
@@ -961,12 +961,13 @@ namespace AyaNova.Util
|
||||
|
||||
if (ExpectedColumns != actualColumns
|
||||
|| ExpectedIndexes != actualIndexes
|
||||
|| ExpectedCheckConstraints != actualCheckConstraints
|
||||
//case 4640 || ExpectedCheckConstraints != actualCheckConstraints
|
||||
|| ExpectedForeignKeyConstraints != actualForeignKeyConstraints
|
||||
|| ExpectedRoutines != actualRoutines
|
||||
|| ExpectedViews != actualViews)
|
||||
{
|
||||
var err = $"E1030 - Database integrity check failed (C{actualColumns}:I{actualIndexes}:CC{actualCheckConstraints}:FC{actualForeignKeyConstraints}:V{actualViews}:R{actualRoutines})";
|
||||
//var err = $"E1030 - Database integrity check failed (C{actualColumns}:I{actualIndexes}:CC{actualCheckConstraints}:FC{actualForeignKeyConstraints}:V{actualViews}:R{actualRoutines})";
|
||||
var err = $"E1030 - Database integrity check failed (C{actualColumns}:I{actualIndexes}:FC{actualForeignKeyConstraints}:V{actualViews}:R{actualRoutines})";
|
||||
_log.LogCritical(err);
|
||||
throw new ApplicationException(err);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
|
||||
namespace AyaNova.Util
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Contains config values from bootup
|
||||
/// </summary>
|
||||
@@ -14,23 +12,29 @@ namespace AyaNova.Util
|
||||
{
|
||||
//############################################################################################################
|
||||
//STATIC HARD CODED COMPILE TIME DEFAULTS NOT SET THROUGH CONFIG
|
||||
internal const int FAILED_AUTH_DELAY = 3000;//ms
|
||||
internal const int JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY = 200;//ms this delay is a temporary measure to ensure super big time consuming batch jobs don't use all server CPU resources
|
||||
internal const int JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS = 5;//seconds between progress updates and checks for cancellation of long running jobs
|
||||
internal const int JOB_OBJECT_EMAIL_LOOP_DELAY = 500;//ms this delay ensures multiple email sendings in a job don't overwhelm the mail server
|
||||
internal const int FAILED_AUTH_DELAY = 3000; //ms
|
||||
internal const int JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY = 200; //ms this delay is a temporary measure to ensure super big time consuming batch jobs don't use all server CPU resources
|
||||
internal const int JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS = 5; //seconds between progress updates and checks for cancellation of long running jobs
|
||||
internal const int JOB_OBJECT_EMAIL_LOOP_DELAY = 500; //ms this delay ensures multiple email sendings in a job don't overwhelm the mail server
|
||||
|
||||
//UPLOAD LIMITS 1048576 = 1MiB for testing 10737420000 10737418240 10,737,418,240
|
||||
internal const long MAX_ATTACHMENT_UPLOAD_BYTES = 10737420000;//slight bit of overage as 10737418241=10GiB
|
||||
internal const long MAX_LOGO_UPLOAD_BYTES = 512000;//500KiB limit
|
||||
internal const long MAX_IMPORT_FILE_UPLOAD_BYTES = 104857600;//100MiB limit
|
||||
internal const long MAX_REPORT_TEMPLATE_UPLOAD_BYTES = 15728640;//15MiB limit; currently the largest v7 export for a report template is 828kb, I'm guessing 15mb is more than enough
|
||||
internal const long MAX_TRANSLATION_UPLOAD_BYTES = 15728640;//15MiB limit; currently export file is 200kb * 50 maximum at a time = 15mb
|
||||
internal const long MAX_ATTACHMENT_UPLOAD_BYTES = 10737420000; //slight bit of overage as 10737418241=10GiB
|
||||
internal const long MAX_LOGO_UPLOAD_BYTES = 512000; //500KiB limit
|
||||
internal const long MAX_IMPORT_FILE_UPLOAD_BYTES = 104857600; //100MiB limit
|
||||
internal const long MAX_REPORT_TEMPLATE_UPLOAD_BYTES = 15728640; //15MiB limit; currently the largest v7 export for a report template is 828kb, I'm guessing 15mb is more than enough
|
||||
internal const long MAX_TRANSLATION_UPLOAD_BYTES = 15728640; //15MiB limit; currently export file is 200kb * 50 maximum at a time = 15mb
|
||||
|
||||
//case 4632 safety constant as it's now referenced in multiple places
|
||||
internal const string CUSTOMER_NOTIFICATION_ATTACHED_REPORT_RENDER_USERNAME =
|
||||
"CUSTOMER NOTIFICATION - NO USER";
|
||||
|
||||
//############################################################################################################
|
||||
|
||||
//############################
|
||||
//SEEDING FLAG INTERNAL ONLY
|
||||
//used to speed up seeding with bypasses to normal validation etc
|
||||
internal static bool SEEDING { get; set; }
|
||||
|
||||
//############################
|
||||
|
||||
//############################
|
||||
@@ -42,17 +46,15 @@ namespace AyaNova.Util
|
||||
//Diagnostic static values used during development, may not be related to config at all, this is just a convenient class to put them in
|
||||
#if (DEBUG)
|
||||
internal static List<string> TranslationKeysRequested { get; set; }
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
//CONTENTROOTPATH
|
||||
//** Not intended for end users but required in release mode
|
||||
internal static string AYANOVA_CONTENT_ROOT_PATH { get; set; } //Note: set in startup.cs, not in program.cs as it requires startup IHostingEnvironment
|
||||
|
||||
|
||||
//LANGUAGE / Translation
|
||||
internal static string AYANOVA_DEFAULT_TRANSLATION { get; set; }
|
||||
|
||||
//** Not intended for end users
|
||||
internal static long AYANOVA_DEFAULT_TRANSLATION_ID { get; set; } //internal setting set at boot by TranslationBiz::ValidateTranslations
|
||||
|
||||
@@ -61,9 +63,9 @@ namespace AyaNova.Util
|
||||
internal static string AYANOVA_USE_URLS { get; set; }
|
||||
internal static int AYANOVA_REPORT_RENDERING_TIMEOUT { get; set; }
|
||||
|
||||
|
||||
//DATABASE
|
||||
internal static string AYANOVA_DB_CONNECTION { get; set; }
|
||||
|
||||
//** Not intended for end users
|
||||
internal static bool AYANOVA_PERMANENTLY_ERASE_DATABASE { get; set; }
|
||||
|
||||
@@ -81,13 +83,13 @@ namespace AyaNova.Util
|
||||
|
||||
//REPORT RENDERING BROWSER PATH (if not set then will attempt to auto-download on first render)
|
||||
internal static string AYANOVA_REPORT_RENDER_BROWSER_PATH { get; set; }
|
||||
//REPORT RENDERING BROWSER PARAMS
|
||||
|
||||
//REPORT RENDERING BROWSER PARAMS
|
||||
internal static string AYANOVA_REPORT_RENDER_BROWSER_PARAMS { get; set; }
|
||||
|
||||
//REPORT RENDERING API URL OVERRIDE
|
||||
internal static string AYANOVA_REPORT_RENDER_API_URL_OVERRIDE { get; set; }
|
||||
|
||||
|
||||
//LOGGING
|
||||
internal static string AYANOVA_LOG_PATH { get; set; }
|
||||
internal static string AYANOVA_LOG_LEVEL { get; set; }
|
||||
@@ -97,9 +99,10 @@ namespace AyaNova.Util
|
||||
internal static string AYANOVA_SET_SUPERUSER_PW { get; set; }
|
||||
|
||||
//HELPFUL INFORMATION FOR DIAGNOSTICS
|
||||
internal static Dictionary<string, string> BOOT_DIAGNOSTIC_INFO { get; set; } = new Dictionary<string, string>();
|
||||
internal static Dictionary<string, string> DBSERVER_DIAGNOSTIC_INFO { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
internal static Dictionary<string, string> BOOT_DIAGNOSTIC_INFO { get; set; } =
|
||||
new Dictionary<string, string>();
|
||||
internal static Dictionary<string, string> DBSERVER_DIAGNOSTIC_INFO { get; set; } =
|
||||
new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Populate the config from the configuration found at boot
|
||||
@@ -108,7 +111,6 @@ namespace AyaNova.Util
|
||||
/// <param name="config"></param>
|
||||
internal static void SetConfiguration(IConfigurationRoot config)
|
||||
{
|
||||
|
||||
#if (DEBUG)
|
||||
TranslationKeysRequested = new List<string>();
|
||||
#endif
|
||||
@@ -121,7 +123,9 @@ namespace AyaNova.Util
|
||||
//LANGUAGE
|
||||
//TranslationBiz will validate this later at boot pfc and ensure a sane default is set (English)
|
||||
AYANOVA_DEFAULT_TRANSLATION = config.GetValue<string>("AYANOVA_DEFAULT_TRANSLATION");
|
||||
AYANOVA_DEFAULT_TRANSLATION = string.IsNullOrWhiteSpace(AYANOVA_DEFAULT_TRANSLATION) ? "en" : AYANOVA_DEFAULT_TRANSLATION;
|
||||
AYANOVA_DEFAULT_TRANSLATION = string.IsNullOrWhiteSpace(AYANOVA_DEFAULT_TRANSLATION)
|
||||
? "en"
|
||||
: AYANOVA_DEFAULT_TRANSLATION;
|
||||
string lowTranslation = AYANOVA_DEFAULT_TRANSLATION.ToLowerInvariant();
|
||||
switch (lowTranslation)
|
||||
{
|
||||
@@ -149,12 +153,11 @@ namespace AyaNova.Util
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//LOGLEVEL
|
||||
AYANOVA_LOG_LEVEL = config.GetValue<string>("AYANOVA_LOG_LEVEL");
|
||||
AYANOVA_LOG_LEVEL = string.IsNullOrWhiteSpace(AYANOVA_LOG_LEVEL) ? "Info" : AYANOVA_LOG_LEVEL;
|
||||
AYANOVA_LOG_LEVEL = string.IsNullOrWhiteSpace(AYANOVA_LOG_LEVEL)
|
||||
? "Info"
|
||||
: AYANOVA_LOG_LEVEL;
|
||||
|
||||
//LOGGING DIAGNOSTIC LOG
|
||||
bTemp = config.GetValue<bool?>("AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG");
|
||||
@@ -162,30 +165,37 @@ namespace AyaNova.Util
|
||||
|
||||
//PORT / API
|
||||
AYANOVA_USE_URLS = config.GetValue<string>("AYANOVA_USE_URLS");
|
||||
AYANOVA_USE_URLS = string.IsNullOrWhiteSpace(AYANOVA_USE_URLS) ? "http://*:7575" : AYANOVA_USE_URLS;
|
||||
AYANOVA_USE_URLS = string.IsNullOrWhiteSpace(AYANOVA_USE_URLS)
|
||||
? "http://*:7575"
|
||||
: AYANOVA_USE_URLS;
|
||||
|
||||
AYANOVA_JWT_SECRET = config.GetValue<string>("AYANOVA_JWT_SECRET");
|
||||
|
||||
//backdoor back door password superuser reset
|
||||
AYANOVA_SET_SUPERUSER_PW = config.GetValue<string>("AYANOVA_SET_SUPERUSER_PW");
|
||||
|
||||
//REPORT RENDERING
|
||||
//REPORT RENDERING
|
||||
|
||||
//RENDER OVERRIDE URL FOR CORS ISSUES BEHIND IIS (case 4398)
|
||||
AYANOVA_REPORT_RENDER_API_URL_OVERRIDE = config.GetValue<string>("AYANOVA_REPORT_RENDER_API_URL_OVERRIDE");
|
||||
AYANOVA_REPORT_RENDER_API_URL_OVERRIDE = config.GetValue<string>(
|
||||
"AYANOVA_REPORT_RENDER_API_URL_OVERRIDE"
|
||||
);
|
||||
|
||||
//RENDER ENGINE PATH
|
||||
AYANOVA_REPORT_RENDER_BROWSER_PATH = ActualFullPath(config.GetValue<string>("AYANOVA_REPORT_RENDER_BROWSER_PATH"));
|
||||
//RENDER ENGINE PATH
|
||||
AYANOVA_REPORT_RENDER_BROWSER_PATH = ActualFullPath(
|
||||
config.GetValue<string>("AYANOVA_REPORT_RENDER_BROWSER_PATH")
|
||||
);
|
||||
|
||||
//RENDER ENGINE PARAMS
|
||||
AYANOVA_REPORT_RENDER_BROWSER_PARAMS = config.GetValue<string>("AYANOVA_REPORT_RENDER_BROWSER_PARAMS");
|
||||
AYANOVA_REPORT_RENDER_BROWSER_PARAMS = config.GetValue<string>(
|
||||
"AYANOVA_REPORT_RENDER_BROWSER_PARAMS"
|
||||
);
|
||||
|
||||
//PROCESS CONTROL
|
||||
int? nTemp = config.GetValue<int?>("AYANOVA_REPORT_RENDERING_TIMEOUT");
|
||||
AYANOVA_REPORT_RENDERING_TIMEOUT = (null == nTemp) ? 5 : (int)nTemp;//default
|
||||
if (AYANOVA_REPORT_RENDERING_TIMEOUT < 1) AYANOVA_REPORT_RENDERING_TIMEOUT = 1; //one minute minimum timeout
|
||||
|
||||
|
||||
AYANOVA_REPORT_RENDERING_TIMEOUT = (null == nTemp) ? 5 : (int)nTemp; //default
|
||||
if (AYANOVA_REPORT_RENDERING_TIMEOUT < 1)
|
||||
AYANOVA_REPORT_RENDERING_TIMEOUT = 1; //one minute minimum timeout
|
||||
|
||||
//DB
|
||||
AYANOVA_DB_CONNECTION = config.GetValue<string>("AYANOVA_DB_CONNECTION");
|
||||
@@ -197,41 +207,64 @@ namespace AyaNova.Util
|
||||
bTemp = config.GetValue<bool?>("AYANOVA_REMOVE_LICENSE_FROM_DB");
|
||||
AYANOVA_REMOVE_LICENSE_FROM_DB = (null == bTemp) ? false : (bool)bTemp;
|
||||
|
||||
|
||||
//FOLDERS
|
||||
string DataFolderPath = ActualFullPath(config.GetValue<string>("AYANOVA_DATA_PATH"));
|
||||
string LogPath = ActualFullPath(config.GetValue<string>("AYANOVA_LOG_PATH"));
|
||||
string AttachmentFilesPath = ActualFullPath(config.GetValue<string>("AYANOVA_ATTACHMENT_FILES_PATH"));
|
||||
string BackupFilesPath = ActualFullPath(config.GetValue<string>("AYANOVA_BACKUP_FILES_PATH"));
|
||||
string TempFilesPath = ActualFullPath(config.GetValue<string>("AYANOVA_TEMP_FILES_PATH"));
|
||||
AYANOVA_BACKUP_PG_DUMP_PATH = ActualFullPath(config.GetValue<string>("AYANOVA_BACKUP_PG_DUMP_PATH"));
|
||||
string AttachmentFilesPath = ActualFullPath(
|
||||
config.GetValue<string>("AYANOVA_ATTACHMENT_FILES_PATH")
|
||||
);
|
||||
string BackupFilesPath = ActualFullPath(
|
||||
config.GetValue<string>("AYANOVA_BACKUP_FILES_PATH")
|
||||
);
|
||||
string TempFilesPath = ActualFullPath(
|
||||
config.GetValue<string>("AYANOVA_TEMP_FILES_PATH")
|
||||
);
|
||||
AYANOVA_BACKUP_PG_DUMP_PATH = ActualFullPath(
|
||||
config.GetValue<string>("AYANOVA_BACKUP_PG_DUMP_PATH")
|
||||
);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(DataFolderPath))
|
||||
{
|
||||
//In this case *must* have paths for *everything* specified
|
||||
if (string.IsNullOrWhiteSpace(LogPath))
|
||||
throw new System.ArgumentNullException("AYANOVA_LOG_PATH configuration setting missing and required");
|
||||
throw new System.ArgumentNullException(
|
||||
"AYANOVA_LOG_PATH configuration setting missing and required"
|
||||
);
|
||||
if (string.IsNullOrWhiteSpace(AttachmentFilesPath))
|
||||
throw new System.ArgumentNullException("AYANOVA_ATTACHMENT_FILES_PATH configuration setting missing and required");
|
||||
throw new System.ArgumentNullException(
|
||||
"AYANOVA_ATTACHMENT_FILES_PATH configuration setting missing and required"
|
||||
);
|
||||
if (string.IsNullOrWhiteSpace(BackupFilesPath))
|
||||
throw new System.ArgumentNullException("AYANOVA_BACKUP_FILES_PATH configuration setting missing and required");
|
||||
throw new System.ArgumentNullException(
|
||||
"AYANOVA_BACKUP_FILES_PATH configuration setting missing and required"
|
||||
);
|
||||
if (string.IsNullOrWhiteSpace(TempFilesPath))
|
||||
throw new System.ArgumentNullException("AYANOVA_TEMP_FILES_PATH configuration setting missing and required");
|
||||
|
||||
throw new System.ArgumentNullException(
|
||||
"AYANOVA_TEMP_FILES_PATH configuration setting missing and required"
|
||||
);
|
||||
}
|
||||
|
||||
//set paths
|
||||
AYANOVA_LOG_PATH = (string.IsNullOrWhiteSpace(LogPath)) ? Path.Combine(DataFolderPath, "logs") : LogPath;
|
||||
AYANOVA_ATTACHMENT_FILES_PATH = (string.IsNullOrWhiteSpace(AttachmentFilesPath)) ? Path.Combine(DataFolderPath, "attachments") : AttachmentFilesPath;
|
||||
AYANOVA_BACKUP_FILES_PATH = (string.IsNullOrWhiteSpace(BackupFilesPath)) ? Path.Combine(DataFolderPath, "backups") : BackupFilesPath;
|
||||
AYANOVA_TEMP_FILES_PATH = (string.IsNullOrWhiteSpace(TempFilesPath)) ? Path.Combine(DataFolderPath, "temp") : TempFilesPath;
|
||||
|
||||
AYANOVA_LOG_PATH =
|
||||
(string.IsNullOrWhiteSpace(LogPath))
|
||||
? Path.Combine(DataFolderPath, "logs")
|
||||
: LogPath;
|
||||
AYANOVA_ATTACHMENT_FILES_PATH =
|
||||
(string.IsNullOrWhiteSpace(AttachmentFilesPath))
|
||||
? Path.Combine(DataFolderPath, "attachments")
|
||||
: AttachmentFilesPath;
|
||||
AYANOVA_BACKUP_FILES_PATH =
|
||||
(string.IsNullOrWhiteSpace(BackupFilesPath))
|
||||
? Path.Combine(DataFolderPath, "backups")
|
||||
: BackupFilesPath;
|
||||
AYANOVA_TEMP_FILES_PATH =
|
||||
(string.IsNullOrWhiteSpace(TempFilesPath))
|
||||
? Path.Combine(DataFolderPath, "temp")
|
||||
: TempFilesPath;
|
||||
|
||||
#endregion server BASICS
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal static string ActualFullPath(string p)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(p))
|
||||
@@ -245,7 +278,9 @@ namespace AyaNova.Util
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(AYANOVA_USE_URLS))
|
||||
{ return null; }
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!AYANOVA_USE_URLS.Contains(";"))
|
||||
{
|
||||
@@ -253,15 +288,7 @@ namespace AyaNova.Util
|
||||
}
|
||||
var s = AYANOVA_USE_URLS.Split(';');
|
||||
return s[0].Replace("*", "localhost");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
} //eoc
|
||||
} //eons
|
||||
|
||||
Reference in New Issue
Block a user