Introduces a complete shipment review system allowing staff to request changes to customer orders and customers to resubmit corrected orders. - Add `ReviewState` enum and `ShipmentReview` model to track review history. - Implement `ShipmentReviewService` to handle approval and change request logic. - Add `resubmit` endpoint for customers to update orders when `changes_requested` state is active. - Add `request-changes` endpoint for staff to flag orders for correction. - Update `ShipmentResource` in Filament to display review states and manage approvals. - Implement WordPress bridge support for fetching and resubmitting orders via AJAX. - Add database migrations for `shipment_reviews` table and `review_state` column on shipments. - Add `StaffApiMiddleware` to secure staff-specific API routes.
6.8 KiB
IFNEX Logistics — Architecture Decisions
ADR-001 — Shipment Review Workflow
Context
The current order workflow uses Shipment.status for operational, approval, and payment-related states simultaneously.
Current flow:
pending_approval → approved → payment → processed → ...
There is currently no independent representation for:
- review decision
- requested customer changes
- review history
- customer resubmission
Decision
Introduce an independent Review State and Review History domain without immediately removing or redesigning the existing ShipmentStatus enum.
Transitional model
Shipment.status remains backward-compatible:
pending_approvalapprovedcancelled- operational statuses
- legacy
pending_payment
A new Shipment.review_state represents:
pendingchanges_requestedapprovedrejected
Target workflow
Create Order
↓
review_state = pending
status = pending_approval
↓
Staff Review
├── Request Changes
│ ↓
│ review_state = changes_requested
│ ↓
│ Customer Edit
│ ↓
│ Resubmit
│ ↓
│ review_state = pending
│
├── Approve
│ ↓
│ review_state = approved
│ status = approved
│
└── Reject
↓
review_state = rejected
status = cancelled
Consequences
This allows the current client workflow to work without immediately breaking existing code that depends on ShipmentStatus.
Long term, approval/review state can be fully separated from operational Shipment status.
ADR-002 — Shipment Review History
Decision
Create a dedicated shipment_reviews domain instead of storing review reasons and decisions directly on shipments or using ShipmentStatusHistory as a substitute.
A review record should contain at minimum:
id
shipment_id
revision_no
decision
reason
notes
reviewed_by
reviewed_at
created_at
updated_at
The review record represents one submission/review cycle.
Rationale
ShipmentStatusHistory records operational status transitions. Review decisions are a different domain concern and require reviewer identity, reason, notes, and revision context.
ADR-003 — Customer Revision / Resubmission
Decision
Customer correction must not be implemented as a blind PATCH against the Shipment.
A resubmission updates the current shipment aggregate transactionally while creating a new review revision.
The aggregate includes:
Shipment
ShipmentPackage[]
ShipmentItem[]
Required behavior
Resubmission must:
- validate the complete order payload;
- recalculate volumetric and chargeable weight server-side;
- recalculate pricing server-side;
- update Shipment fields;
- synchronize Packages;
- synchronize Items;
- create a new review revision;
- return the shipment in
pendingreview state.
AWB remains unchanged because the customer is revising the same order.
ADR-004 — Request Changes vs Reject
Decision
These are distinct actions.
Request Changes
- order remains active;
- customer may edit;
- customer may resubmit;
- reason is mandatory;
- shipment remains operationally pre-approval.
Reject
- order is terminated;
- shipment becomes cancelled;
- customer cannot continue the same review cycle.
API concepts:
POST /staff/orders/{shipment}/request-changes
POST /staff/orders/{shipment}/approve
POST /staff/orders/{shipment}/reject
POST /customer/orders/{shipment}/resubmit
ADR-005 — Payment State
Decision
Do not derive payment state from Shipment.status.
The current implementation remains temporarily compatible with:
Approved → payment available
but the target architecture is:
Shipment Operational Status
Review State
Payment State
as separate domains.
ShipmentStatus::isPaid() must eventually be removed or deprecated because states such as cancelled, failed, and returned cannot safely imply payment completion.
ADR-006 — Order Detail API Contract
Laravel is the canonical source of the Order Detail API contract.
Target response structure:
shipment
├── status
├── review
├── sender
├── receiver
├── packages[]
├── items[]
├── financial
├── documents[]
└── tracking_events[]
WordPress must consume the canonical Laravel structure rather than relying on legacy flattened fields.
Known current contract mismatches:
- Laravel returns
sender.*, WordPress expectssender_name,sender_phone, etc. - Laravel returns
receiver.*, WordPress expects flattened receiver fields. - Laravel returns tracking event keys
date,description,location; WordPress expectsevent_date,event_description,event_time.
These mismatches must be corrected during API/UI hardening.
ADR-007 — Commitment Documents
CommitmentForm is the reusable template.
ShipmentCommitmentForm represents the requirement/instance for a particular shipment.
For the current client:
- physical delivery is the primary process;
- online upload is optional;
- signed-document upload must not block approval/payment unless explicitly required by business policy.
Required documents should eventually be instantiated/snapshotted per shipment instead of dynamically resolving the current active templates.
ADR-008 — Operational Finance Boundary
IFNEX is not intended to become a full accounting system.
IFNEX should provide logistics-relevant financial information:
- wallet
- customer receivable/debt status
- order financial status
- payment transactions
- credit/settlement information
- audit trail
A deeper accounting system should be integrated externally through API rather than recreated inside IFNEX.
ADR-009 — Production Hardening Findings
The following findings require later hardening:
ShipmentStatus::isPaid()is semantically unsafe.Shipment::isDelivered()compares an Enum-cast field with a string.StaffOrderControllerlacks explicit role/permission authorization.- Order Detail sender/receiver API contract is inconsistent with WordPress.
- Tracking event API contract is inconsistent with WordPress.
- PDF download uses a different user-meta token key from the standard Bridge token.
- Commitment-form shipment requirements are not currently snapshotted.
- Customer signed-document uploads currently use public storage semantics.
ShipmentPackageandShipmentItemare not included in the current detailed customer order response.- Payment state is coupled to Shipment status.
- Price calculation and discount consumption require a clear distinction between preview and committed pricing.
- Existing status/schema migration history should be preserved; do not rewrite historical migrations.