# 17 – Automated walk through the full flow (local, new company "AUTO Test Company")

Method: a script sends real requests through the application (routes, login/permission checks, validation, controllers, pages) as each role: company admin, staff (Manager / Sales), customer, carrier. Captcha skipped for these tests only; email is not sent (array mailer), queued mail is checked instead. Company 809; accounts auto.company / auto.manager / auto.sales / auto.customer / auto.carrier @example.com, password Test@12345. Page scripts (select2, popups) are not covered by this method and are tested separately in the browser where needed.

## Step 0 – Registrations: PASSED
Company created through the superadmin route; customer and carrier registered through /register (captcha skipped); the same customer email twice in one company is refused with the message.

## Step 1 – Company Admin set-up: PASSED (A1 and A2 found and fixed locally)
Passed: Equipment (2), Mode Of Transport, Unit, Tax, Discount (type Percentage), Terms & Conditions, Customer Type, Carrier Type, Lead Source / Status, Followup Status, Quote Status, Expense Category, Vendor, Bank; all 5 ID series; currency rates (the page shows the company's country currency and USD = 1 as the base); empty name refused; "Delivered" tracing status refused; same bank account number refused inside the company. Roles Manager (All Data + notification view) and Sales (My Data); a duplicate role name and the name "Admin" are refused. Staff Auto Manager and Auto Sales created; duplicate staff email and a password under 8 characters refused; User List / Role List / Carrier List show them. Carrier edit: saving without the two documents is refused, with them it saves, the future insurance end dates are stored, and a second save without re-uploading keeps the documents. First look: Manager opens Notifications, Sales gets "forbidden", Sales cannot open the staff list, the customer cannot open admin pages, customer Settings shows the fixed Customer Types plus the company's "Shipper".

| # | Sev | Finding |
|---|-----|---------|
| A1 | ~~Medium~~ **Fixed locally 14 Oct (needs upload + migration)** | **Tracing Status names were unique across ALL companies** (database unique index on `tracing_status.name`): a company adding a status another company already has got an error page (HTTP 500). Rule now (owner's rule): the four statuses Dispatched, Pending, Delivered, Send To Invoicing are shared by everyone and nobody can edit them; a status a company adds belongs to that company only (visible to its admin and staff, never to other companies); the name is unique per company, a duplicate (any letter case, or equal to a shared status) gives a friendly message. Tested: 809 can add "Loaded" although 117 has one; admin, Manager and Sales of 809 see the shared four + 809's own; companies 471 and 218 see none of 809's; another company gets 404 renaming 809's status; nobody can rename a shared one; the Tracing page status list follows the same rule. |
| A2 | ~~Low~~ **Fixed locally 14 Oct (needs upload + migration)** | Same class of problem: **Vendor email, Custom Broker email / phone, Location email / phone / street** were checked across ALL companies, and **driver email** across ALL carriers (database index). Now unique per company (per carrier for drivers); a duplicate inside the company / carrier gives a friendly message. Tested for all four: company/carrier 809 adds it, another company/carrier can use the same value, the same one twice is refused, lists show only the company's own rows. |
| A3 | Question | HRM **staff code (`unique_code`)** is unique across all companies (`users.unique_code`). Same rule would make it per company. Waiting for the owner. |
| A4 | Question | The carrier's Update Tracing popup uses its own built-in status list (Pickup, Loaded, In Route, Border Crossed, Delivery Location, Delivered) and does not read the Tracing Status master, so a company's custom statuses are only available in the admin / staff popup. Should the carrier popup also offer the custom statuses of the company that owns the order? Waiting for the owner. |

Mistakes in my own first run (re-checked, not app problems): Discount needs the type "Percentage" / "Flat"; the series pages are /orderSeries and /quoteSeries; the currency page shows only the company's country currency and USD (base = 1).

## Step 2 – Staff, Customer and Carrier: first look and own set-up: PASSED, 3 findings
Passed: **Customer** – Settings opens; the Customer Credit section is view only (9 of 9 fields disabled); Customer Type shows the fixed types + the company's "Shipper" and NOT another company's custom type; the customer saves its own details; credit values (status, limit, terms, currency) are NOT changed even when the customer sends different ones; a short phone is refused; all 17 customer pages open; the customer cannot open admin / carrier pages (redirected). **Carrier** – adds a driver (empty name refused), edits it, Driver List shows it; another carrier cannot see, edit or delete it (404); adds a truck (empty form and "available to before available from" refused), Truck List shows it; another carrier cannot see or delete it; the company admin sees its own carrier's truck under Truck Availability and another company does not; all 23 carrier pages open; the carrier cannot open admin pages.

| # | Sev | Finding |
|---|-----|---------|
| B1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | **The carrier can change company-controlled fields about itself** in its own Settings: Approved (1 → 0), Rating (none → 5), Internal notes and Carrier Type were all saved. They are saved on the shared `carriers` record (not on the per-company record `company_user`), so a carrier that works with two companies would overwrite the same values for both – against the "custom data belongs to one company" rule. Should be view-only for the carrier (like the customer's credit section) and kept per company. |
| B2 | Low | **Carrier Type list on the carrier's Settings is hard-coded "Type 1 / Type 2"** (known CA7) – not the shared + the company's own Carrier Types (the customer's page was fixed this way). |
| B3 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | **Route `carrier/view_Driver` always gives an error page (HTTP 500)**: the route has no `{id}` but the controller needs one. Nothing links to it (dead route), so customers will not meet it unless the address is typed. |

Test note: my first carrier-settings attempt returned HTTP 500 only because my test left out a field the real form always sends (`advertise`); re-tested with the full form.

## Step 3 – Admin / Staff: lead → follow-up → customer: PASSED, 3 findings
Passed: add / edit lead; empty name, duplicate email, duplicate phone and a short phone are refused with messages; **another company can hold the same email / phone** and cannot open, assign, read or delete company 809's lead (404); follow-up saved with its product, and a follow-up missing status / dates / equipment / type gets a clear message; Follow Up Leads, Missed Followups, Quotation Leads open; assign to a staff user works, assigning to a user of another company is refused; convert to customer gives the **new Customer ID series (AUTO-CUS-0001)**; add a customer directly with the credit section (duplicate email refused, credit limit saved); **Sales (My Data) cannot open or delete a lead / customer not assigned to him** while the Manager (All Data) can; bulk upload imports the valid rows, never creates a duplicate lead, and creates the "pending" lead status for the uploading company only.

| # | Sev | Finding |
|---|-----|---------|
| C1 | **Medium** (isolation) | **`lead/followup/details/{id}` has no My Data check**: a staff user on My Data (Sales) can read the follow-up details of a lead that is NOT assigned to him (name, email, phone, follow-ups returned, HTTP 200). The same page for a lead of **another company** gives an error page (HTTP 500, "Attempt to read property on null") instead of "not found" – no data leaks (queries are company-scoped) but it is unguarded code. |
| C2 | Medium | **Bulk upload hides skipped rows**: a file containing a row whose email already exists is answered with "Multiple leads imported successfully!"; the duplicate row is skipped silently and the user is never told which row or why (the code that would show "row N …" is never reached). |
| C3 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | `CrmController::saveFollowup` still has a debug `dd($e->getMessage())` in its error handler: if saving a follow-up ever fails, the user sees a raw dump page instead of a message. (Found by reading the code.) |

## Step 4 – Customer: requests a shipment (RFQ) and an order request: 3 findings (1 High)
Passed: the customer's RFQ saves for the right company (created_by 809, the customer's lead, rfq_by = the customer) with its row, pickup and delivery; the customer's RFQ List shows it and a customer of **another company** does not; the company admin's RFQ List and the Manager (All Data) see it, **Sales (My Data) does not** (customer not assigned to him) and another company's admin does not; another customer gets 404 trying to approve it; "Update Quote" opens for the admin and is 404 for another company and for Sales; the admin gets an "RFQ Requested" notification. Order request (without a charge): saved as a pending order of company 809 with the customer's lead; the customer's RFQ Order List shows it; the admin's and the Manager's Order RFQ List show it, Sales and another company do not; another customer cannot approve it; the normal Order List does not list it before it is approved.

| # | Sev | Finding |
|---|-----|---------|
| D1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | **The customer's "Create RFQ Order" crashes (HTTP 500) as soon as a charge is entered**: `CustomerController` uses `LoadOrderCharge` but never imports it (`use App\Models\LoadOrderCharge;` is missing, `LoadOrderDiscount` is imported). Nothing is saved (the transaction is rolled back) and the customer sees an error page. The form has an "Add Charge" section. |
| D2 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | **The customer's order request accepts a duplicate order number**: the same order number sent twice created two orders (the admin's own Create Order enforces one number per company). |
| D3 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | **A customer RFQ with missing data shows a technical message** (`Undefined array key "customer_type"`) – the RFQ save has no server-side validation; an empty submit gives the same text. |
| D4 | Info | Saving an RFQ also creates **Location master records** for the company from the pickup / delivery addresses the customer typed (2 new locations here): customer input writes into the company's master data. |

(My test data: the accidental second order of the duplicate test was renamed AUTO-REQ-001-DUP.)

## Step 5 – Admin: quotation, Approved list, Load Board, Bid Limit, Publish: 6 findings
Passed: Update Quote saves price + Admin Approved and sends the admin to the right list (without the tick back to the RFQ List, with it to the Quotation List); Sales (My Data) and another company cannot update the RFQ of an unassigned / foreign customer (404); status "Waiting for approval" → "Approved" moves the quote from the Quotation List to the Approved Quotation List; status history opens; Quote PDF opens for the company and is 404 for another company; Send Email answers success (mail not sent locally); the customer sees the price, approves it, gets "Quotation Approved" and "Quotation Published" notifications, and another company's customer sees nothing; Create Quotation pre-fills the number from the company Quote ID series (AUTO-QUO-…), saves, is visible to Sales (customer assigned to him) and not to another company; Send to Load Board, Bid Limit (3 saved), Publish (5 days → published until +5 days) and Unpublish work; another company / Sales (My Data) get 404 on every one of these actions; the Load Board List of another company does not show them.

| # | Sev | Finding |
|---|-----|---------|
| E1 | **Medium** | **Changing the status of an admin-approved RFQ from the Quotation List (or the Approved Quotation List) removes its admin approval**: those two status pop-ups do not send `is_admin_approved`, and the server sets it to 0 whenever it is missing. The RFQ then disappears from the Quotation List and the Approved Quotation List (they only show approved RFQs) and is back only in the RFQ List. (The RFQ List pop-up sends the field correctly.) |
| E2 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | **A duplicate quotation number is accepted**: the same number sent twice created two quotations (the order screen enforces one number per company, the quotation screen does not). |
| E3 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | **Quotation save without server-side validation**: a quotation without rows gives an error page (HTTP 500, `Undefined array key "rows"`); the Update Quote form also needs a specific shape for shippings (a different one from Create) – a wrong shape gives HTTP 500. |
| E4 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | **Bid Limit and Publish days are not validated**: a negative Bid Limit (-5) is saved, text ("abc") gives an error page (HTTP 500, database error); publishing for -3 days is accepted and shows "published" with an expiry date in the past. |
| F1 | ~~Medium~~ **DONE locally 15 Oct (needs upload)** | **ID series numbers are checked against ALL companies**: in `LeadIdService`, `CustomerIdService`, `OrderIdService` and `QuoteIdService` the "is this number already used?" loop searches every company (only `CarrierIdService` filters by company). Two companies with the same prefix interfere: company B's next lead / customer / order / quote number silently skips numbers company A already uses (gaps, and numbering of one company depends on another). Numbers should only have to be unique inside the company. |
| F2 | ~~Low~~ **DONE locally 15 Oct (needs upload)** | The ID series text is parsed as "letters + separator + digits" (e.g. `OR-002`); a series like `AUTO-QUO-000` is not understood and the next number comes out wrong (`AUTO-QUO-0001` → `AUTO-QUO-00011`). The Master page accepts any text for the series without checking the format. |

## Step 6 – Carrier: quotes on the load (Load Board, bids, bid limit): 2 findings
Passed: carriers of company 809 see the published loads; a carrier of **another company** sees none and gets 404 trying to bid; a bid is refused when empty, negative, text, or above the customer price (the message does **not** show the price); a valid bid is saved as pending; a second bid on the same load is refused with a message; the Bid Limit works (load with limit 2: carrier 1 and 2 bid, carrier 3 is told "Bid limit reached"); a carrier edits its own bid (above the price refused) and cannot edit, delete or see another carrier's bids; an expired load disappears from the carrier's Load Board; the admin and the Manager see all bids with carrier names, **Sales (My Data) sees the bids only for loads of HIS customers** (404 for the others), another company gets 404, the customer cannot open the bids and the customer's load pages show no carrier names or bid amounts.

| # | Sev | Finding |
|---|-----|---------|
| G1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | **A bid is accepted on a load that is NOT published or whose publish period has ENDED.** The server only checks "is on the Load Board and belongs to a company the carrier works with" (`DataScope::biddableQuotation`); it never checks `load_status` or `published_until`. The carrier's Load Board hides such loads, so it needs a direct request – but a carrier can bid on any old or withdrawn load of its companies. |
| G2 | **Medium** (carrier must not see the customer's price) | **The customer's final price is still in the carrier's Load Board page source**: in every row's `data-details` JSON (`final_price`) and in the bid field's `max="1200.00"` (the visible pop-up line was hidden earlier; this data was not). A carrier can read the customer's price by viewing the page source. The price rule is needed for the bid limit, but the number does not have to be sent to the browser. |

(Test notes: "1100" found on the customer's load page was a pin-code, not a bid – no leak there. Extra carriers auto.carrier2 / auto.carrier3 were registered in company 809 for the bid-limit test; carrier 3's two test bids were removed.)

## Step 7 – Admin: approves the bid and creates the order: 4 findings (2 High)
Passed: approve / disapprove a bid (the carrier gets "Bid Approved"); Sales (My Data) can approve only bids on loads of HIS customers, another company and the carrier itself cannot approve (404 / redirected); Approved Bids lists them for the admin, only the right ones for Sales, none for another company; the carrier's Closed Bids shows only its own; Create Order opens prefilled from the load (404 for another company and for Sales on an unassigned customer), the order number is pre-filled from the company series and moves on, both orders are saved for company 809 with the right customer, a duplicate order number is refused with a message; Order List / Pending Orders show them to the admin, Manager (All Data) sees both, **Sales sees only the order of his customer**, another company sees none, **the registered customer sees only its own order**; edit, Repeat Order and the Dispatch page open; another company and Sales get 404 on the unassigned order.

| # | Sev | Finding |
|---|-----|---------|
| H1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | **A carrier can still EDIT and DELETE its bid after the admin APPROVED it**: the approved bid of 1100 was changed to 100 and then deleted by the carrier (HTTP 302, "Bid updated / deleted successfully"). The agreed price can be changed or the bid removed without the company knowing. Approved bids must be locked for the carrier. (My test data was restored afterwards.) |
| H2 | **Medium** | **A load keeps accepting NEW bids after a bid was approved and the order was created**: the load stays on the carrier Load Board and a new bid was saved. A load that is awarded / ordered should leave the Load Board (or be closed for bids). |
| H3 | ~~Low / question~~ **DONE locally 14 Oct (owner: restrict; needs upload)** | **Two bids on the same load can both be approved** (no "one winning bid" rule). Confirm if that is intended (e.g. several trucks). |
| H4 | Low | An order saved without rows gives an error page (HTTP 500) – same "no server validation" pattern as D3 / E3 (rows are only checked by the page script). |

---

## Step 8 – Admin dispatches the order to the carrier (test company 809)

Script: scratchpad/step8.php. Orders: order1 (customer without a Sales owner) → carrier 1, order2 (Sales' own customer) → carrier 2.

**PASSED (about 40 checks):**
- Dispatch by Admin and by Sales (own customer) saves carrier, rate, equipment, driver name; order status becomes "dispatched"; carrier gets an "Order Assigned" notification; dispatch email is queued.
- Sales cannot dispatch an unassigned customer's order (404); another company cannot dispatch (404).
- Dispatch List shows the order; Pending Orders no longer lists it.
- Each carrier sees only its own order; a carrier of another company sees none.
- Carrier pages (Order List, Running, Tracking, Reports, Pending, Delivered, Paid, Driver List, Dashboard) show NO customer email, phone, name, company, PO or customer price.
- Assign Driver popup is pre-filled with what the company typed; carrier can save driver/truck; another carrier gets 404 on both read and save.
- Carrier gets 404 on Order PDF and Customer Invoice PDF; Rate Confirmation and BOL (no customer PO / contact) open for the right carrier only; other carrier / other company get 404.
- Customer pages show NO carrier name, email, phone, driver, truck or trailer; customer's Order PDF shows no carrier details.

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| I1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | The server accepts a dispatch to a carrier that belongs to ANOTHER company (carrier id 65 was accepted: "Order dispatched successfully"). `dispatchStore` does not check that the carrier is linked to / approved by this company. Only the dropdown on the screen limits the choice. |
| I2 | ~~High~~ **DONE locally 14 Oct (needs upload)** | The server accepts a dispatch to a carrier whose insurance (liability/cargo) has EXPIRED. The check exists only in the page JavaScript; a direct request or a changed form bypasses it. |
| I3 | Medium | Dispatching an order that is already dispatched (without `dispatch_id`) creates a SECOND dispatch row for the same order (order1 had 2 dispatches). The carrier list / reports can then show it twice and invoices may double up. |
| I4 | ~~High~~ **DONE locally 14 Oct (needs upload)** | The customer can open the **Carrier Rate Confirmation** PDF (`/load-order/rate-config/pdf/{id}`) of his own order. It shows the carrier name, carrier phone and the **carrier rate (USD 1100)** – the company's cost, so the customer can see the margin. Only the carrier, company and staff should open it. |
| I5 | ~~Question~~ **DONE locally 14 Oct (owner: hide carrier details from the customer's BOL; needs upload)** | Customer's BOL shows "Carrier Name: Auto Carrier Transport". A BOL normally names the carrier, but the rule so far is "customer must not see carrier details". Confirm whether the BOL is allowed to name the carrier. |
| I6 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | Rate Confirmation "Driver Contact" shows the carrier's company phone (9820000004), not the driver phone entered (9872222222). |
| I7 | Low | Dispatch with missing fields (no carrier, or no charges/discounts rows) crashes with HTTP 500 (`Undefined array key "carrier"/"charges"`). Worse, when `charges` is missing the dispatch row is already created: the order stays "pending", no driver/tax/notification is saved, and the Rate Confirmation then crashes with `Attempt to read property "gst" on null` for that order. The real form always sends charges, so it is only reachable by a direct request, but there is no validation (DispatchRequest has empty rules) and no transaction. |
| I8 | Info | "Ordered By" (a typed name, not the customer's name) is still visible on carrier pages (Order List, Running, Tracking, Order Report, Pending). Earlier decision: hide it if it is a customer name – confirm for these pages. |

Company-scope check: carrier lists, drivers, PDFs and dispatches are separated per company/carrier, except I1 (server accepts another company's carrier) and I4.

---

## Step 9 – Carrier delivers: tracking, POD, Send Email tick (test company 809)

Script: scratchpad/step9.php (+ s9b.php re-check of the lists).

**PASSED (about 35 checks):**
- Carrier saves tracing with the company's own custom status ("Loaded"); admin saves with "Loaded2"; Sales (own customer) and Manager (All Data) can update; Sales gets 404 on an unassigned customer's order.
- "Send Email" NOT ticked → no email queued; ticked → one email queued, message "Email sent to the customer."
- Another carrier, a carrier of another company, another company's admin, and the customer cannot post tracing or read the tracing history (404 / redirect).
- Carrier "Delivered" + POD → order becomes "delivered", POD saved with the dispatch. Another company, another carrier and Sales (unassigned) get 404 on the POD page. 6 MB POD refused (limit 5 MB). Admin Upload POD without a file shows a message. Another company cannot upload a POD.
- After delivery the order is listed in Delivered Orders for admin, carrier and customer (and not for Sales-unassigned, another carrier, another company). Customer pages show no carrier details.

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| J1 | **High** | **Any file type is accepted as POD** by Update Tracing (`pod` rule is only `file|max:5120`). A `.php` file and an `.html` file were saved under `public/asset/pods/` with their original names (e.g. `1791970279_auto_evil.php`). If the server executes PHP in that folder, a carrier could run its own code on the server; an `.html` file can run scripts on your domain. Test files were deleted. Should accept only pdf/jpg/png and store with a safe random name. |
| J2 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | Admin "Upload POD" (`/load-order/store-pod`) says "Please upload a PDF" but accepts ANY file (a `.txt` was saved and the order moved to "Invoice Not Sent"). No type or size rule. |
| J3 | ~~High~~ **DONE locally 14 Oct (needs upload)** | The customer can open the tracing history page (`/load-order/quick-tracing/show/{order}`) of his own order, and the page data contains the carrier's **name, tracing email and phone** (query joins `carriers.legal_name`, `tracing_email`, `primary_phone`). Breaks "customer must not see carrier details". |
| J4 | Medium | A tracing status that does not exist for the company ("Totally Invented Status") is saved and written to the order. The popup dropdown limits it, but the server does not check against Static + this company's custom statuses. |
| J5 | Medium | A POD file is publicly reachable by its URL (`/asset/pods/<time>_<name>.pdf`) with no login. The name is guessable (timestamp + original name). Customer/carrier documents should be served through a checked route. |
| J6 | ~~Medium~~ **DONE locally 14 Oct (owner: restrict; needs upload)** | An order that is already **delivered** goes back to the new status if a later tracing update with another status is saved (order_status becomes e.g. "Loaded" and drops out of Delivered/Invoice lists). This is the old "reopen" logic; confirm it is wanted, and who may do it (a carrier can do it after delivery and after the POD). |
| J7 | Low | Update Tracing validation errors (e.g. missing status) come back as a red "error" message text instead of normal field errors. Works, only the look differs. |
| J8 | Info | No admin notification is created when the carrier marks the order Delivered or uploads a POD (admin notifications in the last 10 minutes: none). Confirm whether admin should be notified. |
| J9 | Info | Carrier "Running Orders" still lists an order after it is Delivered. Confirm if intended. |

Company-scope check: tracing statuses offered = static 4 + own company's custom ones (the server does not enforce this, J4). Documents/pages are separated per company and carrier, except J3 and J5.

---

## Step 10 – Customer invoice and payment (test company 809)

Script: scratchpad/step10.php (run twice; first run used a fake POD file that could not be merged – test artifact, corrected with a real PDF). Mail was switched to the "array" driver for this test only.

**PASSED (about 60 checks):**
- Invoice Not Sent shows the delivered order with POD for admin and Manager; not for another company; Sales (My Data) does not see the unassigned customer's order.
- Create Invoice window: pre-filled data and customer email; empty invoice number / bad date refused; another company gets 404; carrier and customer redirected away; preparing twice never makes a second invoice; invoice has the order price and chosen number.
- Preview PDF opens for admin only; send refuses no recipient, bad address, message over 2000 characters; duplicate address is merged; a successful send moves the order to Accounts Receivable (status "invoiced"); a second send is refused.
- Customer sees his invoice and can open his invoice PDF; another customer, another company, the carrier and Sales-unassigned get 404; customer invoice pages show no carrier details.
- Payments: empty / negative / text amount, missing date, missing mode and an amount above the balance are all refused with a clear message; another company, Sales-unassigned, customer and carrier cannot pay; part payment (400) → "partially-paid"; over-balance refused (900 > 800); balance paid by cheque → "paid"; cheque starts as "Issued"; nothing more can be paid on a paid invoice; payment history readable only by the right people.
- After payment: Paid Invoices (admin, listed by invoice number), customer Paid list and Paid Orders show it; Sales and other company do not.

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| K1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | Sales (My Data) can **create an invoice for a customer's order that is not assigned to him**. `ManualInvoiceController` checks only the company (`created_by`), not My Data. The invoice row is created and the order status is changed to "Invoice Not Sent" BEFORE the later PDF step returns 404, so the screen shows an error but the change is saved. (Test row removed.) |
| K2 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | Same cause: Sales (My Data) gets HTTP 200 on the Create Invoice window of an unassigned customer's order and sees the customer name, email and price (`details`). Preview / send use the same company-only check. |
| K3 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | The notification "Invoice Payment Received" is saved with `user_id = NULL` for a customer, but the customer bell only reads rows with the customer's own `user_id`, so **no customer ever receives it** (4 rows saved, 0 deliverable). It also stores `admin_id` only, so it cannot be targeted. Fix = save the invoice's customer user id. |
| K4 | Medium | The public invoice link `/global/customer-invoice/pdf/{order id}/{admin id}` opens the invoice PDF **without login**; ids are small running numbers, so a neighbouring order id (63182) also opened (200). Only a wrong admin id returns 404. Anyone who knows or guesses ids can read customer invoices (name, address, price). Needs a signed / random token. |
| K5 | ~~Low~~ **DONE locally 14 Oct (needs upload)** | A payment of 0 is accepted (`min:0`) and creates an empty payment row. |
| K6 | Low | A payment dated 2 years in the future is accepted (no date limit). |
| K7 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | `Delete invoice` (`GET /customer-invoice/delete/{id}`) is a GET link: it permanently deletes all payments of the invoice and resets the order, with no permission check (staff without `invoice_delete` can still call it) and no confirmation on the server. A GET can be triggered by an email link or image. The name also says "delete" but the invoice row is kept. (Read from code; not run to keep test data.) |
| K8 | Info | Manager (All Data) sees all companies' orders of his company (correct). Payments page after save redirects back to the previous page (works). |

Company-scope check: invoice numbers and invoice lists are per company; payments, receivables and PDFs are separated except K1/K2/K4. Invoice series ("Invoice ID") was already reported as F1.

---

## Step 11 – Carrier invoice and payment to the carrier (test company 809)

Script: scratchpad/step11.php.

**PASSED (about 50 checks):**
- Carrier Invoice Not Received list: admin sees the delivered order; Sales-unassigned and another company do not; carrier is redirected away.
- Entering the carrier's invoice: empty number, negative / text amount and bad date refused; another company 404; Sales-unassigned 404; carrier cannot use the company route; valid invoice saved and carrier status becomes "invoiced"; the order moves to Carrier Invoice Received (not visible to Sales-unassigned / other company).
- Carrier sees his own invoice; carrier invoice pages (list, paid list, reports, paid orders) contain no customer email, phone, name, PO or customer price; carrier 2 / another company's carrier / the customer do not see it.
- Payments to the carrier: empty / negative / text amount, missing date / mode and over-amount refused; another company, Sales-unassigned, carrier and customer cannot pay; part payment → "partially-paid"; over-balance refused; payment history readable only by the right people; edit payment works and over-amount edit is refused; another company / the carrier cannot edit or delete; admin delete returns status to "invoiced"; two payments (bank + cheque) → "paid"; nothing more can be paid.
- After payment: carrier Paid Invoices / Paid Orders, admin Carrier Paid list, Payable Age; Sales and other company see nothing.

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| L1 | ~~Medium~~ **DONE locally 14 Oct (needs upload)** | Entering a carrier invoice with a blank **Payment Type** crashes with HTTP 500 (`Column 'payment_type' cannot be null`). The select starts blank and has no server rule. No validation for `payment_type`, `discount_type`, `discount_amount`, `final_amount`. |
| L2 | **High** | `final_amount` is calculated only in the browser and trusted by the server: a carrier invoice for 1100 was saved with `final_amount = 99999` (this is the amount that is then paid and reported). The server should recalculate the final amount from amount − discount. |
| L3 | Medium | A second carrier invoice for the SAME order can be created (carrier invoices on the order: 2 after two saves). Customer invoices correctly refuse a duplicate; carrier ones do not. Reports/payable would double count. (Extra test row removed.) |
| L4 | Medium | "Invoice Payment Received" notification to the carrier is saved with `user_id = NULL` in real use: the hidden field `carrier_id_pay` in the payment form has no `name`, so it is never submitted (my test passed it by hand and then it worked). The carrier never gets the bell for payments. Same family as K3 (customer). |
| L5 | Low | A carrier payment of 0 is accepted even on a fully paid invoice (`min:0`). |
| L6 | Info | The carrier payment route never checks the payment amount against the carrier rate on the order; only against the invoice amount (see L2). |

Company-scope check: carrier invoice numbers, factoring companies and bank lists come from the company's own masters plus static ones; invoice/payment data is separated per company/carrier. Invoice number of carrier invoices is free text and not unique per company (`CINV-AUTO-1` could be entered twice if L3 is open).

---

## Step 12 – Expenses (test company 809)

Scripts: scratchpad/step12.php, step12b.php (payments re-run), small helpers. Expense categories are company data only (there is no static list); vendors are company data.

**PASSED (about 45 checks):**
- Category: empty name refused; saved for the company only; another company does not see, rename or delete it; the same name may exist in two companies.
- General expense: empty name / category, negative and text amount refused; saved with a unique id (EX-xxxxx); same invoice number twice refused in a company but allowed in another company; PDF receipt saved; carrier and customer cannot add; admin list shows it, other company does not; other company gets 404 on edit / update / delete; edit amount works, negative edit refused.
- Vendor expense: empty vendor / negative amount refused; saved; same vendor + same invoice number refused; other company 404 on list / edit / delete.
- Payments: another company 404 (pay, delete, slip); Sales (My Data) gets 404 on an expense he did not enter; 100 → "partially-paid"; balance by cheque → "paid"; deleting a payment moves status back; Paid list and Expense Report only show own company; slip PDF opens (needs `pdf_type`).

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| M1 | Medium | An expense can be saved with an **Expense Category of another company** (id 13 accepted) and a **Vendor of another company** (id 9 accepted): `category` and `vendor` are only `required`, not checked to belong to this company. The other company's category / vendor name then shows in this company's lists (data leak across companies). |
| M2 | ~~Medium~~ **DONE locally 14 Oct (type only; size to be decided; needs upload)** | Receipt upload on Add Expense, Edit Expense and Add Vendor Expense accepts **any file type and any size** (a `.php` file and a 6 MB file were saved to `storage/app/public/receipts`; the file gets a random name so it is less risky than J1, but an HTML/SVG receipt can run scripts when opened). Only Edit Vendor Expense has `mimes:jpg,jpeg,png,pdf|max:2048`. |
| M3 | ~~High~~ **DONE locally 14 Oct (needs upload)** | **Expense payment has no validation** (`/expense-pay`): a NEGATIVE amount is saved; an amount above the expense (9999 on a 300 expense) is saved and the expense still shows paid; a second payment above the balance is saved. Customer/carrier invoice payments already have these checks. |
| M4 | ~~Medium~~ **DONE locally 14 Oct with M3 (needs upload)** | The same payment form with a text amount, missing date or missing mode shows a **raw SQL error** to the user ("SQLSTATE… Incorrect decimal value… insert into `expense_payments` …") which exposes table and column names. Should be normal field messages. |
| M5 | Medium | An invalid expense date (`not a date`) crashes with HTTP 500 (date has no rule; the field is not in `validate()`). |
| M6 | Low | The same Expense Category name can be added twice in the same company (no uniqueness), unlike Tracing Status / Vendor / Custom Broker / Location which are now unique per company. |
| M7 | Low | The expense payment slip (`/expense-slip/pdf/{ids}`) crashes with HTTP 500 (`Undefined variable $pdf`) when the `pdf_type` parameter is missing; no default. The screen always sends it. |
| M8 | Info | `GET /expenses/{id}` and `GET /Vendor_expenses/{id}` delete an expense by a GET link (same pattern as K7). |

Company-scope check: category / vendor lists, expense list, paid list, report and slips are separated per company; M1 is the gap (dropdown is limited on screen, server is not).

---

## Step 13 – Reports (test company 809)

Scripts: scratchpad/step13.php (56 admin report URLs × 6 users), s13b–s13g (Sales scope, PDFs, customer / carrier reports).

**PASSED:**
- All 56 admin-side report URLs (old `Xxx_Report` set, `report/…` set, `reports/…` set incl. Excel / PDF links) were opened as company admin, another company's admin, Sales (My Data), Manager (All Data), carrier and customer. Admin / Manager: 200 for all except the ones below. Carrier and customer are redirected away from every admin report (302).
- **No cross-company leak**: another company's admin sees none of our test data (order, invoices, expense, customer, carrier, payment refs) on any report or PDF.
- **Sales (My Data)** sees only his own customer's rows: the unassigned customer's order (AUTO-REQ-001-DUP1) and invoice (INV-AUTO-1) are absent from Load Order, Accounts Receivable, All Revenue, Received Payment, Customer Invoice, Profitability, Master, Claims, Cheque, Quotation, Order and Invoice reports; company-wide reports (Expense, GST, Accounts Payable, Company / Employee Performance, Profit & Loss, Financial Year, User) correctly return 403 for Sales.
- Customer Invoice Report shows the customer's own orders and no carrier details; another customer sees none of it.
- Carrier reports (Order, Invoice, Received Payment, Driver, Expense, Load Request, Bids) show only the carrier's own data, no customer name / email / phone / PO / price in the visible rows; carrier 2 and a carrier of another company see none of carrier 1's data.

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| N1 | **High** | **Admin → User Report (`/report/user-report`) always fails with HTTP 500** for admin and Manager. The code calls `Http::get('https://quickchartz.io')` (a wrong / unreachable domain, left from a test) and then `->throw()`, so the report can never be produced when that site cannot be reached. The real chart service is `quickchart.io`, and the request is not even used by the PDF. |
| N2 | Medium | **Customer portal → Order Report is broken.** The route `customer/Order-report` points to `ReportController@OrderReport`, which PHP resolves (method names are case-insensitive) to the ADMIN `orderReport()` (company-wide report). For a customer it returns a PDF with "No orders found" and the company header, although the customer has delivered / paid orders. The intended customer method is spelled `OrderReportld` (typo) and is never used. Not a data leak (empty), but the report is useless. |
| N3 | Medium | The carrier **Bids Report** page source still contains the customer's price as `max="1200.00"` in the edit-bid form (same cause as G2). |
| N4 | Low | Carrier Bids Report (and other carrier reports using `createFromFormat('M d, Y')`) crash with HTTP 500 ("A textual month could not be found") when `start_date` / `end_date` come in any other format (e.g. `2026-01-01`). The screen sends the right format; a manual URL or bookmark breaks it. |
| N5 | Info | `reports/employee-performance-employee`, `financial-year-excel/pdf`, `profit-loss-drilldown` answer 404 without their parameters (expected, parameters not tested). |
| N6 | Info | Report totals were NOT matched against hand-calculated values in this run (only presence / scope / crashes were checked); totals should be reviewed manually for order 63181: customer price 1200, carrier cost 1100, margin 100, expense 300 (paid 120/50). |

Company-scope check: reports use the company id + My Data scoping consistently; the shared masters (currencies, tax, statuses) appear as static + own-company data.

---

## Step 14 – Isolation check (company 809)

Scripts: step14a.php (id sweep), step14b.php (page crawl), plus the per-step "other company / Sales / other carrier" checks of Steps 1–13.

**Method 1 – id sweep:** every GET route that has an id in the address (70 routes, destructive ones skipped) was opened with OUR order, quotation, dispatch, invoice, customer, carrier and expense ids as: another company's admin, another company's customer, another company's carrier, carrier 2 (same company, different carrier) and Sales (My Data). 2,450 requests. **Only 2 pages answered with our data** (both already reported): `/customer-invoice/manual/{order}/details` for Sales (K2) and `/lead/followup/details/{lead}` for Sales (C1). Everything else answered 404 / 403 / redirect.

**Method 2 – crawl of every list page (243 pages, no parameters):** opened as Sales (My Data), a brand-new customer (id 817), a brand-new carrier (id 818), another company's admin / customer / carrier. **No page showed another party's data** (order, invoice, customer/carrier name, email, phone, PO, expense, driver). The brand-new customer opens 35 pages and the brand-new carrier 60 – all empty of other people's data.

**Staff on My Data (Sales):** dashboard, bell, Order/Invoice/Payment/Carrier-invoice/Expense/Paid/Delivered/Tracking/Claim/Cancel lists and the reports show only his own customers; the unassigned customer's order is correctly missing everywhere, except the gaps K1 (create invoice), K2 (invoice window), C1 (follow-up details).

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| O1 | Low | Pages that answer HTTP 500 instead of a normal "not allowed / not found": (a) wrong-role pages: `/carrier-profile` for admin / staff / customer ("property on null"), `/Customer_rfq/add` and `/Customer_order_rfq/add` for admin / staff / carrier, `/Customer_invocies/list` and `/paid-list` for the admin; (b) dead links: `/vendors` (controller method `getVendors` does not exist), `/view_Vendor_Expense` (method is spelled `view_Vender_Expense`), `/carrier/view_Driver` (B3), `/report/user-report` (N1). (Pages that need parameters, e.g. multi-pay and cheque-print, also 500 when opened bare – ignored.) |
| O2 | Info | Destructive GET routes (delete invoice / expense / vendor expense etc.) were NOT swept to keep test data; their guard (`DataScope`) was checked one by one in Steps 10–12 (404 for other company). |
| O3 | Info | Sales gets 200 on `/customer-invoice/manual/{id}/details` and `/lead/followup/details/{id}` (K2, C1) – the only leaks found. |

---

## Step 15 – Notifications, Mass Email, cancel / repeat / claim, settings (company 809)

Scripts: step15.php, s15b/s15c (re-checks and restore of admin password / profile).

**PASSED (about 60 checks):**
- **Notifications list:** admin sees the company's notifications (own + the ones sent to carrier and customers); filters For / Type / search / dates work (empty result shows "No notifications"); bad filter values do not crash; another company's list shows none of ours; Manager (has permission) opens it; Sales (no permission) gets 403; carrier and customer are redirected.
- **Bell:** carrier / customer bells load; another carrier or another company cannot mark my notification as read (404); the owner can; another customer's bell shows none of ours; Sales' bell lists only his own customers' orders.
- **Mass Email:** admin opens; carrier / customer / Sales (no permission) blocked; empty subject / body / group and an invalid group refused; a campaign to the staff group queued with exactly this company's 2 staff addresses, one queued job each, script in the subject is escaped; another company cannot open the campaign or its status; the customer group would send to 4 customers (this company only). (Queued jobs removed so nothing can be mailed from the test.)
- **Cancel / restore / repeat / claim:** another company, Sales-unassigned, the carrier and the customer cannot cancel; admin cancels the dispatched order → appears in Cancelled Orders (admin, Sales for his customer; not another company); cancelling twice shows "already cancelled"; another company cannot restore / repeat / claim / reset; restore returns a dispatched order to "dispatched"; Repeat Order opens (admin only); a claim moves the order to "claim" and shows in Claim / Dispute List. The customer sees a cancelled order as cancelled.
- **Settings / profile:** admin / customer / carrier Profile, Agreement and General Setting pages open; a carrier cannot open the customer agreement page and vice versa; empty username refused; a foreign phone number with + and brackets accepted; Change Password page opens.

**FINDINGS (not fixed):**

| ID | Severity | Finding |
|---|---|---|
| P1 | ~~High~~ **DONE locally 14 Oct (needs upload)** | **Any order can be cancelled, even a delivered, invoiced and PAID one.** `cancelOrder` has no status rule and no reason is required. The customer then sees it as cancelled; **Restore** sets the order to "dispatched" (not back to "paid"), while the invoice stays paid, so the order shows wrong status everywhere. (Order 63181 was cancelled and put back to paid by the test.) |
| P2 | Medium | When a DISPATCHED order is cancelled, the **carrier is not told** (no notification) and the carrier still lists it in Running Orders. |
| P3 | Medium | **Claim Reset** puts the order back to "pending" but the dispatch stays (the code looks up the dispatch with `Dispatch::find($orderId, 'load_order_id')` – by dispatch id = order id – so it never finds the right one). The order is then "pending" and "dispatched" at the same time; dispatching it again makes a second dispatch (I3). |
| P4 | Medium | Admin Profile: the **username is not unique** (another admin's username "Vipin" was accepted); the form shows "Profile updated successfully" even when name / email / phone are ignored (only username, password and image are saved); `profile_image` accepts any file type (like J1/M2); the password rule here is min 6 while Change Password uses min 8. |
| P5 | Medium | **Change Password** (`/change-password`) does not ask for the current password and does not check that the `email` field belongs to the logged-in user (only that it exists); any logged-in session can set a new password. (Probably meant for the first-login forced change; confirm.) The admin test password was restored. |
| P6 | Low | "Order Cancelled" text shows the internal order id (`Your Order No.: 63182`) instead of the order number (AUTO-REQ-...). |
| P7 | Info | 7 notifications to customers and 1 to a carrier of this company are saved with `user_id = NULL` (never delivered): the payment ones (K3, L4) and "Order Cancelled" for customers that have no login. |
| P8 | Info | Mass Email to "customers" goes to ALL customers of the company; a staff member with the Mass Email permission is not limited to My Data (Sales has no such permission in this test). Emails were not really sent (queue not run). |
| P9 | Info | Mass Email "completed" status and the real arrival of the email could not be tested locally (mail/queue worker). |

Company-scope check: notification list, bell, mass-email recipients, cancel/claim lists are all company-scoped; staff see only their own customers. Test data kept.

---

# Summary – all 15 steps done (14 Oct 2026)

Totals: 83 findings recorded (A1–P9) – **High 14, Medium 31, Low 21, Info/Question 17**. A1 and A2 were already fixed locally; nothing else has been changed. Test data (company 809, users, orders, invoices, payments, expenses, campaign) is kept.

**High (fix first):**
- B1 carrier can edit Approved / Rating / Internal notes / Carrier Type
- D1 customer "Create RFQ Order" with a charge crashes (missing `use LoadOrderCharge`)
- G1 bids accepted on unpublished / expired loads
- H1 carrier can edit and delete his bid after the admin approved it
- I1 dispatch accepted to a carrier of ANOTHER company
- I2 dispatch accepted to a carrier with EXPIRED insurance (client-side check only)
- I4 customer can open the carrier Rate Confirmation (sees carrier cost)
- J1 any file type (.php/.html) accepted as POD in Update Tracing
- J3 customer's tracing history exposes carrier name / email / phone
- K1 Sales can create an invoice for an unassigned customer's order
- L2 carrier invoice `final_amount` is trusted from the browser
- M3 expense payment has no validation (negative / over-payment saved)
<!-- - N1 admin User Report always 500 (wrong external URL `quickchartz.io`) -->
- P1 a PAID order can be cancelled; restore puts it to "dispatched"

**Questions waiting for the owner:** A3 (staff code per company?), A4 (carrier popup offer company statuses?), H3 (two bids approved on one load?), I5 (may the customer's BOL name the carrier?), I8 (hide "Ordered By" on carrier pages?), J6 (may a later tracing update re-open a delivered order?), P5 (Change Password without current password – first-login only?).

**Pattern to remember:** most gaps are "the screen limits it, the server does not" (I1, I2, J4, L2, M1, M3) and "missing validation → HTTP 500 or raw SQL text" (D3, E3, E4, H4, I7, L1, M4, M5). Isolation between companies is otherwise strong: the id sweep (2,450 requests) and the 243-page crawl found no cross-company data.

## Fix log (findings resolved one by one after the owner's review)
- **B1 DONE (local, 14 Oct):** the carrier's Settings page now shows Carrier Type, Rating, CSA Approved, Approved and Internal Notes as view-only (disabled), and `SettingController::updateSettings` no longer writes those five fields, so a changed form or a direct request cannot change them. They stay company-controlled. Checked: a PUT with changed values leaves all five unchanged (the SQL no longer contains them). Files: app/Http/Controllers/SettingController.php, resources/views/carrireP/Setting/General_Setting.blade.php.
- **B3 DONE (local, 14 Oct):** route `carrier/view_Driver` had no id and always gave 500; it is now `carrier/view_Driver/{id}`; the sidebar highlight uses `carrier/view_Driver*`. Checked: own driver 200, another carrier 404. No page linked to it, so no visible change. Files: routes/web.php, resources/views/carrireP/includes/under-Driver-sidebar.blade.php.
- **B1 completed (local, 14 Oct) – per company:** the carrier's Settings now READS Carrier Type, Rating, CSA Approved, Approved and Internal Notes from this carrier's own record for its company (`company_user`, the same row the company edits in Edit Carrier), shown read-only (type and rating as plain text, so the hard-coded Type 1 / Type 2 list is no longer used for them). Checked: values set by the company show on the carrier's page; carrier 2 does not see them; the carrier's save still cannot change them. Files: SettingController.php (General_Setting passes `$companyTerms`), General_Setting.blade.php.
- **C3 DONE (local, 14 Oct):** `CrmController::saveFollowup` no longer calls `dd()`; on failure it rolls back, writes the reason to the log and returns to the form with the typed values and the message "Follow-up could not be saved. Please check the details and try again." (The same `dd()` pattern still exists in the follow-up delete handler of the same file - not touched, not in the report.)
- **D1 DONE (local, 14 Oct):** the "Create RFQ Order" route is in use (customer sidebar), so it was kept and the bug fixed: `use App\Models\LoadOrderCharge;` added to CustomerController. Checked: a customer order request with a charge now saves ("RFQ Order created successfully!", 1 order, 1 charge); test order AUTO-D1-FIX kept as test data.
- **D2 DONE (local, 14 Oct):** `CustomerController::order_rfq_store` now refuses an order number already used in the company (same rule as the admin's Create Order) with the message "This order number is already used. Please reload the page to get a new one." Checked: same number twice saves one order; a different number saves.
- **D3 DONE (local, 14 Oct):** `rfq_store` and `order_rfq_store` validate first and show a plain message through the normal red toast (customer type must be Lead or Customers, quote type, customer, at least one truck/equipment line, price and quantity must be numbers and not negative). Empty, missing-type, invalid-type and no-rows submissions save nothing and no longer show `Undefined array key`. A valid RFQ and order still save. File: app/Http/Controllers/CustomerController.php.
- **E2 DONE (local, 14 Oct):** `QuotationController::quotation_store` and `quotation_update` refuse a quotation number already used in the company ("This quotation number is already used in your company. Please reload the page to get a new one."). Update only checks when the number is changed, so older quotations that already share a number can still be saved. Checked: same number twice = 1 quotation; update keeping its own number works; update to another quotation's number refused.
- **E3 DONE (local, 14 Oct):** both methods now validate first and show a plain message in the normal red toast: customer type (Lead or Customers), quote type, customer, at least one truck/equipment line, price / quantity / trucks must be numbers and not negative. The Update Quote save also no longer crashes on a wrong shippings shape (missing commodity / currency / base price read as empty). Checked: no rows, no customer type, text price, negative quantity, update without rows - nothing saved, clear message. The Load Request edit screen (which also posts to Update Quote and has no quotation-number field) was kept working by not requiring that field on update.
- **E4 DONE (local, 14 Oct):** `loadRequestController`: Bid Limit must be a whole number 0 to 1000 (negative, text and decimals refused with a message, saved value unchanged); Publish and Bulk Publish days must be a whole number 1 to 365. For the Ajax pop-ups the message appears in the existing red toast ("Error ... : <message>"). Checked with -5, abc, 5000, 1.5, 4 for the bid limit and valid 7 days for publish. Files: QuotationController.php, loadRequestController.php.
- **G1 DONE (local, 14 Oct):** `loadRequestController::submitBid` refuses a bid when the load is not "published" or its publish period has ended: "This load is no longer open for bids." Checked: expired and unpublished loads refused, nothing saved; a published load still takes the bid.
- **H1 DONE (local, 14 Oct):** `updateBid` and `deleteBid` refuse an APPROVED bid ("This bid has been approved by the company and can no longer be changed / deleted."). Pending bids can still be edited and deleted. Checked: approved bid amount unchanged and bid kept; pending bid edit works.
- **H3 DONE (local, 14 Oct, owner decision: restrict):** `bids_approve` refuses to approve a second bid on a load that already has an approved bid (HTTP 422, shown in the red toast: "Another bid on this load is already approved. Disapprove it first."). After the first is disapproved the other can be approved. File: app/Http/Controllers/loadRequestController.php. Test bids restored to their earlier status.
- **I1 DONE (local, 14 Oct):** `LoadOrderController::dispatchStore` now checks on the server that the carrier works with this company (a `company_user` link, or created by the company); another company's carrier, an unknown carrier or no carrier is refused with "The selected carrier does not work with your company." / "Please select a carrier." Nothing is saved.
- **I2 DONE (local, 14 Oct):** the same check applies the Dispatch screen's insurance rule on the server (company's own liability and cargo end dates, or the carrier's own if no company record; missing or past dates = expired): "Selected Carrier Cargo or Liability Insurance is Expired, cant dispatch order with selected carrier." Checked with liability expired and cargo expired; a valid carrier still dispatches (update of an existing dispatch, row count unchanged).
- **I4 DONE (local, 14 Oct):** `rateConfigPDF` returns 404 for a customer login. Checked: customer 404, the dispatched carrier 200, company admin 200, another carrier / another company 404. Files: app/Http/Controllers/LoadOrderController.php (helper `carrierDispatchProblem`).
- **J2 DONE (local, 14 Oct):** admin "Upload POD" (`LoadOrderController::storePod`) accepts only PDF, Word (doc, docx) and images (jpg, jpeg, png), up to 5 MB; html, txt and php are refused with "The POD must be a PDF, Word document (doc, docx) or an image (jpg, png)." and nothing is saved. Checked with txt / html / php (refused), pdf and png (saved). The carrier's Update Tracing POD upload (J1) was NOT changed - still open.
- **J3 DONE (local, 14 Oct):** `showTracing` (the tracing history pop-up) returns only the tracking facts for a customer login (status, date, place, link, border, notes); the carrier's name, tracing email, phone, rate, driver and truck fields and the GPS "carrier_name / carrier_contact / carrier_email / driver_name" are removed. Checked: customer JSON has 12 fields and no carrier name, email, phone or rate; admin and carrier still get everything (38 fields).
- **I5 DONE (local, 14 Oct):** the BOL (`bol-pdf.blade.php`) leaves Carrier Name, Trailer Number and SCAC blank when a customer opens it; admin, staff and carrier see them as before. Checked in the PDF text.
- **Other places where carrier details could reach the customer (checked, nothing else found):** all customer pages and lists (dashboard, orders, invoices, reports, load pages) contain no carrier name, email, phone, rate, driver or truck; Order PDF and customer invoice are clean; the Send Email tracking mail never names the carrier. Still to remember: the customer's "Ordered By" (I8) and the BOL "Pro Number / seal" fields are typed by the company.
- **I6 DONE (local, 14 Oct):** the Rate Confirmation "Driver Contact" now shows the driver's phone (the one the carrier assigned, else the one entered at dispatch); the carrier's company phone only when there is no driver phone. Checked on order 63181: shows 9873333333 (assigned driver), not 9820000004. File: resources/views/loadOrder/load-order-rate-config-pdf.blade.php.
- **J6 DONE (local, 14 Oct):** `CarrierController::saveTracking` - once an order is delivered (or invoiced, paid, completed, cancelled, in claim), the carrier can no longer save a later tracking update with another status ("This order is already delivered. Its status can no longer be changed by the carrier. Please contact the company."); nothing is saved. A second "Delivered" update (late POD) still works. The company's own Update Tracing (admin screen) is unchanged, so the company can still re-open an order. Checked on order 63182 (restored afterwards).
- **K1 + K2 DONE (local, 14 Oct):** `ManualInvoiceController::findOrder` (used by details, prepare, preview and send) now also applies the My Data guard (`DataScope::order($id, 'invoice')`), so Sales gets 404 on an unassigned customer's order and nothing is created or changed. Checked: Sales 404 on details / prepare / preview / send (no invoice created, order status unchanged); another company 404; admin and Manager (All Data) 200.
- **K3 DONE (local, 14 Oct):** `InvoiceController::payInvoice` now addresses "Invoice Payment Received" to this invoice's customer (`user_id` = the customer's login). Checked: customer 810's bell shows it, another customer's does not. (The carrier-side twin, L4, is not changed yet.)
- **K5 DONE (local, 14 Oct):** `payInvoice` requires an amount of at least 0.01 ("The amount field must be at least 0.01."); a payment of 0 is refused and nothing is saved; a normal payment (40) saves. (Carrier payment L5 not changed.) Test order AUTO-D1-FIX (id 63184) now has a customer invoice K3-INV with a part payment, kept as test data. Files: ManualInvoiceController.php, InvoiceController.php.
- **K7 DONE (local, 14 Oct):** Delete invoice (customer and carrier) is no longer a GET link: the routes `customer-invoice/delete/{id}` and `carrier-invoice/delete/{id}` are now POST, the two screens (`customer-invoiced.blade.php`, `carrier-invoice-received.blade.php`) still ask "Are you sure...?" and then submit a small form with the CSRF token, and both controller methods check the `invoice_delete` permission (staff without it get "You do not have permission."). Checked: GET answers 405 for everyone (nothing deleted); POST by another company and by Sales-unassigned 404; POST by admin works as before. Not changed: the delete still removes the payments and sets the order back to "Invoice Not Sent" while the invoice row itself is kept (old behaviour). Test invoice K3-INV lost its test payment.
- **L1 DONE (local, 14 Oct):** `InvoiceController::carrierInvoiceStore` now validates everything the form sends, with plain messages in the red toast: Payment Type required (Regular, Quick Pay or Factoring); a Factoring company is required for Factoring and must be one of this company's own; Discount Type flat / percentage; Discount a number, not negative, a percentage not above 100, a flat discount not above the invoice amount; Final Amount a number, not negative; currency length. A blank discount (e.g. Factoring, where the discount boxes are hidden) no longer crashes: it uses the column defaults, and a blank final amount takes the invoice amount. Checked 15 cases (blank / bad payment type, factoring without or with another company's company, bad / negative / text discount, 150%, too-big flat discount, text / negative final amount - all refused, nothing saved; regular, quick + 10%, factoring all saved). L2 (server recalculating the final amount) is NOT done yet. Test order 63184 used and its test carrier invoices removed.
- **M3 + M4 DONE (local, 14 Oct):** `ExpensesController::expenseMultiPay` validates before saving and shows one plain message in the red toast: payment mode and a valid payment date required; at least one expense; every amount a number MORE than 0 (negative, zero, text and empty refused); the total paid for an expense cannot be more than what is still due ("Payment not saved: 250.00 is more than the amount still due on expense EXP-AUTO-1 (200.00)."). The raw SQL text is gone. Checked 13 cases; valid part payment (100) -> partially-paid, balance (200 by cheque) -> paid, nothing more can be paid, another company 404. Test payments on expense 39 removed afterwards.
- **P1 DONE (local, 14 Oct):** `LoadOrderController::cancelOrder` refuses an order that is delivered, invoicing-pending / send to invoicing, invoiced, partially-paid, paid, completed or in claim, or that already has a customer or carrier invoice: "This order is already delivered, invoiced or paid and cannot be cancelled." Checked: a paid order and delivered / invoicing-pending / claim orders are refused and unchanged; a dispatched order and a pending order can still be cancelled and restored (back to dispatched / pending). Not done: P2 (tell the carrier about a cancellation) and P3 (claim reset).
- **M2 DONE (local, 14 Oct):** the receipt on Add Expense, Edit Expense, Add Vendor Expense and Edit Vendor Expense accepts only PDF, Word (doc, docx) and images (jpg, jpeg, png); php, html and txt are refused with "The receipt must be a PDF, Word document (doc, docx) or an image (jpg, png)." and nothing is saved; the message is also shown under the file box on the four screens. Size limit NOT added (owner will decide); Edit Vendor Expense keeps its old 2 MB limit. Checked add / edit / vendor with php, html, txt (refused) and pdf, png (saved); test rows removed. Files: ExpensesController.php, Expense/Add_Expense, Add_Vender_Expense, edit_Expense, edit_Vender_Expense blade files.
- **K7 follow-up (local, 14 Oct, owner: keep behaviour, say it in the message):** behaviour unchanged. The Delete confirm box now says what will happen - customer side: "Delete this invoice? All payments recorded on it will be removed and the order goes back to Invoice Not Sent (the invoice itself is kept so it can be edited and sent again)."; carrier side: "Delete this invoice? The invoice will be removed and the order goes back to Carrier Invoice Not Received." - and the result message says the same ("Invoice payments removed. The order is back in Invoice Not Sent (the invoice is kept and can be edited and sent again)." / "Invoice deleted. The order is back in Carrier Invoice Not Received."). Files: customer-invoiced.blade.php, carrier-invoice-received.blade.php, InvoiceController.php.
- **K7 wording corrected:** both messages now say plainly that ALL the payments are DELETED (customer: payments deleted, invoice kept, order back to Invoice Not Sent; carrier: invoice and its payments deleted - payments go with the invoice in the database - order back to Carrier Invoice Not Received).
- **F2 re-confirmed (15 Oct, QA company):** series QA-CAR-000 -> first ID QA-CAR-0001, second QA-CAR-00011 (increment() accepts only letters + one separator + digits; a prefix with two parts like QA-CAR- falls through and gets a 1 appended). Affects all five ID services. Not fixed - waiting for the owner.
- **F2 DONE (local, 15 Oct):** the ID number rule increment() in the Order, Quote, Lead, Customer and Carrier ID services now takes ANY prefix followed by a separator and digits (QA-CAR-000, AUTO-QUO-000, OR-002...) and adds one to the digits after the LAST separator, keeping the zero padding. Checked 13 patterns on all five services (QA-CAR-000 -> QA-CAR-001, QA-CAR-0001 -> QA-CAR-0002, QA-CAR-0099 -> QA-CAR-0100, OR-002 -> OR-003, OR002 -> OR003, ABC -> ABC1, 5 -> 6, ...). Old single-hyphen series behave exactly as before. QA company test IDs repaired (customer 749 = QA-LEAD-001 / QA-CUS-001, carrier = QA-CAR-001). Files: Order/Quote/Lead/Customer/CarrierIdService.php.
- **F1 DONE (local, 15 Oct):** the is this number already used? check of LeadIdService, CustomerIdService, OrderIdService and QuoteIdService now looks only at the numbers of THIS company (created_by), like the Carrier ID. Two companies with the same series no longer influence each other. Checked: with another company (809) already holding QA-ORD-001, QA-QUO-001, QA-LEAD-002 and QA-CUS-002, the QA company still gets exactly those numbers next; inside the QA company a used number is still skipped (QA-ORD-001 used -> next QA-ORD-002). Test rows removed. Not changed: the two old random-number helpers generateLeadUniqueId() (RegistrationController / CrmController) which are separate and random. Files: Order/Quote/Lead/CustomerIdService.php.
