Commit Graph

12 Commits

Author SHA1 Message Date
spouliot 843d1c3c51 Add token-authenticated catalog import API endpoint
POST /PowderCatalog/ImportApi accepts the JSON scrape format in the request
body, authenticated by a shared secret in the X-Import-Token header (matched
constant-time against CatalogImport:Token), with the vendor in X-Vendor-Name.
Runs through the same ImportJsonAsync -> shared upsert as the manual upload, so
the offline PrismaticSync tool can push unattended.

ImportJsonAsync refactored to take a Stream (the form upload now passes
file.OpenReadStream()). Endpoint is AllowAnonymous + IgnoreAntiforgeryToken
(it's token-gated, not cookie-auth) and returns 401 until a token is configured,
so it's inert by default. README updated with the route + token wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:35:30 -04:00
spouliot c59d55529f Add PrismaticSync console tool for unattended Prismatic catalog sync
Standalone .NET 8 console app (not part of the main solution) that scrapes the
Prismatic Powders catalog via Playwright and pushes it into the app's catalog
import. Prismatic has no API, so this runs on a workstation (Task Scheduler),
never the deployed server.

- Discovery: incremental newest-first via ?category=created_at (stops once it
  reaches already-known URLs — cheap, finds new colors) and a full all-colors
  crawl for occasional reconcile.
- Scraper: resumable product-page scrape (sku/color/description/price tiers/
  SDS/TDS/app-guide/image), with --refresh-older-than to re-scrape stale
  products and catch price changes. Output matches the app import format so it
  flows through the same shared upsert as the Columbia sync.
- Resilience: brisk randomized base delay, escalating 403 cooldown-and-retry to
  avoid hard bans, periodic rest. All configurable.
- Visibility: streams every product + the inter-product wait to the console
  (colored) and a log file, with an up-front ETA.
- Push: token-authenticated POST to the app import endpoint (skips to manual
  upload when unconfigured).

The app-side token import endpoint is a separate follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:30:47 -04:00
spouliot 32a95052fa Remove accidentally-committed publish-output/ and stray root artifacts
Deletes the committed dotnet publish output folder (434 files: DLLs,
bundled static assets) plus 73 stray root files (old *_FIX/*_SUMMARY
docs, .bak files, loose .sql scripts, deploy.zip, screenshots) and a
few scripts/. Repo housekeeping to reclaim disk space; no src/ or
wwwroot/ files touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:09:11 -04:00
spouliot 0b839d0746 Fix Company Settings save, invoice PAID stamp, and purge script
- Company Settings: switch save button from type=submit to type=button
  to bypass HTML5 form validation blocking the submit event; replace
  AutoMapper Map() with explicit property assignment so EF change
  tracking reliably detects mutations; fix showButtonSuccess() never
  re-enabling the button after a successful save
- Invoice PDF: move PAID stamp into the header row as a centered middle
  column so it sits between the company and invoice blocks without
  adding height to the document
- Purge script: use business-date fields instead of CreatedAt so
  imported records (which all share today's CreatedAt) are correctly
  filtered by actual transaction dates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 17:36:15 -04:00
spouliot 82fb48f7a5 Patch export/import for missing fields; add CustomerContacts export
- DataExportController + AccountDataExportController: add ProjectName to
  Jobs, Quotes, Invoices (XLSX + CSV); add LeadSource + ShipTo fields to
  Customers (XLSX + CSV); add CustomerContacts sheet/CSV (new)
- Both export views: add Customer Contacts checkbox (checked by default)
- CustomerImportDto: add LeadSource + ShipTo* fields
- JobImportDto: add ProjectName
- QuoteImportDto: add ProjectName
- InvoiceImportDto: add Project Name (dual-name alias for round-trip)
- CsvImportService: wire all new import fields to entity creation;
  also patch invoice update path for ProjectName
- Add scripts/purge_imported_data.sql (dry-run T-SQL for data cleanup)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 15:14:27 -04:00
spouliot acbd9f60be Hide email controls when no email on file; show SMS hint for quote/job events
- Quotes Create/Edit: hide 'Send via email' checkbox when customer has no
  email; show badge 'send via SMS from details' or 'SMS consent required'
  when customer has a mobile number. JS responds to customer dropdown change.
- Quotes Details: hide 'Send Quote via Email' button and approval email
  checkbox; hide SMS button when no mobile; show consent-required note.
- Jobs Details (Mark Complete modal): hide email checkbox; show
  'SMS notification will be sent' badge or consent-required note.
- Jobs Index (status modal): hide email row when customer has no email.
- Jobs Edit: hide 'Notify customer if status changes' when no email.
- Invoices Details: hide Send/Re-send buttons when no email (vs. disabled).

DTOs: added CustomerEmail + CustomerNotifyByEmail to JobDto/JobListDto;
added CustomerNotifyByEmail/CustomerMobilePhone/CustomerNotifyBySms to
QuoteDto. Mapped in JobProfile and QuotesController customer blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:32:08 -04:00
spouliot 810d5a5dc1 Update idempotent EF migration script for prod deploy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:23:22 -04:00
spouliot 11a1b91be1 Add platform powder catalog management UI with full CRUD and AI lookup
- PowderCatalogController: Create, Edit, ToggleDiscontinued actions; searchable/filterable/sortable Index with pagination; AiLookup and AiAugmentFromUrl endpoints backed by IInventoryAiLookupService
- New views: Create, Edit, _Form partial (with AI-assisted field population), overhauled Index grid with completeness quality badges and responsive mobile cards
- New ViewModels: PowderCatalogIndexViewModel, PowderCatalogFormViewModel, PowderCatalogListItemViewModel
- AI lookup improvements: SpecificGravity field added to InventoryAiLookupResult; ApplyPowderFallbacks derives CoverageSqFtPerLb from specific gravity when docs omit it; DefaultTransferEfficiency (65%) applied everywhere transfer efficiency is null
- powder-catalog-ai-lookup.js: client-side AI lookup and URL augment wiring for the catalog form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 00:27:44 -04:00
spouliot 4c58d57928 Commit misc scripts, feature specs, SQL deploy scripts, and settings updates 2026-05-04 22:14:25 -04:00
spouliot 1cb7a8ca4a Phases 3 & 4: Complete data access architecture migration
Phase 3 — eliminated ApplicationDbContext from all non-exempt controllers,
routing all data access through IUnitOfWork. Added IPlainRepository<T> for
the four platform entities (Announcement, BannedIp, DashboardTip, ReleaseNote)
that intentionally don't extend BaseEntity and therefore can't use the
constrained IRepository<T>. Added permanent-exception comments to the 18
controllers that legitimately retain direct DbContext access (Identity infra,
cross-tenant platform ops, bulk streaming exports).

Phase 4 — added EnforceDataAccessArchitecture() to Program.cs, a startup
gate that reflects over every Controller subclass and throws at boot if any
non-exempt controller injects ApplicationDbContext. The app cannot start with
a violation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 09:17:29 -04:00
spouliot 0bb96a502a Add passkey / biometric login (WebAuthn FIDO2)
Shop floor workers can log in once with a password, enroll a passkey,
and use Face ID / Windows Hello / fingerprint for all future logins.

- UserPasskey entity + AddUserPasskeys migration (Fido2 v4.0.1)
- PasskeyController: RegisterOptions, Register, LoginOptions, Login,
  Manage, Remove endpoints
- Login page: platform-aware button (Face ID / Windows Hello / etc.)
  hidden automatically if browser doesn't support WebAuthn
- Post-login floating prompt to enroll on first use; session-dismissed
- Passkeys & Biometrics link in user dropdown menu
- Manage page: list registered devices, add new, remove individual
- passkey.js: targeted base64url conversion (only challenge + user.id
  + credential IDs) — fixes "Required parameters missing" error caused
  by blindly converting rp.id and other string fields to ArrayBuffers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 15:07:01 -04:00
spouliot 63e12a9636 Initial commit 2026-04-23 21:38:24 -04:00