Feature/application slices - #10
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 7 — Complete application-layer feature slices (Steps 32–40)
Summary
Phase 7 completes the application layer with 16 commands and queries covering the full call-to-cash workflow:
The scheduling engine now operates against persisted data, and an integration test exercises the complete workflow from customer creation through payment.
No controllers or public API contracts are included; those remain scheduled for Steps 47–49.
Main changes
Application
Added feature slices for:
Customers/CreateCustomerCustomers/AddServiceLocationCustomers/GetCustomerCustomers/ListCustomersTechnicians/CreateTechnicianTechnicians/SetSkillsTechnicians/SetShiftTechnicians/ListTechniciansJobs/CreateJobJobs/ChangeJobStatusJobs/AssignJobScheduling/OptimizeDayScheduling/InsertJobDispatch/GetBoardInvoicing/GenerateInvoiceInvoicing/MarkPaidEach slice includes its command/query, handler, validator where applicable, result projections, and typed errors.
AddApplicationdiscovers all 16 handlers and 14 validators through assembly scanning.Domain
Added:
Technician.SetSkillsJob.CanTransitionJob.MarkInvoicedJob.MarkPaidStopPlacementlogic enforcing one stop per jobThe job transition theory now covers all 72 status combinations.
Infrastructure
Added:
SystemClockAddInfrastructureregistrationIScheduler → AnnealingSchedulerDispatchBoardReadModelFakePaymentGatewayThe dispatch board is produced with three fixed queries rather than loading aggregate graphs or issuing per-row queries.
Testing support
Added reusable application-test fakes and a
SliceHostthat execute requests through the real MediatR pipeline, validators, scheduling engine, and travel provider.A reflection-based request-shape test prevents commands and queries from accepting
OrgIdor rawGuidvalues. Tenant identity must come fromITenantContext.Important implementation decisions
Payment ordering
MarkPaidcharges the payment gateway before changing invoice and job state. This avoids recording payment when the gateway refuses the charge.There is still a known risk when a real processor is introduced: payment could succeed before the database transaction fails.
ChargeAsyncaccepts the invoice identity to support idempotent retries, but durable payment-reference storage and reconciliation remain future work.The current fake gateway is intentional v1 product scope because real payment processing is explicitly excluded.
Scheduling precision
Planned timestamps are truncated to PostgreSQL’s microsecond precision inside
StopPlacement.Without this, a schedule read from PostgreSQL differs slightly from the same schedule recomputed in .NET, causing unchanged stops to appear moved. Integration coverage confirms that optimizing an unchanged day publishes no
AssignmentChangedevents.Dropped scheduled jobs
When optimization can no longer place a job, its stop is removed but its status remains
Scheduledbecause the state machine has noScheduled → Unscheduledtransition.The job remains visible in the dispatch board’s unassigned pile, but this is the main unresolved edge in the phase. A complete fix requires
Job.Unschedule(), a contract-table change, regeneration, and a client release.Manual skill overrides
Manual assignment does not enforce technician skill matching. The optimizer still treats skill matching as a hard constraint, but a dispatcher may override it when manually placing work.
Reviewer feedback is specifically requested on this choice. A stricter alternative would return a conflict unless the command contains an explicit override flag.
Manual assignment does not re-time the route
Moving a stop manually recalculates that stop’s sequence and travel time but does not re-time all later stops. Full route timing remains the scheduling engine’s responsibility.
The dispatch board therefore orders stops by scheduled start and treats sequence as a label rather than assuming it is always contiguous.
Commands carry primitives for validated values
Commands use primitives for coordinates, time windows, and objective weights. Validators reject malformed values before handlers construct domain value objects.
This prevents expected input errors from escaping as domain exceptions and becoming HTTP 500 responses. Strongly typed IDs remain on commands because an unknown ID is a handler-level
NotFoundresult rather than malformed input.UTC conversion
External
DateTimeOffsetvalues are converted to UTC before persistence because Npgsql rejects non-zero offsets fortimestamptz.This currently occurs in four handlers. A future persistence convention could centralize the conversion, although scheduling precision must still be normalized before domain comparison.
Domain events for invoicing
Job.MarkInvoicedandJob.MarkPaiddo not publish separate job events. Invoice creation andInvoicePaidalready describe those actions, so publishing equivalent events from both aggregates would duplicate one business occurrence.Testing
All checks pass:
make check-contractscleanCoverage includes:
Run locally with:
make test make test-fast make check-contractsReviewer focus
Feedback is most useful on:
Scheduled → Unscheduledtransition for optimizer-dropped jobs.Deliberately excluded
This phase does not include: