Compare commits

...

10 Commits

Author SHA1 Message Date
cf97ab8aca vm dev machine setup guide 2026-02-07 20:56:43 +00:00
95c2abe449 2026-02-03 04:02:17 +00:00
eec79aeaec Case 4641 AI era starts now 2026-02-02 02:48:54 +00:00
d96942a296 case 4640 2025-11-24 14:19:27 +00:00
15933c0b1c case 4632 2025-04-03 21:05:18 +00:00
ff1ce4d483 prepare for next release 2025-02-18 21:50:30 +00:00
9d7c575dd1 case 4632 2025-02-18 21:08:56 +00:00
406e42df4e 2025-02-14 21:16:13 +00:00
a64dd93589 case 4629 2025-02-10 16:37:43 +00:00
d780a12367 case 4629 2025-02-10 15:59:57 +00:00
22 changed files with 2232 additions and 361 deletions

4
.vscode/launch.json vendored
View File

@@ -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": {

View 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
View 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
View 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
View 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 |

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,4 +1,4 @@
{
"latestversion":"8.2.3",
"latestversion":"8.2.4",
"changelogurl":"https://ayanova.com/docs/changelog/#ayanova-823"
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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).

View File

@@ -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.

View File

@@ -8,7 +8,7 @@ site_name: AyaNova manual
site_dir: "../../../server/AyaNova/wwwroot/docs"
site_url: https://ayanova.com/docs/
strict: true
copyright: Copyright &copy; 2022-2024 Ground Zero Tech-Works Inc. REV-2024-12-02
copyright: Copyright &copy; 2022-2025 Ground Zero Tech-Works Inc. REV-2025-11-24
extra:
generator: false
# Extensions

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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