- Carry OvenBatches/OvenCycleMinutes from Quote → Job entity (was missing fields; all job pricing recalcs hardcoded 1/null)
- Fix invoice creation from job always showing Quantity=1 (was using TotalPrice as UnitPrice with qty 1)
- Add IsAiItem to JobItem + migration; map in all 3 JobItemAssemblyService.CreateJobItem overloads so AI photo jobs no longer double-price on first edit after quote→job conversion
- Propagate IsAiItem through all existingItemsData JSON blocks in Jobs views (Edit, EditItems, Create) so the wizard preserves AI routing on re-edit
- Add PricingRoutingFlags_ExistOnBothQuoteItemAndJobItem structural test + 3 behavioral IsAiItem tests to JobItemAssemblyServiceTests
- Consolidate item wizard partials (_ItemWizardModal, _SqFtCalculatorModal) and item-wizard.css into shared locations
- Document pricing flag propagation checklist in CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Stripe payments/refunds/chargebacks now post DR/CR entries (PaymentController)
- Vendor credit void now reverses the posted GL lines (VendorCreditsController)
- Gift certificate issue/redeem/void post GL to account 2500 GC Liability;
FinancialReportService Trial Balance + Balance Sheet include GC liability and
breakage income; P&L shows deferred revenue deduction and breakage income line
- Customer deposits now post DR Checking / CR 2300 on record, reverse on delete;
invoice auto-apply uses DR 2300 / CR AR (not a second bank debit); draft
invoice delete reverses deposit-apply GL before the AR reversal
- Deposit.DepositAccountId column added; account 2300 seeded via migration
- InvoicesController.ApplyCredit now posts DR Sales Discounts / CR AR,
consistent with CreditMemosController.Apply
- IssueRefund (cash/card) posts DR AR / CR Bank and sets Refund.DepositAccountId;
refund modal gains a bank account selector hidden for store-credit path
- CancelRefund (cash/card) reverses the IssueRefund GL entries
- LedgerService GetAccountLedgerAsync + ComputePriorBalanceAsync now include
Refunds, CreditMemoApplications, VendorCreditApplications, GC Liability (2500),
and Customer Deposits (2300) so account ledger view and RecalculateAllAsync
produce correct balances
- Three EF migrations applied: SeedSalesDiscountsAccount, AccountingGapsPhase2,
AccountingDepositsGL
- Unit tests updated for new IAccountBalanceService constructor params (200/200)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1 — Invoice total didn't match job total for direct jobs:
- Root cause: all three item-save paths in JobsController passed null for
ovenCostId, so FinalPrice/ShopSuppliesAmount were stored without oven cost
while the Details page recalculated live with OvenCostId and showed higher.
- Add OvenBatchCost stored field to Job entity (migration AddJobOvenBatchCost,
default 0 for existing rows).
- Fix Create, Edit, and UpdateItems to pass job.OvenCostId and save OvenBatchCost.
- Fix InvoicesController.Create GET for direct jobs to use stored OvenBatchCost
and ShopSuppliesAmount as separate labeled lines instead of recalculating
shop supplies from scratch (which excluded the oven cost base).
Bug 2 — Quote status stayed Draft after "Send Quote via Email":
- ResendQuote advanced the approval token and sent the email but never
updated the status. Added Draft → Sent advancement (same guard used by
the SMS send path) so the status updates on successful email send.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Invoice Write-Off: WriteOff POST action in InvoicesController posts bad-debt JE
(DR bad debt expense / CR AR), reduces customer balance, marks invoice WrittenOff;
write-off modal added to Invoice Details view with expense account selector
- Fixed Assets: FixedAsset + FixedAssetDepreciationEntry entities with straight-line
depreciation; FixedAssetsController (Index/Create/Edit/Details/PostDepreciation/Delete);
PostDepreciation auto-generates one JE per asset per period, skips already-posted,
fully-depreciated, and disposed assets; full CRUD views + nav link
- Period Locking: Company.BookLockedThrough field; AccountingPeriodValidator static helper;
lock check added to JE Post and Bill Create (blocks backdating into closed periods);
SetPeriodLock action + date picker UI in Company Settings Accounting section
- 1099 Tracking: Is1099Vendor flag on Vendor entity + DTOs; checkbox in Create/Edit views;
TaxReporting1099 report action + view lists payments by year, flags vendors >= $600;
report card added to Reports Landing
- Migration AddFixedAssetsLockAnd1099 applied
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Voided invoices no longer block creating a new invoice for the same job: voided invoice's
JobId FK is cleared so the unique index slot is freed for the replacement
- Invoice Details view shows voided invoices as history rather than hiding them
- Payment terms: standardized SelectList (Due on Receipt, Net 15/30/45/60/90, 2% 10 Net 30,
COD) with custom-term preservation; invoice-due-date.js auto-updates Due Date on term change
- Shop supplies on direct (no-quote) jobs: InvoicesController derives the shop supplies line
from the company rate when the job has no source quote to read the pre-agreed amount from
- Job entity: ShopSuppliesAmount + ShopSuppliesPercent fields preserved through job lifecycle
- Migration: AddShopSuppliesAmountToJob
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a tenant uploads a logo it is stored in Azure Blob Storage and
LogoData (the legacy DB byte[]) is cleared. All PDF controllers were
still reading the now-null LogoData, so logos never appeared on any
PDF after upload. Fixed by injecting ICompanyLogoService into all six
affected controllers (Quotes, Invoices, Deposits, GiftCertificates,
PurchaseOrders, CatalogItems) and loading the blob-stored logo first
before falling back to the legacy DB field.
Also added structured logging to the AI photo promotion path in
QuotesController Create/Edit POST so upload failures are visible in
production logs instead of silently swallowed.
Added onclick safety net to the Create and Edit quote submit buttons
so dynamically-injected hidden fields (AiPhotoTempIds) are written
before iOS Safari collects the form data on submit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setup Wizard: reduced from 10 steps to 5 (Company Info → QB Migration →
Pricing Defaults → Named Ovens → Notifications). Removed Doc Numbering,
Job Settings, Payment Terms, Pricing Tiers, and Team Members steps — these
all have sensible defaults and are accessible any time in Company Settings.
Wizard now completes in ~5 minutes instead of 15–20.
Dashboard progress widget (new): "Get the most out of your shop" checklist
appears for Company Admins after wizard completion. Tracks six post-setup
activation tasks with dynamic progress badge, motivating subtitle copy,
collapsed-state persistence via localStorage, and a full completion state
("Your shop is fully set up 🎉") that replaces the checklist at 100%.
The next recommended step is highlighted with a solid CTA button and a
subtle blue row tint. Completed steps show encouraging green subtext instead
of just "Done". Widget disappears from controller when AllDone would have
caused a silent vanish — now renders the completion state instead.
Guided activation (Daily Board): rewrote the BoardIntroStep callout to lead
with "This is your shop in real time" and a plain-English description of the
board's purpose. Added a separate InstructionText field to
GuidedActivationCalloutViewModel so the "Move this job to the next stage"
action prompt renders as a distinct bold line with an arrow icon rather than
being buried in the body copy. After the stage change, the confirmation
callout now reads "Nice — your workflow just updated" to reinforce what just
happened before prompting the invoice step.
All copy passes the "shop owner, not SaaS" test: no technical jargon,
benefit-driven descriptions, natural language throughout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrated InvoicesController, QuotesController, JobsController, BillsController,
PurchaseOrdersController, and CustomersController to route all data access
through IUnitOfWork typed/generic repositories instead of injecting
ApplicationDbContext directly.
New typed repositories added: IJobRepository (GetScheduledJobsForDateAsync,
GetActiveJobsForMobileAsync, LoadForCostingAsync), INotificationLogRepository
(GetLatestForJobAsync, GetAllForJobAsync), IQuoteRepository (GetItemsWithCoatsAsync
with CatalogItem eager load + AsNoTracking), and IJobRepository.GetOrphanedConversionJobAsync.
All EF complex include chains relocated into repository methods; controllers now
call named query methods rather than composing raw IQueryable chains.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Email delivery failures and PDF generation errors now show a permanent
warning/error toast that requires manual dismissal, so users cannot
accidentally miss critical action-blocking feedback.
- ToastHelper: WarningPermanent TempData key + Warning/WarningPermanent
extension methods on both ITempDataDictionary and Controller
- SetNotificationResultToast: NotificationStatus.Failed now uses
ToastWarningPermanent (previously auto-dismissed in 5 s)
- InvoicesController.Send: TempData["Warning"] → TempData["WarningPermanent"]
when PDF generation or email dispatch fails
- InvoicesController.DownloadPdf: TempData["Error"] → TempData["ErrorPermanent"]
with the actual exception message so root cause is visible
- _Layout.cshtml: WarningPermanent hidden div
- toast-notifications.js: WarningPermanent handler (timeOut: 0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PasskeyController: set LastLoginDate on passkey sign-in so Company Health
and audit pages show accurate last-login times (was always showing 'Never')
- Jobs/Index status modal: disable 'Notify customer' email toggle and show
warning when customer has notifications turned off; CustomerNotifyByEmail
added to JobListDto + JobProfile mapping + data-customer-notify attribute
- Quotes/Create: disable 'Send quote via email' checkbox with 'Notifications
off' badge when selected customer has email opt-out; ViewBag.CustomerEmailOptOutIds
added alongside existing CustomerTaxExemptIds pattern
- Quotes/Create: Quick Quote / Full Quote segmented toggle at top of form;
hides non-essential fields (dates, notes, tags, oven, discount, photos) in
Quick mode; selection persisted in localStorage
- InvoicesController Send action: improved error logging and user-facing
warning when PDF generation or email dispatch fails after status is saved
- item-wizard.js: guard item restoration with try/catch; ensure writeHiddenFields
always runs on form submit via capture-phase listener
- Help docs and AI knowledge base updated for all new features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>