Pallet Working Release - #4
Conversation
This comprehensive refactoring transforms Pallet into a complete enterprise-grade Warehouse Management System following the Fleetops architecture patterns. Backend Enhancements: - Added 11 new models for enterprise WMS features: * PickList & PickListItem for warehouse picking operations * Wave for wave-based picking management * CycleCount & CycleCountItem for inventory accuracy * StockTransfer & StockTransferItem for inter-warehouse transfers * BinLocation for detailed location tracking * WarehouseZone for zone management * InventoryReservation for order allocations * ProductKitComponent for kit/bundle management - Enhanced existing models: * Product: Added tracking flags, reorder points, shelf life, kit support * Inventory: Added lot/serial tracking, multi-UOM, reserved quantities * Warehouse: Added zones, bins, capacity tracking, utilization metrics Frontend Refactoring: - Refactored Product components to modular Fleetops pattern: * product/form.hbs & .js - Comprehensive form component * product/details.hbs & .js - Read-only detail view * product/panel-header.hbs & .js - Panel header component * product/pill.hbs & .js - Compact display component - Modernized templates: * Updated products/index to use Layout::Resource::Tabular * Updated products/index/details to use Layout::Resource::Panel * Updated products/index/edit to use Layout::Resource::Panel Database Schema: - Created comprehensive migration for all new tables and enhanced columns - Added proper indexes and foreign key constraints - Supports lot/serial tracking, reservations, and advanced WMS operations Features Implemented: ✓ Lot/batch and serial number tracking ✓ Inventory reservations (soft/hard) ✓ Pick list management with multiple strategies ✓ Wave-based picking ✓ Cycle counting with variance tracking ✓ Inter-warehouse stock transfers ✓ Bin location and zone management ✓ Kit/bundle product support ✓ Expiry date tracking and alerts ✓ Reorder point management This establishes the foundation for a complete, scalable, enterprise-grade WMS.
Refactored all remaining resources to follow Fleetops architecture pattern: - Inventory: form, details, panel-header, pill components - Warehouse: form, details, panel-header, pill components - Supplier: form, details, panel-header, pill components - Purchase Order: form, details, panel-header, pill components - Sales Order: form, details, panel-header, pill components - Batch: form, details, panel-header, pill components Updated all templates to use modern Layout components: - All index templates now use Layout::Resource::Tabular - All details templates now use Layout::Resource::Panel with TabNavigation - All edit templates now use Layout::Resource::Panel with form integration This completes the frontend modernization across all 7 resources: ✓ Product (previously completed) ✓ Inventory ✓ Warehouse ✓ Supplier ✓ Purchase Order ✓ Sales Order ✓ Batch Total: 48 new component files + 84 app exports + 18 template updates
Enterprise WMS Refactoring with Fleetops Architecture
Backend fixes:
- [B1] Fix WarehouseDockController class name (was WarehouseSectionController)
- [B2] Fix PurchaseOrder resource orde_date_at typo -> order_date_at; add order_number alias for public_id
- [B3] Fix SalesOrder resource orde_date_at typo -> order_date_at; add order_number alias for public_id
- [B4] Rename WarehouesDock.php -> WarehouseDock.php (filename typo fix)
- [B5] Rename SuplierFilter.php -> SupplierFilter.php (filename typo fix)
- [B6] Fix PurchaseOrderController payload key: purchaseOrder -> purchase_order
- [B7] Fix SalesOrderController payload key: salesOrder -> sales_order
- [B8] Fix Audit model User namespace: App\Models\User -> Fleetbase\Models\User
Add HasPublicId trait, SoftDeletes, align fillable with actual DB schema
- [B9] Add all missing WMS fields to Inventory resource (status, bin_location_uuid,
zone_uuid, lot_number, serial_number, uom, reserved_quantity, available_quantity,
max_quantity, reorder_point, unit_cost, received_at, last_counted_at)
- [B10] Fix PalletServiceProvider error messages (Storefront -> Pallet)
- [B11] Fix InventoryController::createRecord - explicitly set batch_uuid on inventory
record; add received_at timestamp; improve batch_number generation
- [B12] Fix StockAdjustment resource - use incrementing_id; add missing fields
(inventory_uuid, warehouse_uuid, reason, notes, adjustment_type)
- [B13] Create missing Audit HTTP Resource class
Frontend fixes:
- [F1] Build out Audit model with all attributes, relationships, and computed properties
- [F2] Build out AuditsIndexController with columns, search, queryParams, and tracking
- [F3] Fix routes.js - add index sub-routes to audits, reports, and batch parent routes
- [F4] Build out audits/index.hbs template with Layout::Resource::Tabular
- [F5] Fix inventory model - add all missing WMS fields (bin_location_uuid, zone_uuid,
lot_number, serial_number, uom, reserved_quantity, available_quantity, max_quantity,
reorder_point, unit_cost, received_at, last_counted_at, isLowStock, isExpired)
Fix supplier relationship type (vendor -> supplier)
- [F6] Fix purchase-order model - add order_number, order_date_at, currency, meta attrs
Fix supplier relationship type (vendor -> supplier)
- [F7] Fix sales-order model - add order_number, currency, meta attrs
Fix supplier relationship type (vendor -> supplier)
- [F8] Fix inventory-form-panel.hbs - fix @onchange on Supplier ModelSelect
(was passing string value, now correctly uses fn (mut ...))
- [F9] Fix @isResizeble typo -> @isResizable across ALL form panel components:
inventory, warehouse, supplier, purchase-order, sales-order, batch,
product, stock-adjustment, warehouse-editor
- [F10] Create addon/serializers/audit.js
- [F11] Complete translations/en-us.yaml - add all ~120 missing translation keys
across resource, common, product, inventory, warehouse, supplier,
purchase-order, sales-order, batch, and audit namespaces
## What changed
### Backend
**Migration (modified, not new)**
- Refactored pallet_audits table: added event_type column (indexed), renamed
auditable_uuid/type to subject_uuid/subject_type for clarity, added
scheduled_at/completed_at for time-bounded events, added composite indexes
on (company_uuid, event_type) and (subject_uuid, subject_type)
**New: AuditEventType constants class**
- Defines all machine-readable event type keys: stock_adjustment, cycle_count,
po_received, so_fulfilled, stock_transfer, inventory_created, batch_created, etc.
**New: HasOperationalAuditTrail trait**
- Reusable trait any Pallet model can use to call logAuditEvent()
- Automatically captures company_uuid, performed_by_uuid, subject, and meta
**New: AuditService**
- Centralised service for programmatic audit logging from controllers
- Provides log() and logForModel() helpers
**Refactored: Audit model**
- Now immutable (no direct create/update/delete via API)
- Added event_type, subject_uuid/type, scopes (byEventType, bySubject, recent)
- Added SoftDeletes, HasPublicId, correct Fleetbase User namespace
**Refactored: AuditController**
- Now read-only: index() and show() only
- Added eventTypes() endpoint: GET /pallet/v1/audits/event-types
- Filters by event_type, subject_type, performed_by_uuid, date range
**Refactored: Audit HTTP Resource**
- Returns event_type, subject_label, action, reason, meta, performedBy
**Refactored: routes.php**
- Replaced generic fleetbaseRoutes('audits') with explicit read-only routes
- Added GET /pallet/v1/audits/event-types endpoint
**WMS model integrations**
- StockAdjustment: logs STOCK_ADJUSTMENT event on created()
- CycleCount: logs CYCLE_COUNT event on complete() and approve()
- PurchaseOrder: markAsReceived() logs PO_RECEIVED event
- SalesOrder: markAsFulfilled() logs SO_FULFILLED event
- StockTransfer: logs STOCK_TRANSFER event on ship() and receive()
**Spatie LogsActivity added to 8 primary models**
- Product, Inventory, Warehouse, Supplier, Batch, PurchaseOrder, SalesOrder,
StockAdjustment now all use LogsActivity with logOnly() + logOnlyDirty()
- Consistent with how core-api handles User, Alert, File, etc.
### Frontend
**Audit model**
- Updated to use event_type, subject_uuid/type instead of auditable_uuid/type
- Added eventTypeLabel, subjectLabel, eventTypeBadgeClass computed properties
- Added createdAgo with addSuffix option
**Audits/index controller**
- queryParams updated: event_type + subject_type replace auditable_type
- Columns updated: Event Type, Action, Subject, Subject ID, Reason, Performed By, Date
- Added eventTypeOptions array for dropdown filter
- Added filterByEventType() and clearFilters() actions
**Audits/index template**
- Added event type filter dropdown in subheader slot
- Added clear filters button (shown when any filter is active)
- Set @Cancreate=false and @canDelete=false (immutable trail)
**Audit serializer**
- Removed createdBy embedded relation (no longer in schema)
**Translations**
- Expanded audit section with event-types, filter labels, search placeholder
- Added common.clear_filters and common.no-records keys
Backend: - Add migration: purchase_order_items and sales_order_items tables with full schema: product_uuid, warehouse_uuid, quantity, quantity_received/ quantity_fulfilled, outstanding_quantity, unit_price, unit_cost, total_price, currency, sku, lot_number, serial_number, expiry_date, unit_of_measure, status, notes, meta, received_at/fulfilled_at - Add PurchaseOrderItem model with recalculateTotalPrice(), relationships to Product, Warehouse, PurchaseOrder; LogsActivity trait - Add SalesOrderItem model with recalculateTotalPrice(), relationships to Product, Warehouse, Inventory, SalesOrder; LogsActivity trait - Add hasMany items() + item_count/total_value aggregates to PurchaseOrder model - Add hasMany items() + item_count/total_value aggregates to SalesOrder model - Add PurchaseOrderItemController (index, store, update, destroy) - Add SalesOrderItemController (index, store, update, destroy) - Add PurchaseOrderItem and SalesOrderItem HTTP Resources - Update PurchaseOrder resource to include items, item_count, total_value - Update SalesOrder resource to include items, item_count, total_value - Add nested item routes: GET/POST/PUT/DELETE for both PO and SO items Frontend: - Add purchase-order-item Ember model with all attributes + computed helpers - Add sales-order-item Ember model with all attributes + computed helpers - Add hasMany items + item_count/total_value to purchase-order Ember model - Add hasMany items + item_count/total_value to sales-order Ember model - Add purchase-order-item and sales-order-item serializers - Update purchase-order and sales-order serializers to embed items - Add purchase-order-panel/items tab component (HBS + JS) with: - Inline add row with product ModelSelect, SKU, quantity, unit price - Inline edit row per item - Read-only rows showing product, SKU, qty, qty received, unit price, total - Status badge per item - Delete per item - Disabled when order is received/cancelled - Add sales-order-panel/items tab component (HBS + JS) with same pattern (qty fulfilled instead of qty received) - Wire Items tab into purchase-order-panel and sales-order-panel components - Expand translations: purchase-order.line-items.* and sales-order.line-items.*
Backend:
- PurchaseOrderController: full receive() action with DB transaction,
line-item iteration, inventory create/increment, lot/serial/expiry/bin
tracking, PO status transition (partial/received), audit trail logging
- SalesOrderController: full fulfill() action with pre-flight stock check,
FEFO inventory selection, available_quantity deduction, SO item status
tracking, SO status transition (partial/fulfilled), audit trail logging
- routes.php: POST purchase-orders/{id}/receive and sales-orders/{id}/fulfill
Frontend:
- receive-purchase-order-form-panel: order summary, per-item receipt rows
with ordered/received/outstanding quantities, lot/expiry/notes inputs,
submits to API, calls onReceived callback on success
- fulfill-sales-order-form-panel: order summary, FEFO notice, per-item
fulfillment rows with ordered/fulfilled/outstanding quantities, notes
input, submits to API, calls onFulfilled callback on success
- context-panel.js: registered receiving intent for purchaseOrder and
fulfilling intent for salesOrder
- purchase-orders/index.js: receivePurchaseOrder() action, improved columns
- sales-orders/index.js: fulfillSalesOrder() action, improved columns
- translations/en-us.yaml: added receive and fulfill translation keys
…emplates
Both purchase-order-panel/items.hbs and sales-order-panel/items.hbs had
the {{#if (eq this.editingItem.id item.id)}} block incorrectly closed
with {{/each}} instead of {{/if}}, with the {{/each}} for the outer
each loop also missing. This caused a Babel build error:
'if doesn't match each - 28:23'
Fixed both files:
- {{/each}} on line 89 replaced with {{/if}}
- {{/each}} added on line 90 to correctly close the outer each loop
- extension.js: register 'pallet' dashboard via widgetService.registerDashboard()
and 8 widgets via widgetService.registerWidgets() using correct Widget +
ExtensionComponent pattern (Widget/ExtensionComponent from @fleetbase/ember-core/contracts)
Removed unused Hook import. Default widgets: inventory-summary, low-stock,
po-status, so-status, recent-activity. Optional: stock-value, expiring-stock,
top-products.
- templates/home.hbs: replaced bare {{outlet}} with <Dashboard> component using
@defaultDashboardId='pallet', @defaultDashboardName='Pallet Dashboard',
@extension='pallet' inside <Layout::Section::Body> with overflow scroll.
Frontend widget components (widget/ namespace):
- widget/inventory-summary: 5-KPI banner (SKUs, units, value, warehouses, low-stock)
- widget/low-stock: table of products at/below min_stock_level
- widget/po-status: 4-status badge grid + recent PO list
- widget/so-status: 4-status badge grid + recent SO list
- widget/recent-activity: scrollable audit trail feed with event icons
- widget/stock-value: horizontal bar chart of value per warehouse
- widget/expiring-stock: table of batches expiring within 30 days
- widget/top-products: ranked bar chart of most-moved products
Backend:
- MetricsController: 7 read-only endpoints (inventory-summary, low-stock,
po-status, so-status, stock-value, expiring-stock, top-products), all scoped
to session company_uuid
- routes.php: added metrics prefix group with all 7 endpoints under
fleetbase.protected middleware
PHP fatal error: 'Cannot declare class CreateOrderItemsTables, because the
name is already in use' was caused by a naming conflict with another package
that registers a migration class with the same name.
All 14 named-class migrations have been converted to the anonymous class
pattern (return new class extends Migration { ... };) which is the modern
Laravel standard and completely eliminates cross-package class name conflicts.
The 2024_11_06_create_wms_tables.php and 2024_11_07_create_order_items_tables.php
files were already using the anonymous pattern and were left unchanged.
pallet_purchase_order_items and pallet_sales_order_items were referencing 'pallet_products' and 'pallet_warehouses' which do not exist as standalone tables. The correct backing tables are: - pallet_products → 'entities' (Product extends FleetOps Entity) - pallet_warehouses → 'places' (Warehouse extends Fleetbase Place) The 'pallet_inventory' reference on sales_order_items is correct and unchanged. All other Pallet migrations already use the correct 'entities' and 'places' table names consistently.
pallet_sales_order_items referenced 'pallet_inventory' but the actual
table created by the inventory migration is 'pallet_inventories' (plural).
Fixed: ->on('pallet_inventory') → ->on('pallet_inventories')
…et_warehouses table - Create new pallet_warehouses migration with WMS-specific fields: code, type, status, capacity, current_utilization, floor_area_sqm, operating_hours, timezone, phone, email, manager_uuid, total_docks, is_active, is_default, meta, place_uuid (FK to places table) - Update 8 existing migrations to reference pallet_warehouses instead of places for warehouse_uuid foreign keys - Rewrite Warehouse PHP model to extend Model (not Place) with: - place_uuid belongsTo(Place) for geographic/address data - company(), createdBy(), manager() relationships - All WMS hasMany relationships preserved - getAddressAttribute() proxy to linked Place - getTotalInventoryValue() using entities table - Update WarehouseController to create/update linked Place from address fields on create/update operations - Rewrite WarehouseResource to proxy address fields from linked Place and include new WMS-specific fields (code, type, status, capacity, utilization_percentage, floor_area_sqm, operating_hours, etc.) - Update WarehouseFilter to remove type=pallet-warehouse constraint and add proper type/status/isActive filter methods - Update 9 Pallet models to use Warehouse class instead of FleetOps\Place for warehouse_uuid relationships: Inventory, WarehouseSection, WarehouseDock, WarehouseZone, BinLocation, CycleCount, StockTransfer, PickList, Wave, InventoryReservation - Rewrite Ember warehouse model to extend Model (not PlaceModel) with all WMS attributes, place belongsTo, and computed properties - Update warehouse serializer to embed place, sections, docks, zones - Update warehouse-form-panel with two content panels: 'Warehouse Details' (name, code, type, status, capacity, phone, email, is_active, is_default) and 'Address' (street, city, etc.) - Update warehouse/details.hbs to show new WMS fields (code, type, status, is_active) replacing the old is_3pl field - Expand translations with new warehouse field keys (code, type, status, is-active, is-default, email, phone, floor-area, timezone, total-bins, total-docks, total-zones, utilization) and add common.active/inactive keys
|
Hello @roncodes! Well done! |
- align ember-concurrency to ^4.0.6: committed pnpm-lock.yaml resolved 4.0.6 against specifier ^3.1.1, so 'pnpm install --frozen-lockfile' (the CI install step) failed on this branch; lockfile regenerated, frozen install verified - composer test:unit now self-heals a vendor -> server_vendor symlink: pest's binary resolves vendor/autoload.php relative to itself, so the renamed vendor-dir made 'composer test:unit' unrunnable as shipped - run pest with E_DEPRECATED suppressed so the EOL pest v1 stack boots on PHP 8.4 (CI's PHP 8.2 unaffected) - ignore /vendor (compatibility symlink) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔄 Completion loop — iteration 1 (discovery + toolchain repair)Landed: c33317b — the branch's dev toolchain was unrunnable and is now fixed:
Baselines measured (fresh, honest numbers):
Next: backend Testbench harness → stock-correctness defect fixes with regression tests (see findings comment below) → frontend boot fix → coverage rigs → CI overhaul. Full contract matrix and risk ledger maintained in the loop's working state; progress checklist in the PR description will stay current. |
🔎 Issues uncovered — discovery sweep (iteration 1)Consolidated findings from a full backend/frontend/tests/CI audit of this branch. Each will be fixed in this PR with a regression test, or explicitly flagged for a decision. Severity-ordered. Critical — stock correctness
High — schema/model integrity
Lifecycle gaps
Frontend defects
Process/infra
Items 1–9 are the current fix queue, in order. Fixes will reply to this comment with commit SHAs. |
- add orchestra/testbench ^8.36 (blocker: core-api controllers need Illuminate\Foundation, which only laravel/framework provides; no container existed for request()/session()/config() so the suite could not run at all) - upgrade pest ^1 -> ^2.33 + collision ^7 (matches core-api's own dev harness; the EOL pest 1 stack breaks on PHP 8.4 once Laravel's error handler re-enables deprecations) - server/tests/TestCase.php: Testbench app on a shared per-process SQLite file; the mysql connection aliases to it because core models pin $connection = 'mysql'; shims for core tables (companies, users, places, files, ...) whose real migrations are MySQL-only; runs Pallet's own migrations. MySQL-only contracts go to the MySQL lane. - server/tests/Pest.php binds the TestCase; runner passes --test-directory server/tests (pest resolves Pest.php from there) - rename Feature.php -> FeatureTest.php and tighten phpunit discovery to suffix Test.php (bare .php ingested Pest.php/TestCase.php as tests); migrate phpunit.xml.dist to the PHPUnit 10 schema - make migration up/down paths SQLite-safe: split multi-column drops into one call each, skip dropForeign on sqlite (identical behavior on MySQL); rollback now runs green every test teardown - pin runner flags: xdebug.mode=off (9x faster), memory_limit=512M, E_DEPRECATED suppressed composer test:unit: 18 passed (64 assertions) in ~30s — first green backend suite on this package. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
getDisplayNameAttribute() declares : string but fell through to public_id, which is null on unsaved/partial records — TypeError. Terminal fallback is now an empty string. Caught by the existing resource-contract test once the suite became runnable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three changes, on this working branch rather than a separate one as requested. Only these three files are staged — the in-progress addon edits are left alone. 1. release.yml delegates to the reusable workflow in fleetbase/fleetbase, which validates and pushes the tag when a dev-v* branch merges to main. The version is never retyped: it comes from the branch name, and the tag is refused unless composer.json, package.json, extension.json and RELEASE.md already agree with it. 2. create-release.yml is NEW. pallet was the only module without one, so tagging it published to npm and GitHub Packages and created no GitHub Release at all. Copied from the other modules, with body_path: RELEASE.md so the notes are the release body, matching fleetbase/fleetbase. 3. RELEASE.md is seeded, because the tag workflow refuses to release without notes whose first line names the version. Both placeholder markers must be replaced before a release will go through — that guard exists so the template itself can never be published as the release notes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Publish GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: ${{ github.ref_name }} | ||
| name: ${{ github.ref_name }} | ||
| body_path: RELEASE.md | ||
| generate_release_notes: true | ||
| draft: false | ||
| prerelease: false | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
…guard - add @ember/legacy-built-in-components ^0.4.2 (devDep): ember-engines 0.9's LinkToExternal extends @ember/routing LinkTo, which is not a constructible class on Ember 5.4 — the whole test suite failed at boot with 'Class extends value [object Object]'. With the package present (same peer resolution as fleetops and the console) ember-engines takes its legacy LinkComponent branch and the suite executes. - pallet-engine.css: split two multi-layer background shorthands into longhands — clean-css v3 (ember-engines css pipeline) crashes parsing 'gradient position / size, color' shorthand, which broke the production build. Identical rendering. - tests/test-helper.js: QUnit testTimeout 30s — a hung await previously wedged the suite for 90+ minutes with no output. Production build verified green; test suite now runs (failures are pre-existing placeholder tests, tracked for the coverage phase). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…port Three production defects, each with regression coverage: - StockTransfer::cancel() on an in_transit transfer only flipped status — stock deducted by ship() was permanently lost. Cancel now restores source-warehouse stock in a transaction with row locks and records a transfer_cancelled ledger entry + audit event. (StockTransferTest) - the stock ledger was unwritable and unreadable: recordStockTransaction() inserts transaction_date_at but the table never had the column (every ledger insert failed), and Inventory::transactions() joins on inventory_uuid which also didn't exist. Repair migration adds both plus a transaction_type index; StockTransaction gains real casts, relations, and HasPublicId; the writer now populates inventory_uuid. - Warehouse + WarehouseController imported Fleetbase\Models\Place, which doesn't exist (Place lives in Fleetbase\FleetOps\Models) — the place() relation and address accessors fatally errored whenever touched. Harness: TestCase now registers responsecache/activitylog providers (inert in tests) and applies core-api's class expansions (Str::humanize etc.) so model save paths run like production. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
composer test:lint now passes in check mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔄 Completion loop — replanned roadmap + first stock-correctness fixesRoadmap replanned per Ron's direction — new phase order: A functional + visual completion (console at localhost:4200 as ground truth) → B interactive warehouse layout designer → C user testing guide → D public consumable API (Fleetbase v1 conventions) → E Postman collection → F coverage/CI/codecov last. The PR checklist below will track these phases. Landed this iteration:
Backend suite: 21/21 green. Test harness now boots model save paths like production (responsecache/activitylog providers, core-api class expansions). Next: remaining stock-correctness queue — cycle-count atomic approve, PO receive locks, stock-adjustment lock, reservation error-in-txn rollback + locks, storefront single release/commit guards, inventory create transaction — then per-screen visual verification begins. Waiting on Ron (non-blocking): ① commit/push the WIP in the main checkout ( |
|
↳ Findings update (original findings comment): item 1 (transfer-cancel stock loss) and item 9 (unreadable stock ledger) are fixed in b0f7957 with regression tests. Newly uncovered beyond the original list, also fixed in the same commit: the ledger write path never worked (missing |
Stock-correctness fixes, each with regression tests:
- CycleCount::approve() applied per-item adjustments in independent
transactions; a mid-loop failure (reserved-stock guard) left earlier
adjustments committed with the count still 'completed'. The whole
approval — every adjustment plus the status change — is now one
transaction. CycleCountItem::recordCount() also now requires the parent
count to be in_progress.
- PurchaseOrderController::receive() read the PO line item and inventory
row without locks inside its transaction — concurrent receipts could
exceed outstanding_quantity. Both reads now lockForUpdate.
- StockAdjustmentController::resolveInventory() same unlocked
read-modify-write — now locked.
- InventoryReservation::release()/fulfill() checked status on the
in-memory instance only; a stale instance could release or commit
stock twice. Both now re-read the row under lock inside the
transaction. InventoryReservationController::createRecord() returned
an error response from inside its transaction closure (committing
instead of rolling back) — it now throws.
- reordered catch blocks in Warehouse/Inventory/SalesOrder/PurchaseOrder
controllers where catch \Exception preceded the specific handlers,
making them unreachable.
Test harness: the default DB connection is now the same connection NAME
models pin ('mysql') — connection names get separate PDO handles, so
DB::transaction() on a different default never covered model writes;
atomicity tests were silently meaningless before this.
Suite: 31 passed (110 assertions), stable across 3 consecutive runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🔄 Iteration update — stock-correctness queue 5/7 done
Also a notable harness fix: Fleetbase models pin the Next: storefront single release/commit + unlink transaction guards, inventory-create transaction — then the per-screen visual pass begins (still waiting on the main-checkout WIP commit + a one-time console login). |
…, link uniqueness - StorefrontInventoryController::unlink() saved the product and cleared variant links non-atomically — now one transaction - InventoryController::createRecord() created a Batch then an Inventory with no transaction — a failed inventory save orphaned the batch - storefront link uniqueness was PHP-only; unique indexes on pallet_products.storefront_product_uuid and pallet_product_variants.storefront_variant_uuid now backstop the race (single-id storefront release/commit and batch transitions were already covered by the locked in-transaction re-check added to InventoryReservation::release/fulfill) Suite: 34 passed (118 assertions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Stock-correctness queue complete (7/7)
All seven clusters from the findings audit are now fixed with regression tests: transfer-cancel stock loss, unwritable ledger, cycle-count atomicity, PO over-receive races, adjustment locks, reservation double-release/fulfill, and the link/create transaction gaps. Next: lifecycle gaps — wave release generating pick lists, picking actually moving stock, an expired-reservation sweep — then the sales-order customer-column repair and the per-screen visual pass. |
Pallet v0.0.2 working release — inventory & warehouse management extension.
Completion progress (auto-maintained)
Last updated: 2026-08-17 (iter 3) · phased roadmap per Ron's direction · autonomous completion loop
Phase A — Functional & visual completion (PRIORITY 1) — in progress
Phase B — Warehouse layout designer — not started
Phase C — User testing guide — not started
Phase D — Public consumable API (Fleetbase v1 conventions) — not started
Phase E — Postman collection (~/Development/fleetbase/postman) — not started
Phase F — 100% coverage · CI · codecov · README badge (LAST) — not started