diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fda129d..ce1252d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -16,8 +16,48 @@ "Bash(del \"C:\\Users\\User\\source\\repos\\BrowserExtensions\\DataFlow-Mobile\\DataFlow.Mobile\\Models\\PageModel.cs\")", "Bash(move:*)", "Bash(sed:*)", - "Bash(emulator:*)" + "Bash(emulator:*)", + "Bash(adb:*)", + "Bash(timeout:*)", + "Read(//c/Users/User/**)", + "Bash(cat:*)", + "Bash(mkdir:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(del \"c:\\Users\\User\\source\\repos\\BrowserExtensions\\css-js-toinject\\index.js\")", + "Bash(del \"c:\\Users\\User\\source\\repos\\BrowserExtensions\\css-js-toinject\\sku-qty-focus.js\")", + "Bash(node -e:*)", + "Bash(del \"c:\\Users\\User\\source\\repos\\BrowserExtensions\\css-js-toinject\\IMPLEMENTATION-SUMMARY.md\")", + "Bash(mv:*)", + "WebSearch", + "WebFetch(domain:cdn.jsdelivr.net)", + "Bash(curl:*)", + "Bash(git -C \"C:\\Users\\User\\source\\repos\\BrowserExtensions\" status --short css-js-toinject/)", + "Bash(git init:*)", + "Bash(gh repo create:*)", + "Bash(echo:*)", + "Bash(del HTMLZebraPrinterService-v1.0.0.zip)", + "Bash(sc query:*)", + "Bash(findstr:*)", + "Bash(del HTMLZebraPrinterService-v.zip)", + "Bash(del diagnose.ps1)", + "Bash(del test-service-startup.ps1)", + "Bash(del check-aspnetcore.ps1)", + "Bash(del fix-service.ps1)", + "Bash(del test-manual-run.ps1)", + "Bash(del fix-http-permissions.ps1)", + "Bash(del switch-to-localservice.ps1)", + "Bash(del reinstall-service-correct-path.ps1)", + "Bash(del install.ps1)", + "Bash(del uninstall.ps1)", + "Bash(del install-standalone.ps1)", + "Bash(del HTMLZebraPrinterService-v1.1.0-HTTPS.zip)", + "Bash(del HTMLZebraPrinterService-v1.1.0-HTTPS.zip 2)", + "Bash(NUL)", + "Bash(test:*)", + "Bash(python:*)", + "Bash(sqlite3:*)" ], "deny": [] } -} \ No newline at end of file +} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..864180c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,359 @@ +# GitHub Copilot Instructions for BrowserExtensions + +## Repository Overview + +Multi-project repository containing browser extensions, mobile apps, and Windows services for warehouse management automation. Active PR: **net9-upgrade** (downgrading from .NET 10 to .NET 9). + +## Project Structure + +### 1. **scheduled-print-service** (ASP.NET Core Windows Service, .NET 8) 🔥 +- **Location**: `scheduled-print-service/ScheduledPrintService/` +- **Purpose**: Headless browser automation for printing SPA pages (PuppeteerSharp) + API polling with configurable action chains +- **Architecture**: Hosted services pattern with dependency injection +- **Key Services**: + - `ApiPollSchedulerService`: Polls API, extracts IDs, executes sub-action chains + - `SubActionExecutor`: Executes configurable actions (CallApi, GetUrlAndPrint, NavigateOnly, PrintCapturedPage, CreatePicklistBatch, Delay) + - `TokenRenewalService`: Singleton for automatic auth token renewal on 401 + - `PdfBrowserManager`: Manages PuppeteerSharp browser lifecycle with auth injection +- **Critical Pattern**: **Two-stage print workflow** - `NavigateOnly` navigates & waits for SPA data load, then `PrintCapturedPage` prints the kept-alive page (avoids re-rendering losing XHR state) +- **Configuration**: `appsettings.json` with complex `Api.SubActions[]` array supporting action chaining via `ChainedArrayJsonPath`/`UseChainedInput` +- **Data Paths**: Uses `DataPaths.DataRoot` (defaults to `%ProgramData%\ScheduledPrintService`, overrideable via `SCHEDULED_PRINT_DATA_ROOT` env var) + +**Critical Concepts**: +```csharp +// Sub-action chaining: CreatePicklistBatch extracts IDs, then GetUrlAndPrint uses {pickListId} tokens +{ + "Type": "CreatePicklistBatch", + "ChainedArrayJsonPath": "data", // Extract array from response + "ChainedItemFieldPath": "pickListId" // Field to pass to chained actions +}, +{ + "Type": "GetUrlAndPrint", + "UseChainedInput": true, // Receives context from previous action + "Endpoint": "https://example.com/#page?id={pickListId}", // Token replaced + "WaitForNetworkIdleMs": 3000, + "MakeHiddenVisible": true // Converts hidden inputs to visible before PDF +} +``` + +**Dev Workflow**: +```powershell +# Set local data root (keeps artifacts in repo during dev) +$env:SCHEDULED_PRINT_DATA_ROOT = (Resolve-Path ".\scheduled-print-service").Path + +# Run in console mode +dotnet run --project .\scheduled-print-service\ScheduledPrintService\ScheduledPrintService.csproj + +# Install as Windows service (Administrator) +cd scheduled-print-service +.\install-service.ps1 -ExePath "C:\path\to\ScheduledPrintService.exe" + +# View logs +Get-Content "$env:ProgramData\ScheduledPrintService\logs\scheduled-print-service-$(Get-Date -Format yyyyMMdd).log" -Tail 50 -Wait +``` + +**Key Documentation**: `CHAINING-GUIDE.md`, `API-POLLING-GUIDE.md`, `TOKEN-RENEWAL.md`, `MANUAL-MODE-USAGE.md` + +### 2. **DataFlow-Mobile** (.NET MAUI, .NET 9) +- **Location**: `DataFlow-Mobile/` +- **Architecture**: MVVM with Repository pattern, Entity Framework Core + SQLite +- **DI Registration**: All services in `MauiProgram.cs` - follow existing patterns (interfaces in `Services/Interfaces/`) +- **HTTP Resilience**: Uses `AddStandardResilienceHandler()` (no Polly, no manual timeouts) +- **Key Pattern**: Generic repository with UnitOfWork (`IGenericRepository`, `IUnitOfWork`) +- **Testing**: xUnit tests in `DataFlow.Mobile.Tests/` + +**Critical Build Commands**: +```powershell +# Build for Android (common target) +dotnet build DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj -t:Run -f net9.0-android + +# Restore +dotnet restore DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj +``` + +### 3. **html-printer-service** (ASP.NET Core Windows Service, .NET 8) +- **Location**: `html-printer-service/HTMLZebraPrinterService/` +- **Purpose**: HTML → ZPL conversion + direct Zebra printer TCP/IP printing +- **Architecture**: Dependency injection, hosted service pattern (`PrintQueueService`) +- **Configuration**: `appsettings.json` (Printer IP, Label dimensions, Retry logic, Sentry DSN) +- **Key Files**: `Program.cs`, `Services/` (HtmlParser, ZplGenerator, PrinterCommunicator) + +**Service Management**: +```powershell +# Install (as Administrator) +cd html-printer-service +.\install.ps1 + +# Logs location +Get-Content "HTMLZebraPrinterService\bin\Debug\net8.0\logs\service-*.txt" -Tail 50 +``` + +### 4. **word-template-extension** (Browser Extension + Python Native Host) +- **Extension**: Manifest v3 (Chrome/Edge), extracts web data into Word templates +- **Native Host**: Python 3.7+ using `python-docx`, communicates via native messaging +- **Build Script**: `build-packages.ps1` - creates store packages, optionally builds PyInstaller executable +- **Template Syntax**: `{{PLACEHOLDER_NAME}}` in .docx files + +**Development Workflow**: +```powershell +# Build distribution packages +cd word-template-extension +powershell -ExecutionPolicy Bypass -File build-packages.ps1 -Version "1.0.1" + +# Install native host (Windows, as Administrator) +cd native-host +.\install.bat +``` + +### 5. **scan-overlay-extension** (Browser Extension) +- **Purpose**: Scanning workflows with overlays, audio feedback, configurable selectors +- **Key Files**: `background.js`, `content.js`, `overlay.js`, `settings.js` +- **Configuration**: XPath/CSS selectors via settings page +- **Audio**: MP3 files generated using `generate-audio.html` + +### 6. **css-override-extension** (Browser Extension) +- **Purpose**: Inject custom CSS by URL pattern (Override/Replace modes) +- **Pattern**: Manifest v3, simple rule management UI + +### 7. **css-js-toinject/** (Injectable Scripts) +- **Purpose**: Site-specific enhancements for internal 3PL website (hash-based SPA routing) +- **Router**: `router.js` (main) and `malchus-router.js` (domain-specific) detect URL hash fragments, load feature-specific modules +- **Pattern**: Each route registers actions that inject CSS/JS when URL hash matches +- **Key Features**: + - `auto-print-buttons.js`: Sequential printing (Carton Label → Packing Slip) + - `status-dropdown.js`: Auto-fill status scan fields + - `table-item-linker.js`: SKU/Qty clickable items + - `placard-text-enhancer.css`: Doubles text size on shipping placards + - `item-line-id.js`: Adds Item Line ID column to tables via MutationObserver + +## Development Conventions + +### PowerShell Scripts +- **Execution Policy**: Always use `-ExecutionPolicy Bypass` for scripts +- **Admin Rights**: Install scripts (`install.bat`, `install.ps1`) require Administrator elevation +- **Build Scripts**: Located at project roots (e.g., `build-packages.ps1`) +- **Environment Variables**: Use `$env:VARIABLE_NAME` to persist settings across commands (e.g., `$env:SCHEDULED_PRINT_DATA_ROOT`) + +### .NET Projects +- **Current Migration**: Downgrading DataFlow-Mobile from .NET 10 → .NET 9 (see `Apply-DataFlowNet9Upgrade.ps1`) +- **HTTP Clients**: Use `AddStandardResilienceHandler()` - DO NOT use Polly directly or set `client.Timeout` +- **Service Registration**: Group by type in `MauiProgram.cs` (Repository → Data → API → ViewModels → Pages) +- **File-Scoped Namespaces**: Use `namespace DataFlow.Mobile.Services;` (no braces) +- **Singletons for State**: `TokenRenewalService` is singleton - use for shared auth state across services +- **Hosted Services**: Use `BackgroundService` base class for long-running services (ApiPollSchedulerService, PrintSchedulerService) + +### SPA Navigation Pattern (scheduled-print-service) +**Critical for hash-based SPAs**: Pages load via JavaScript after hash changes, requiring multi-stage waits: +1. Navigate to root URL to establish domain context +2. Inject auth token into localStorage/sessionStorage IMMEDIATELY after domain load +3. Set hash via `window.location.href` (triggers SPA routing) +4. Wait 1500ms for hash detection + initial render +5. Wait `WaitForNetworkIdleMs` for XHR data loading +6. Optionally wait for `WaitForSelector` and `AdditionalWaitSelectors` +7. Poll `DataReadyRowSelector` count until `MinimumDataRows` met (max `DataLoadRetryMs`) +8. Optional `PostSelectorStableMs` stabilization delay + +**Do NOT pass CancellationToken to critical waits** - prevents interruption during route detection. + +### Browser Extensions +- **Manifest Version**: All extensions use Manifest v3 +- **Content Scripts**: Injected via `manifest.json` or programmatic injection +- **Native Messaging**: Word extension uses JSON protocol with Python host + +### Testing +- **MAUI Tests**: xUnit in `DataFlow.Mobile.Tests/` +- **Manual Testing**: Browser extensions load unpacked during development +- **Service Tests**: PowerShell diagnostic scripts (e.g., `diagnose.ps1`, `test-https.ps1`) +- **Service Console Mode**: Run scheduled-print-service via `dotnet run` (not as Windows service) for debugging + +## Critical Workflows + +### Run Scheduled Print Service (Development) +```powershell +# Set local data root to keep artifacts in repo +$env:SCHEDULED_PRINT_DATA_ROOT = (Resolve-Path ".\scheduled-print-service").Path + +# Run in console mode (not as service) +dotnet run --project .\scheduled-print-service\ScheduledPrintService\ScheduledPrintService.csproj + +# Watch logs in real-time +Get-Content "$env:SCHEDULED_PRINT_DATA_ROOT\logs\scheduled-print-service-$(Get-Date -Format yyyyMMdd).log" -Tail 50 -Wait +``` + +### Install Scheduled Print Service (Production) +```powershell +# Build and publish +cd scheduled-print-service +.\publish.ps1 -Configuration Release -Runtime win-x64 -SelfContained + +# Install as Windows service (as Administrator) +.\install-service.ps1 -ExePath "C:\path\to\publish\ScheduledPrintService.exe" + +# Manage service +Start-Service ScheduledPrintService +Stop-Service ScheduledPrintService +Restart-Service ScheduledPrintService + +# View production logs +Get-Content "$env:ProgramData\ScheduledPrintService\logs\scheduled-print-service-$(Get-Date -Format yyyyMMdd).log" -Tail 100 +``` + +### Manual Mode (scheduled-print-service) +Run once and exit (for debugging sub-actions): +```powershell +# Edit appsettings.json: "ManualMode": true +dotnet run --project .\scheduled-print-service\ScheduledPrintService\ScheduledPrintService.csproj + +# Or with command-line args (future) +dotnet run --project .\scheduled-print-service\ScheduledPrintService\ScheduledPrintService.csproj -- --manual --api-number 1 +``` + +### Build DataFlow-Mobile for Android +```powershell +dotnet build DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj -f net9.0-android +``` + +### Install HTML Printer Service +```powershell +# As Administrator +cd html-printer-service +.\install.ps1 +Restart-Service HTMLZebraPrinterService +``` + +### Package Word Template Extension +```powershell +cd word-template-extension +powershell -ExecutionPolicy Bypass -File build-packages.ps1 -Version "1.0.2" -IncludeSource +# Output: dist/word-template-extension-chrome-v1.0.2.zip +``` + +### Inject CSS-JS Scripts (Manual) +Load `css-js-toinject/router.js` or `malchus-router.js` via browser console or extension to enable feature routing. + +## Common Patterns + +### Adding a Service (DataFlow-Mobile) +1. Create interface in `Services/Interfaces/IMyService.cs` +2. Implement in `Services/MyService.cs` +3. Register in `MauiProgram.RegisterServices()`: + ```csharp + services.AddScoped(); + ``` + +### Adding a Sub-Action (scheduled-print-service) +Edit `appsettings.json` in `Api.SubActions` array: +```json +{ + "Type": "GetUrlAndPrint", + "Name": "Print Order Details", + "Enabled": true, + "Endpoint": "https://example.com/#order/{id}", + "UseChainedInput": false, + "WaitForNetworkIdleMs": 3000, + "MakeHiddenVisible": true, + "ContinueOnError": true +} +``` + +**Available SubAction Types**: +- `CallApi`: Make HTTP request +- `GetHtmlAndPrint`: Fetch HTML from API and print +- `GetUrlAndPrint`: Navigate Puppeteer browser to URL and print +- `NavigateOnly`: Navigate and keep page alive (two-stage print) +- `PrintCapturedPage`: Print previously captured page +- `CreatePicklistBatch`: Batch create picklists and trigger chained actions +- `Delay`: Wait specified milliseconds + +### Adding a Route (css-js-toinject) +Edit `router.js` or `malchus-router.js`, add to `ROUTES` array: +```javascript +{ + name: 'My Feature Route', + pattern: /^#path\/to\/page$/i, + action: () => { + console.log('Matched route'); + // Inject scripts/styles dynamically + // Use MutationObserver for DOM watching + } +} +``` + +### Configuring API Polling (scheduled-print-service) +Edit `appsettings.json`: +```json +{ + "Api": { + "Enabled": true, + "BaseUrl": "https://example.com", + "BearerToken": "your-token", + "WarehouseId": 1, + "UserEmail": "user@example.com", + "Password": "password", + "Cookies": {}, + "PrimaryEndpoint": "/api/orders/list", + "PrimaryHttpMethod": "POST", + "IdJsonPath": "[0]", + "ProcessedIdsPath": "processed-orders.txt", + "SubActions": [] + }, + "Scheduler": { + "Enabled": true, + "IntervalSeconds": 300 + } +} +``` + +### Configuring Printer Service +Edit `HTMLZebraPrinterService/appsettings.json`: +```json +{ + "Printer": { "IpAddress": "192.168.1.244", "Port": 9100 }, + "Label": { "WidthInches": 4.0, "HeightInches": 6.0, "DPI": 300 } +} +``` + +## Troubleshooting + +### .NET MAUI Build Failures +- Verify target framework: `net9.0-android` (not `net10.0-android`) +- Check Android SDK licenses: `AcceptAndroidSDKLicenses=true` in `.csproj` +- Clean: `dotnet clean && dotnet build` + +### Scheduled Print Service Issues +- **Service won't start**: Check Event Viewer → Application logs for errors +- **No PDFs generated**: Verify `Printer.Mode` is `File` and `OutputDirectory` exists +- **SPA data missing**: Increase `WaitForNetworkIdleMs` (try 5000-10000ms) +- **Token expired (401)**: Service auto-renews if `UserEmail`/`Password` configured +- **Chained actions not running**: Verify `UseChainedInput: true` and action order + +**Check logs**: +```powershell +# Development (local data root) +Get-Content "scheduled-print-service\logs\scheduled-print-service-$(Get-Date -Format yyyyMMdd).log" -Tail 100 + +# Production +Get-Content "$env:ProgramData\ScheduledPrintService\logs\scheduled-print-service-$(Get-Date -Format yyyyMMdd).log" -Tail 100 +``` + +### Word Extension Native Host Issues +- Run `native-host/diagnose.ps1` to check Python, packages, registry +- Verify manifest path in registry: `HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.wordtemplate.nativehost` + +### HTML Printer Service Not Starting +- Check Event Viewer → Windows Logs → Application +- Verify .NET 8 runtime: `dotnet --version` +- Test printer connectivity: `Test-NetConnection -ComputerName 192.168.1.244 -Port 9100` + +## File Locations + +- **Solution File**: `BrowserExtensions.sln` (DataFlow-Mobile only) +- **Root Instructions**: `CLAUDE.md` (detailed project documentation) +- **Task Files**: VSCode tasks in `.vscode/tasks.json` (e.g., "Zip Extension Files") + +## Notes for AI Agents + +- **Minimal Changes**: Preserve existing patterns unless explicitly refactoring (per `CLAUDE.md`) +- **PowerShell Default**: Use PowerShell syntax for commands (Windows environment) +- **No Generic Advice**: Follow project-specific conventions above +- **Active PR Context**: Currently migrating .NET 10 → .NET 9 (see `Apply-DataFlowNet9Upgrade.ps1`) diff --git a/.github/skills/straighten-quotes/SKILL.md b/.github/skills/straighten-quotes/SKILL.md new file mode 100644 index 0000000..88bac6d --- /dev/null +++ b/.github/skills/straighten-quotes/SKILL.md @@ -0,0 +1,13 @@ +--- +name: straighten-quotes +description: Cleans up HTML text by fixing quotes, apostrophes, em dashes, and double spaces for CMS compatibility. +--- + +# Instructions + +When I ask you to "clean this article" or "fix formatting," perform all these steps: + +1. Replace all curly double quotes with straight double quotes. +2. Replace all curly single quotes and apostrophes with straight single quotes. +3. Find every long dash and replace it with two short dashes (--). +4. Remove any double spaces between words and replace them with a single space. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 08fce9b..5aa5dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,158 @@ -scan-overlay-extension.zip +# ========================= +# Build Artifacts & Output +# ========================= + +# .NET Build outputs +**/bin/ +**/obj/ +**/publish/ +**/.vs/ + +# Extension packages +*.zip + +# Python build artifacts +**/build/ +**/dist/ +**/__pycache__/ +*.pyc +*.pyo +*.pyd +*.egg-info/ +*.egg + +# ========================= +# Logs & Temporary Files +# ========================= + +# Service logs +**/logs/ +*.log + +# Temporary files +*.tmp +*.temp +*.stackdump +error.txt +output.txt +printed-urls.txt + +# ========================= +# Cache & Downloaded Files +# ========================= + +# Chromium cache for PDF printing +**/chromium-cache/ + +# Package manager caches +**/node_modules/ +**/packages/ + +# ========================= +# IDE & Editor Files +# ========================= + +# Visual Studio +.vs/ +*.suo +*.user +*.userosscache +*.sln.docstates + +# VS Code +.vscode/* +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# JetBrains IDEs +.idea/ +*.iml + +# ========================= +# OS Generated Files +# ========================= + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Linux +*~ + +# ========================= +# Project-Specific +# ========================= + +# Native host executables (should be built, not committed) +word-template-extension/native-host/dist/*.exe + +# Local configuration overrides +**/appsettings.Development.json +**/appsettings.Local.json + +# Test outputs +**/TestResults/ + +# NuGet packages (should be restored, not committed) +*.nupkg +*.snupkg + +# User-specific files +*.rsuser +*.suo +*.user +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# ========================= +# Security & Sensitive Data +# ========================= + +# Secrets and certificates +*.pfx +*.p12 +*.key +*.pem +secrets.json +*.env.local + +# Database files (environment-specific) +*.db +*.db-shm +*.db-wal + +# ========================= +# Claude AI Assistant +# ========================= + +# Claude configuration (keep structure, ignore transient data) +.claude/* +!.claude/.gitkeep +.lollms/ +scheduled-print-service/out/20251111_115318525_demo-print.pdf diff --git a/.lollms/discussions/1758065076507.json b/.lollms/discussions/1758065076507.json new file mode 100644 index 0000000..e6d8a5c --- /dev/null +++ b/.lollms/discussions/1758065076507.json @@ -0,0 +1,23 @@ +{ + "id": "1758065076507", + "title": "New Discussion", + "messages": [ + { + "role": "system", + "content": "🤖 **Agent Mode Activated.** Using model: `ollama/mistral`. Please provide your goal.", + "id": "1758065083599jn27cln24ns" + }, + { + "role": "system", + "content": "🎯 **Objective:** tell me a programming joke\n\n🧠 Thinking... Generating a plan.", + "id": "1758065511130lxgmlbmve4" + }, + { + "role": "system", + "content": "❌ **Critical Error during planning:** request to http://localhost:9642/v1/chat/completions failed, reason: ", + "id": "1758065512295h0xmhpr3v9a" + } + ], + "timestamp": 1758065512295, + "groupId": null +} \ No newline at end of file diff --git a/.lollms/discussions/1758126214796.json b/.lollms/discussions/1758126214796.json new file mode 100644 index 0000000..196d2bf --- /dev/null +++ b/.lollms/discussions/1758126214796.json @@ -0,0 +1,7 @@ +{ + "id": "1758126214796", + "title": "New Discussion", + "messages": [], + "timestamp": 1758126214796, + "groupId": null +} \ No newline at end of file diff --git a/.lollms/skills.json b/.lollms/skills.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/.lollms/skills.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.vs/BrowserExtensions/DesignTimeBuild/.dtbcache.v2 b/.vs/BrowserExtensions/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000..3c89252 Binary files /dev/null and b/.vs/BrowserExtensions/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/BrowserExtensions/FileContentIndex/1a83ba49-dcfa-46ad-8c2b-09ea19e366ea.vsidx b/.vs/BrowserExtensions/FileContentIndex/1a83ba49-dcfa-46ad-8c2b-09ea19e366ea.vsidx new file mode 100644 index 0000000..942f082 Binary files /dev/null and b/.vs/BrowserExtensions/FileContentIndex/1a83ba49-dcfa-46ad-8c2b-09ea19e366ea.vsidx differ diff --git a/.vs/BrowserExtensions/FileContentIndex/8cfc232f-6e5b-4dbb-af45-a56caa6bcff6.vsidx b/.vs/BrowserExtensions/FileContentIndex/8cfc232f-6e5b-4dbb-af45-a56caa6bcff6.vsidx new file mode 100644 index 0000000..4798ff1 Binary files /dev/null and b/.vs/BrowserExtensions/FileContentIndex/8cfc232f-6e5b-4dbb-af45-a56caa6bcff6.vsidx differ diff --git a/.vs/BrowserExtensions/v18/.futdcache.v2 b/.vs/BrowserExtensions/v18/.futdcache.v2 new file mode 100644 index 0000000..c39020a Binary files /dev/null and b/.vs/BrowserExtensions/v18/.futdcache.v2 differ diff --git a/.vs/BrowserExtensions/v18/.suo b/.vs/BrowserExtensions/v18/.suo new file mode 100644 index 0000000..46af012 Binary files /dev/null and b/.vs/BrowserExtensions/v18/.suo differ diff --git a/.vs/BrowserExtensions/v18/DocumentLayout.backup.json b/.vs/BrowserExtensions/v18/DocumentLayout.backup.json new file mode 100644 index 0000000..2403634 --- /dev/null +++ b/.vs/BrowserExtensions/v18/DocumentLayout.backup.json @@ -0,0 +1,47 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\User\\source\\repos\\BrowserExtensions\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{e506b91c-c606-466a-90a9-123d1d1e12b3}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:129:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/BrowserExtensions/v18/DocumentLayout.json b/.vs/BrowserExtensions/v18/DocumentLayout.json new file mode 100644 index 0000000..2403634 --- /dev/null +++ b/.vs/BrowserExtensions/v18/DocumentLayout.json @@ -0,0 +1,47 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\User\\source\\repos\\BrowserExtensions\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{e506b91c-c606-466a-90a9-123d1d1e12b3}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:129:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ea967fc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,40 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Scheduled Print Service", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-scheduled-print-service", + "program": "${workspaceFolder}/scheduled-print-service/ScheduledPrintService/bin/Debug/net8.0-windows10.0.19041.0/ScheduledPrintService.dll", + "args": [], + "cwd": "${workspaceFolder}/scheduled-print-service/ScheduledPrintService", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "Debug Scheduled Print Service (Manual Mode)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-scheduled-print-service", + "program": "${workspaceFolder}/scheduled-print-service/ScheduledPrintService/bin/Debug/net8.0-windows10.0.19041.0/ScheduledPrintService.dll", + "args": [], + "cwd": "${workspaceFolder}/scheduled-print-service/ScheduledPrintService", + "console": "internalConsole", + "stopAtEntry": false, + "justMyCode": false + }, + { + "name": "Debug Scheduled Print Service (API #7)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-scheduled-print-service", + "program": "${workspaceFolder}/scheduled-print-service/ScheduledPrintService/bin/Debug/net8.0-windows10.0.19041.0/ScheduledPrintService.dll", + "args": ["--manual", "--api-number", "7"], + "cwd": "${workspaceFolder}/scheduled-print-service/ScheduledPrintService", + "console": "internalConsole", + "stopAtEntry": false, + "justMyCode": false + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8111ebf..9ce5c8d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -5,6 +5,19 @@ "label": "Zip Extension Files", "type": "shell", "command": "Compress-Archive -Path c:\\Users\\User\\source\\repos\\BrowserExtensions\\scan-overlay-extension\\* -DestinationPath c:\\Users\\User\\source\\repos\\BrowserExtensions\\scan-overlay-extension.zip -Force" + }, + { + "label": "build-scheduled-print-service", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/scheduled-print-service/ScheduledPrintService/ScheduledPrintService.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile", + "group": "build" } ] } \ No newline at end of file diff --git a/Apply-DataFlowNet9Upgrade.ps1 b/Apply-DataFlowNet9Upgrade.ps1 new file mode 100644 index 0000000..57e3ed8 --- /dev/null +++ b/Apply-DataFlowNet9Upgrade.ps1 @@ -0,0 +1,296 @@ + $ErrorActionPreference = 'Stop' + + $root = 'C:\Users\User\source\repos\BrowserExtensions' + $projDir = Join-Path $root 'DataFlow-Mobile\DataFlow.Mobile' + $csproj = Join-Path $projDir 'DataFlow.Mobile.csproj' + $mauiProgram = Join-Path $projDir 'MauiProgram.cs' + $apiService = Join-Path $projDir 'Services\ApiService.cs' + $authService = Join-Path $projDir 'Services\AuthenticationService.cs' + $iapiService = Join-Path $projDir 'Services\Interfaces\IApiService.cs' + $ipageService = Join-Path $projDir 'Services\Interfaces\IPageService.cs' + $pageService = Join-Path $projDir 'Services\PageService.cs' + + function Replace-InFile([string]$path, [string]$pattern, [string]$replacement) { + if (-not (Test-Path $path)) { throw "File not found: $path" } + $c = Get-Content $path -Raw + $n = [regex]::Replace($c, $pattern, $replacement, 'Singleline') + if ($n -ne $c) { + Set-Content -Path $path -Value $n -NoNewline + Write-Host "Updated $path" + } else { + Write-Host "No change needed for $path" + } + } + + # 1) csproj: switch to net9, remove Polly, set logging debug to 9.0.9 + + Replace-InFile $csproj 'net10.0-android' 'net9.0-android' + Replace-InFile $csproj 'net10.0-ios' 'net9.0-ios' + Replace-InFile $csproj 'net10.0-maccatalyst' 'net9.0-maccatalyst' + Replace-InFile $csproj 'net10.0-windows10.0.19041.0' 'net9.0-windows10.0.19041.0' + Replace-InFile $csproj 'Microsoft.Extensions.Logging.Debug" Version="[^"]+' 'Microsoft.Extensions.Logging.Debug" Version="9.0.9' + Replace-InFile $csproj '(?ms)\r?\n\s*]+/>\r?\n' '' + Replace-InFile $csproj '(?ms)\r?\n\s*]+/>\r?\n' '' + + # 2) MauiProgram.cs: remove Polly usings; remove client.Timeout lines + + Replace-InFile $mauiProgram '(?m)^\susing Polly(.|;).\r?\n' '' + Replace-InFile $mauiProgram '(?m)^\sclient.Timeout\s=\sTimeSpan.FromSeconds(\d+);\s\r?$' '' + + # Ensure DataFlowApi still adds UA header (no change needed normally) + + # 3) IApiService.cs: rewrite with CancellationToken and correct namespace + + @" + using DataFlow.Mobile.Models; + + namespace DataFlow.Mobile.Services.Interfaces; + + public interface IApiService + { + // Page-based API calls with automatic authentication + Task> GetAsync(int pageId, CancellationToken cancellationToken = default); + Task> GetAsync(string url, Dictionary? headers = null, int? pageId = null, + CancellationToken cancellationToken = default); + Task> PostAsync(string url, object? data = null, Dictionary? headers = null, int? pageId = + null, CancellationToken cancellationToken = default); + Task> PutAsync(string url, object? data = null, Dictionary? headers = null, int? pageId = + null, CancellationToken cancellationToken = default); + Task> DeleteAsync(string url, Dictionary? headers = null, int? pageId = null, + CancellationToken cancellationToken = default); + + // Raw API calls without authentication + Task> GetRawAsync(string url, Dictionary? headers = null, CancellationToken + cancellationToken = default); + Task> PostRawAsync(string url, object? data = null, Dictionary? headers = null, + CancellationToken cancellationToken = default); + + // Utility methods + Task TestConnectionAsync(string url, Dictionary? headers = null, CancellationToken cancellationToken = + default); + Task TestPageConnectionAsync(int pageId, CancellationToken cancellationToken = default); + Task> ExecutePageDataRequestAsync(int pageId, CancellationToken cancellationToken = default); + + } + "@ | Set-Content -Path $iapiService -NoNewline + + # 4) IPageService.cs: replace incorrect interface + + @" + using DataFlow.Mobile.Models; + + namespace DataFlow.Mobile.Services.Interfaces; + + public interface IPageService + { + Task> GetAllPagesAsync(CancellationToken cancellationToken = default); + Task GetPageByIdAsync(int id, CancellationToken cancellationToken = default); + Task CreatePageAsync(Page page, CancellationToken cancellationToken = default); + Task UpdatePageAsync(Page page, CancellationToken cancellationToken = default); + Task DeletePageAsync(int id, CancellationToken cancellationToken = default); + Task> FetchPageDataAsync(int pageId, CancellationToken cancellationToken = default); + } + "@ | Set-Content -Path $ipageService -NoNewline + + # 5) PageService.cs: correct implementation using Pages DbSet + + @" + using Microsoft.EntityFrameworkCore; + using DataFlow.Mobile.Models; + using Microsoft.Extensions.Logging; + using DataFlow.Mobile.Services.Interfaces; + + namespace DataFlow.Mobile.Services; + + public class PageService : IPageService + { + private readonly DataFlowDbContext _context; + private readonly ILogger _logger; + + public PageService(DataFlowDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task> GetAllPagesAsync(CancellationToken cancellationToken = default) + { + try + { + return await _context.Pages + .AsNoTracking() + .Include(p => p.Template) + .Include(p => p.Actions) + .Where(p => p.IsActive) + .OrderBy(p => p.Name) + .ToListAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting all pages"); + return Enumerable.Empty(); + } + } + + public async Task GetPageByIdAsync(int id, CancellationToken cancellationToken = default) + { + try + { + return await _context.Pages + .Include(p => p.Template) + .Include(p => p.Actions) + .FirstOrDefaultAsync(p => p.Id == id && p.IsActive, cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting page by ID: {PageId}", id); + return null; + } + } + + public async Task CreatePageAsync(Page page, CancellationToken cancellationToken = default) + { + try + { + page.CreatedAt = DateTime.UtcNow; + page.UpdatedAt = DateTime.UtcNow; + _context.Pages.Add(page); + await _context.SaveChangesAsync(cancellationToken); + _logger.LogInformation("Created new page: {PageName} (ID: {PageId})", page.Name, page.Id); + return page; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating page: {PageName}", page.Name); + throw; + } + } + + public async Task UpdatePageAsync(Page page, CancellationToken cancellationToken = default) + { + try + { + page.UpdatedAt = DateTime.UtcNow; + _context.Entry(page).State = EntityState.Modified; + await _context.SaveChangesAsync(cancellationToken); + _logger.LogInformation("Updated page: {PageName} (ID: {PageId})", page.Name, page.Id); + return page; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating page: {PageId}", page.Id); + throw; + } + } + + public async Task DeletePageAsync(int id, CancellationToken cancellationToken = default) + { + try + { + var page = await _context.Pages.FindAsync(new object?[] { id }, cancellationToken); + if (page == null) + return false; + + page.IsActive = false; + page.UpdatedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(cancellationToken); + _logger.LogInformation("Deleted page: {PageId}", id); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting page: {PageId}", id); + return false; + } + } + + public async Task> FetchPageDataAsync(int pageId, CancellationToken cancellationToken = default) + { + try + { + await Task.CompletedTask; // to be implemented with ApiService + _logger.LogInformation("Fetching data for page: {PageId}", pageId); + return Enumerable.Empty(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching data for page: {PageId}", pageId); + return Enumerable.Empty(); + } + } + + } + "@ | Set-Content -Path $pageService -NoNewline + + # 6) AuthenticationService.cs: do not persist tokens in DB in CacheTokenAsync + + if (Test-Path $authService) { + $authText = Get-Content $authService -Raw + + # Remove lines that persist tokens to DB inside CacheTokenAsync + + $authText = [regex]::Replace($authText, '(?m)^\sconfig.(AccessToken|RefreshToken|TokenExpiry)\s=.\r?\n', '') + $authText = [regex]::Replace($authText, '(?m)^\sawait\s+SaveAuthConfigAsync(config);\s*\r?\n', '') + Set-Content -Path $authService -Value $authText -NoNewline + Write-Host "Updated $authService (removed DB token persistence)" + } + + # 7) ApiService.cs: DI scope fix, CancellationToken support, safer logging + + if (Test-Path $apiService) { + + # Add DI using + + Replace-InFile $apiService '(?m)^using Microsoft.Extensions.Logging;\s*$' "using Microsoft.Extensions.Logging;rnusing + Microsoft.Extensions.DependencyInjection;" + + # Add IServiceScopeFactory field + + Replace-InFile $apiService 'private readonly INetworkService _networkService;\s*' '$0private readonly IServiceScopeFactory + _scopeFactory;' + + # Add scopeFactory to constructor signature and assign + + Replace-InFile $apiService 'INetworkService networkService)\s*{' 'INetworkService networkService, IServiceScopeFactory + scopeFactory){' + Replace-InFile $apiService '_networkService = networkService;\s*' "_networkService = networkService;rn _scopeFactory = + scopeFactory;" + + # Fix invalid scope creation + + Replace-InFile $apiService '_httpClientFactory.GetService()' '_scopeFactory.CreateScope()' + + # Propagate CancellationToken in SendAsync if not already present + + Replace-InFile $apiService 'SendAsync(request);' 'SendAsync(request, cancellationToken);' + Replace-InFile $apiService 'SendAsync(request)' 'SendAsync(request, cancellationToken)' + + # Safer body logging (Debug-only, truncated) + + Replace-InFile $apiService '(?ms)_logger.LogDebug("Request {RequestId} body: {Body}", requestId, jsonData);' 'if + (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Request {RequestId} body: {Body}", requestId, jsonData?.Length > 2048 ? + jsonData.Substring(0,2048) + "...": jsonData); }' + Replace-InFile $apiService '(?ms)_logger.LogDebug("Response {RequestId} body: {Body}", requestId, content);' 'if + (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Response {RequestId} body: {Body}", requestId, content?.Length > 2048 ? + content.Substring(0,2048) + "...": content); }' + Write-Host "Updated $apiService (DI scope fix + safer logging)" + } + + # Create branch and commit + + try { + Set-Location $root + git rev-parse --is-inside-work-tree *> $null 2>&1 + if ($LASTEXITCODE -eq 0) { + git checkout -b refactor/net9-upgrade + git add -A + git commit -m "Upgrade to .NET 9; Http resilience; DI fix; tokens in secure storage; IPageService/PageService fixes" + Write-Host "Committed on branch refactor/net9-upgrade" + } else { + Write-Host "Not a git repo; skipping commit." + } + } catch { + Write-Warning "Git commit failed: $($_.Exception.Message)" + } + + Write-Host "Script done. Run: dotnet restore; dotnet build" \ No newline at end of file diff --git a/BrowserExtensions.sln b/BrowserExtensions.sln index 7e5019f..d2b4808 100644 --- a/BrowserExtensions.sln +++ b/BrowserExtensions.sln @@ -1,11 +1,14 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11018.127 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataFlow-Mobile", "DataFlow-Mobile", "{9D0C9662-57AD-2F02-E20D-7E7ACBE487C8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataFlow.Mobile", "DataFlow-Mobile\DataFlow.Mobile\DataFlow.Mobile.csproj", "{E17C427E-81DD-FF51-EA67-66E6C8069723}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataFlow.Mobile.Tests", "DataFlow-Mobile\DataFlow.Mobile.Tests\DataFlow.Mobile.Tests.csproj", "{D6561274-6C7E-CAF7-91E9-7155D7D70450}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -16,6 +19,10 @@ Global {E17C427E-81DD-FF51-EA67-66E6C8069723}.Debug|Any CPU.Build.0 = Debug|Any CPU {E17C427E-81DD-FF51-EA67-66E6C8069723}.Release|Any CPU.ActiveCfg = Release|Any CPU {E17C427E-81DD-FF51-EA67-66E6C8069723}.Release|Any CPU.Build.0 = Release|Any CPU + {D6561274-6C7E-CAF7-91E9-7155D7D70450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6561274-6C7E-CAF7-91E9-7155D7D70450}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6561274-6C7E-CAF7-91E9-7155D7D70450}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6561274-6C7E-CAF7-91E9-7155D7D70450}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CLAUDE.md b/CLAUDE.md index 2dd6560..9e368ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,25 @@ This repository contains two browser extensions: ## Development Commands +### Scheduled Print Service + +**CRITICAL: Always publish to the SAME folder after ANY code changes** + +```bash +cd scheduled-print-service +powershell -ExecutionPolicy Bypass -File scripts/publish.ps1 +``` + +**NEVER create new publish folders. ALWAYS use the publish script.** +- Correct: `scheduled-print-service/publish/` (output from publish.ps1) +- Wrong: `scheduled-print-service/ScheduledPrintService/bin/publish/` +- Wrong: Any other location + +After any code modification: +1. Run the publish script: `powershell -ExecutionPolicy Bypass -File scripts/publish.ps1` +2. Verify files are in `scheduled-print-service/publish/` +3. The script automatically builds and publishes with self-contained deployment + ### Word Template Extension #### Building Packages @@ -109,9 +128,17 @@ BrowserExtensions/ │ ├── templates/ # Sample Word templates │ ├── build-packages.ps1 # Build script for distribution │ └── *.md # Documentation files -└── scan-overlay-extension/ # Complete browser extension - ├── audio/ # Sound effects - └── *.js, *.html, *.css # Extension files +├── scan-overlay-extension/ # Complete browser extension +│ ├── audio/ # Sound effects +│ └── *.js, *.html, *.css # Extension files +└── css-js-toinject/ # Injectable scripts for website enhancement + ├── router.js # Main routing logic + ├── auto-print-buttons.js # Auto print buttons feature + ├── ui-feedback.js # Overlay system + popup controller (replaces overlay-manager.js) + ├── status-dropdown.js # Status dropdown controls + ├── table-item-linker.js # SKU/Qty clickable items + ├── item-line-id.js # Item line ID column + └── *.md # Feature documentation ``` ## Key Technologies @@ -136,6 +163,17 @@ BrowserExtensions/ - Build script creates packages for Chrome Web Store and Edge Add-ons - Extension IDs and native host names are hardcoded in configuration files +## Documentation Standards + +**IMPORTANT: All documentation files must be created in the `docs` folder within each project.** + +- **Scheduled Print Service**: All `.md` documentation files should be placed in `scheduled-print-service/docs/` +- **Word Template Extension**: All `.md` documentation files should be placed in `word-template-extension/docs/` +- **Scan Overlay Extension**: All `.md` documentation files should be placed in `scan-overlay-extension/docs/` +- **CSS-JS-ToInject**: All `.md` documentation files should be placed in `css-js-toinject/docs/` + +Exception: Project root files like `README.md` and `CLAUDE.md` should remain in the root directory. + ## Testing ### Word Template Extension @@ -148,4 +186,67 @@ BrowserExtensions/ - Load unpacked from root folder - Test overlay rendering on various websites - Verify audio feedback functionality -- Check accessibility features with screen readers \ No newline at end of file +- Check accessibility features with screen readers + +### CSS-JS-ToInject Scripts + +Injectable scripts for enhancing the 3PL website: + +#### Auto Print Buttons Feature (`#outbound/packing`) +- Monitors "Create Shipment" button clicks +- Automatically combines "Print Carton Label" + "Packing Slip" buttons +- Prints **Carton Label first** (async API), waits for completion +- Then prints **Packing Slip second** (prevents window overlap) +- Auto-clicks ONLY when "Create Shipment" button was pressed +- See `AUTO-PRINT-BUTTONS-README.md` and `SEQUENTIAL-PRINTING-EXPLANATION.md` for details + +**Testing:** +```javascript +// Disable auto-click +window.autoPrintButtons.setAutoClick(false); + +// Manual trigger +window.autoPrintButtons.printAll(); + +// View configuration +window.autoPrintButtons.config +``` + +#### Status Dropdown (`#outbound/ProcessPersonalizedOrderItems`) +- Adds status dropdown to page tools +- Auto-fills status-scan input field +- Enlarges scan modal for better visibility + +#### Item Line ID Column (`#outbound/ProcessPersonalizedOrderItems`) +- Adds Item Line ID column to order items table +- Auto-populates from API responses +- Watches for table changes via MutationObserver + +#### Packing Slip Column (`#outbound/shipment`) +- Adds "Packing Slip" link column to shipment table +- Links to `#outbound/packingSlipdetail?id=` +- Updates dynamically as table loads + +#### Placard Text Enhancement (`#Outbound/shipmentdetails`) +- Doubles text sizes on shipping placards +- Makes all text bold for better readability +- Works in iframes and main document + +#### Production Status Column (`#SO/orderdetails`) +- Adds "Production Status" column to order items table +- Intercepts API responses to get ItemStatusId for each item +- Looks up status names from session storage +- Updates dynamically as table loads +- See `docs/PRODUCTION-STATUS-COLUMN.md` for full documentation + +**Testing:** +```javascript +// Refresh the column manually +window.productionStatusAPI.refreshColumn(); + +// View stored API data +console.log(window.productionStatusAPI.store.data); + +// Get status list +console.log(window.productionStatusAPI.getStatusList()); +``` \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile.Tests/DataFlow.Mobile.Tests.csproj b/DataFlow-Mobile/DataFlow.Mobile.Tests/DataFlow.Mobile.Tests.csproj new file mode 100644 index 0000000..33d3a62 --- /dev/null +++ b/DataFlow-Mobile/DataFlow.Mobile.Tests/DataFlow.Mobile.Tests.csproj @@ -0,0 +1,41 @@ + + + + net9.0 + enable + enable + Exe + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DataFlow-Mobile/DataFlow.Mobile.Tests/Services/ApiServiceTests.cs b/DataFlow-Mobile/DataFlow.Mobile.Tests/Services/ApiServiceTests.cs new file mode 100644 index 0000000..51e4aa8 --- /dev/null +++ b/DataFlow-Mobile/DataFlow.Mobile.Tests/Services/ApiServiceTests.cs @@ -0,0 +1,22 @@ +using Xunit; + +namespace DataFlow.Mobile.Tests.Services; + +public class ApiServiceTests +{ + [Fact] + public void Placeholder_Test_Should_Pass() + { + Assert.True(true); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("DELETE")] + public void ValidateHttpMethod_Should_Accept_Valid_Methods(string method) + { + Assert.False(string.IsNullOrEmpty(method)); + } +} \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile.Tests/UnitTest1.cs b/DataFlow-Mobile/DataFlow.Mobile.Tests/UnitTest1.cs new file mode 100644 index 0000000..ae7f55f --- /dev/null +++ b/DataFlow-Mobile/DataFlow.Mobile.Tests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace DataFlow.Mobile.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} diff --git a/DataFlow-Mobile/DataFlow.Mobile.Tests/xunit.runner.json b/DataFlow-Mobile/DataFlow.Mobile.Tests/xunit.runner.json new file mode 100644 index 0000000..86c7ea0 --- /dev/null +++ b/DataFlow-Mobile/DataFlow.Mobile.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json" +} diff --git a/DataFlow-Mobile/DataFlow.Mobile/App.xaml.cs b/DataFlow-Mobile/DataFlow.Mobile/App.xaml.cs index b067f30..f7f77f0 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/App.xaml.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/App.xaml.cs @@ -1,4 +1,6 @@ -namespace DataFlow.Mobile; +using DataFlow.Mobile.Services; + +namespace DataFlow.Mobile; public partial class App : Application { @@ -11,4 +13,23 @@ protected override Window CreateWindow(IActivationState? activationState) { return new Window(new AppShell()); } + + protected override async void OnStart() + { + base.OnStart(); + + // Initialize database + try + { + var dbContext = IPlatformApplication.Current?.Services?.GetService(); + if (dbContext != null) + { + await dbContext.Database.EnsureCreatedAsync(); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Database initialization error: {ex.Message}"); + } + } } \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile/AppShell.xaml.cs b/DataFlow-Mobile/DataFlow.Mobile/AppShell.xaml.cs index 936483f..6e4455d 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/AppShell.xaml.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/AppShell.xaml.cs @@ -14,6 +14,6 @@ public AppShell() Routing.RegisterRoute("pagewizard", typeof(PageWizardPage)); Routing.RegisterRoute("apiconfiguration", typeof(ApiConfigurationPage)); Routing.RegisterRoute("advancedtemplatedesigner", typeof(AdvancedTemplateDesignerPage)); - Routing.RegisterRoute("actionconfiguration", typeof(ActionConfigurationPage)); + // Removed ActionConfigurationPage route (feature not implemented) } } diff --git a/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToColorConverter.cs b/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToColorConverter.cs index 0dc34c2..598cfaa 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToColorConverter.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToColorConverter.cs @@ -4,7 +4,7 @@ namespace DataFlow.Mobile.Converters; public class BoolToColorConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool boolValue) return boolValue ? Colors.Green : Colors.Gray; @@ -12,7 +12,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return Colors.Gray; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToStatusConverter.cs b/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToStatusConverter.cs index cdf5e22..f065921 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToStatusConverter.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Converters/BoolToStatusConverter.cs @@ -4,7 +4,7 @@ namespace DataFlow.Mobile.Converters; public class BoolToStatusConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool boolValue) return boolValue ? "Active" : "Inactive"; @@ -12,7 +12,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return "Unknown"; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/DataFlow-Mobile/DataFlow.Mobile/Converters/IntToBoolConverter.cs b/DataFlow-Mobile/DataFlow.Mobile/Converters/IntToBoolConverter.cs index 86f1ae4..25b1fbd 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Converters/IntToBoolConverter.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Converters/IntToBoolConverter.cs @@ -1,10 +1,11 @@ using System.Globalization; +using System.Collections; namespace DataFlow.Mobile.Converters; public class IntToBoolConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is int intValue) return intValue > 0; @@ -15,7 +16,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return false; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj b/DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj index f69d335..a895918 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj +++ b/DataFlow-Mobile/DataFlow.Mobile/DataFlow.Mobile.csproj @@ -1,8 +1,8 @@ - net10.0-android;net10.0-ios;net10.0-maccatalyst - $(TargetFrameworks);net10.0-windows10.0.19041.0 + net9.0-android;net9.0-ios;net9.0-maccatalyst + $(TargetFrameworks);net9.0-windows10.0.19041.0 + true - DataFlow.Mobile + DataFlow Mobile - com.companyname.dataflow.mobile + com.dataflow.mobile - 1.0 + 1.0.0 1 + + apk + true + None 15.0 15.0 21.0 + + + android-arm;android-arm64;android-x64 + 10.0.17763.0 10.0.17763.0 + + + 36 + 36 + + + + true + dataflow-mobile-release.keystore + dataflow-mobile + password123 + password123 @@ -61,11 +83,9 @@ - - - - - + + + diff --git a/DataFlow-Mobile/DataFlow.Mobile/MauiProgram.cs b/DataFlow-Mobile/DataFlow.Mobile/MauiProgram.cs index fc06cf9..b51af84 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/MauiProgram.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/MauiProgram.cs @@ -2,9 +2,6 @@ using CommunityToolkit.Maui; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Http.Resilience; -using Polly; -using Polly.CircuitBreaker; -using Polly.Extensions.Http; using DataFlow.Mobile.Services; using DataFlow.Mobile.Services.Interfaces; using DataFlow.Mobile.ViewModels; @@ -50,45 +47,17 @@ private static void ConfigureHttpClients(IServiceCollection services) // Configure default HttpClient with resilience policies services.AddHttpClient("DataFlowApi", client => { - client.Timeout = TimeSpan.FromSeconds(30); + // removed: set timeout via resilience handler client.DefaultRequestHeaders.Add("User-Agent", "DataFlow-Mobile/1.0"); }) - .AddResilienceHandler("default", builder => - { - builder - .AddRetry(new HttpRetryStrategyOptions - { - MaxRetryAttempts = 3, - Delay = TimeSpan.FromSeconds(1), - BackoffType = DelayBackoffType.Exponential, - UseJitter = true - }) - .AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions - { - FailureRatio = 0.5, - SamplingDuration = TimeSpan.FromSeconds(30), - MinimumThroughput = 3, - BreakDuration = TimeSpan.FromSeconds(30) - }) - .AddTimeout(TimeSpan.FromSeconds(10)); - }); + .AddStandardResilienceHandler(); // Configure authentication-specific HttpClient services.AddHttpClient("AuthApi", client => { - client.Timeout = TimeSpan.FromSeconds(15); + // removed: set timeout via resilience handler }) - .AddResilienceHandler("auth", builder => - { - builder - .AddRetry(new HttpRetryStrategyOptions - { - MaxRetryAttempts = 2, - Delay = TimeSpan.FromMilliseconds(500), - BackoffType = DelayBackoffType.Linear - }) - .AddTimeout(TimeSpan.FromSeconds(5)); - }); + .AddStandardResilienceHandler(); } private static void RegisterServices(IServiceCollection services) @@ -140,7 +109,7 @@ private static void RegisterServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + // Removed registration for ActionConfiguration feature (no implementation) services.AddTransient(); // Pages @@ -152,7 +121,7 @@ private static void RegisterServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + // Removed registration for ActionConfiguration feature (no implementation) services.AddTransient(); } } diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/ActionModel.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/ActionModel.cs index e023c11..fe5016c 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/ActionModel.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/ActionModel.cs @@ -13,7 +13,7 @@ public class ActionModel public int PageId { get; set; } [ForeignKey(nameof(PageId))] - public Page Page { get; set; } = null!; + public DataPage Page { get; set; } = null!; [Required] [MaxLength(100)] diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/ApiResponse.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/ApiResponse.cs index 24a1c99..9bcc3ab 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/ApiResponse.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/ApiResponse.cs @@ -8,6 +8,9 @@ public class ApiResponse public T? Data { get; set; } public string? ErrorMessage { get; set; } public HttpStatusCode StatusCode { get; set; } + + // Alias for backward compatibility + public string? Message => ErrorMessage; public Dictionary? Headers { get; set; } public TimeSpan ResponseTime { get; set; } diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/AudioConfigModel.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/AudioConfigModel.cs index 3b63ced..853d848 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/AudioConfigModel.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/AudioConfigModel.cs @@ -48,5 +48,12 @@ public class AudioConfigModel [Required] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + // Additional properties for compatibility + public string FilePath + { + get => AudioFileName; + set => AudioFileName = value; + } + public ICollection Actions { get; set; } = []; } \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationConfig.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationConfig.cs index a89339f..3cfbbdc 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationConfig.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationConfig.cs @@ -12,6 +12,10 @@ public class AuthenticationConfig [Required] public int PageId { get; set; } + [Required] + [MaxLength(100)] + public string Name { get; set; } = string.Empty; + [Required] [MaxLength(50)] public string AuthenticationType { get; set; } = string.Empty; // Bearer, ApiKey, Basic, OAuth @@ -48,6 +52,11 @@ public class AuthenticationConfig public string? AdditionalHeaders { get; set; } + public bool IsActive { get; set; } = true; + + [MaxLength(500)] + public string? TokenValue { get; set; } + [Required] public DateTime CreatedAt { get; set; } = DateTime.UtcNow; @@ -55,5 +64,5 @@ public class AuthenticationConfig public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; // Navigation property - public Page Page { get; set; } = null!; + public DataPage Page { get; set; } = null!; } \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationModel.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationModel.cs index 7f2f0d3..35e770f 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationModel.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/AuthenticationModel.cs @@ -63,5 +63,5 @@ public class AuthenticationModel [Required] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - public ICollection Pages { get; set; } = []; + public ICollection Pages { get; set; } = []; } \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/ImportResult.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/ImportResult.cs index 074897f..b3e1544 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/ImportResult.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/ImportResult.cs @@ -5,6 +5,7 @@ public class ImportResult public bool IsValid { get; set; } public string? ErrorMessage { get; set; } public List Warnings { get; set; } = []; + public List Conflicts { get; set; } = []; public ImportSummary? Summary { get; set; } } @@ -14,5 +15,11 @@ public class ImportSummary public int TemplatesCount { get; set; } public int ActionsCount { get; set; } public int SettingsCount { get; set; } + public int ColorSchemesCount { get; set; } + public int LayoutTemplatesCount { get; set; } + public int AudioConfigsCount { get; set; } + public DateTime ExportedAt { get; set; } + public string Version { get; set; } = string.Empty; + public string AppVersion { get; set; } = string.Empty; public List ConflictingItems { get; set; } = []; } \ No newline at end of file diff --git a/DataFlow-Mobile/DataFlow.Mobile/Models/LayoutTemplate.cs b/DataFlow-Mobile/DataFlow.Mobile/Models/LayoutTemplate.cs index b07040b..cc3fe6f 100644 --- a/DataFlow-Mobile/DataFlow.Mobile/Models/LayoutTemplate.cs +++ b/DataFlow-Mobile/DataFlow.Mobile/Models/LayoutTemplate.cs @@ -81,6 +81,9 @@ public class LayoutTemplate [Required] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + // Additional properties for compatibility + public string? Configuration { get; set; } + // Navigation properties public ICollection