Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Janblack07/OxygenDispatch/llms.txt

Use this file to discover all available pages before exploring further.

A dispatch records the outbound movement of one or more medical gas cylinders to a client. Each dispatch captures who received the cylinders, when they left, supporting voucher and remission references, and an automatically computed document number derived from the source batch data. Every dispatched tank is locked from further allocation, and a corresponding SALIDA inventory movement is written to the audit trail — all within a single database transaction.

Dispatch methods

OxygenDispatch provides two ways to create a dispatch: hand-picking specific cylinders by serial number, or letting the system pull the right quantity automatically using FIFO selection.
Use this method when you know exactly which cylinder serials should leave the warehouse — for example, when a client requests a specific batch or a technician has pre-staged particular units.
StepRoute
Open the create formGET /dispatches/create
Submit the dispatchPOST /dispatches

Browsing available tanks

The create page lists every tank whose status is DISPONIBLE (status = 1). You can narrow the table using the following query parameters:
batch
string
Partial match against the tank’s batch batch_number or document_number. Useful when you know the purchase order reference but not individual serials.
serial
string
Partial match against the tank’s serial field.
capacity_id
integer
Exact match on the cylinder capacity (e.g. 10 L, 40 L). Filters the table to a single size class.
The table is paginated at 15 rows per page. If the request carries an X-Requested-With: XMLHttpRequest header (i.e. request()->ajax() is true), the endpoint returns JSON instead of a full page render:
{
  "html": "<table>…rendered rows…</table>",
  "total": 42
}

Form fields

client_id
integer
ID of the client receiving the cylinders. Must exist in the clients table. Leave blank to create a client-less dispatch — dispatches.entity_type will be stored as NULL.
dispatched_at
date
required
The date the cylinders were dispatched. Accepted in any format Laravel’s date rule recognises (e.g. 2025-07-15).
document_number
string
Do not populate this field manually. It is ignored on submission and overwritten automatically from the batch data of the selected tanks. See document_number auto-calculation.
remission_plate
string
Vehicle licence plate on the remission note. Maximum 50 characters.
voucher_type
string
Type of voucher (e.g. Factura, Nota de entrega). Maximum 50 characters.
voucher_number
string
Voucher reference number. Maximum 100 characters.
remission_number
string
Remission note number. Maximum 100 characters.
notes
string
Free-text observations. No length limit enforced at the application layer.
tank_ids[]
array of strings
required
One or more string IDs identifying the tank_units to dispatch. The array must contain at least one element. Each ID must exist in the tank_units table. Duplicate IDs are deduplicated automatically before processing.

What happens during dispatch

Both creation paths delegate to DispatchService, which executes the following steps inside a single DB::transaction. If any step raises an exception the entire transaction is rolled back and the database is left unchanged.
  1. Row-level lock — each selected TankUnit row is fetched with lockForUpdate() to prevent concurrent dispatches from claiming the same cylinder.
  2. Status transitiontank.status is set to DESPACHADO and tank.dispatched_at is stamped with the current timestamp.
  3. Dispatch line — a DispatchLine record is created linking dispatch_idtank_unit_id. The dispatch_lines table enforces a unique constraint on this pair, so a tank can only appear once per dispatch and cannot be re-dispatched while its status remains DESPACHADO.
  4. Inventory movement — a SALIDA InventoryMovement record is written, capturing the tank, the originating warehouse area, the batch, the performing user’s email, and the dispatch’s document_number as the reference document.
// Simplified excerpt from DispatchService::createDispatch()
DB::transaction(function () use ($dispatchData, $tankIds, $performedBy) {
    $dispatch = Dispatch::create($dispatchData + [
        'performed_by_user_email' => $performedBy,
    ]);

    $tanks = TankUnit::whereIn('id', $tankIds)->lockForUpdate()->get();

    foreach ($tanks as $tank) {
        if ((int) $tank->status->value !== TankStatus::DISPONIBLE->value) {
            throw new \RuntimeException("El tanque {$tank->serial} no está disponible.");
        }

        $tank->status = TankStatus::DESPACHADO;
        $tank->dispatched_at = now();
        $tank->save();

        DispatchLine::create([
            'dispatch_id'  => $dispatch->id,
            'tank_unit_id' => $tank->id,
        ]);

        InventoryMovement::create([
            'type'                    => MovementType::SALIDA,
            'occurred_at'             => now(),
            'tank_unit_id'            => $tank->id,
            'from_area_id'            => $tank->warehouse_area_id,
            'batch_id'                => $tank->batch_id,
            'performed_by_user_email' => $performedBy,
            'reference_document'      => $dispatch->document_number,
        ]);
    }

    return $dispatch->refresh()->load(['client', 'lines.tankUnit']);
});

document_number auto-calculation

The document_number on a dispatch is never typed by the user — it is derived from the source batches of every cylinder included in the dispatch. After all tanks have been selected, the controller collects each tank’s batch.document_number, drops any blank values, trims whitespace, deduplicates, and joins the results with ", ":
ScenarioResulting document_number
All tanks originate from batch 42974328-HY42974328-HY
Tanks come from batches 42974328-HY and 42974329-HY42974328-HY, 42974329-HY
No batch carries a document_numberN/S
For the by-quantity flow the placeholder N/S is written at creation time, and a separate syncDocumentNumberFromDispatchTanks() call recalculates and persists the real value once the transaction has committed and the assigned tank IDs are known.

Dispatch record fields

Each row in the dispatches table stores the following information:
FieldTypeDescription
client_idbigint nullableFK → clients.id. Set to NULL if the client is deleted (null-on-delete).
dispatched_atdatetimeDate and time the cylinders left the warehouse.
document_numberstring(100) nullableAuto-calculated from batch data (see above).
entity_typetinyint nullableCopied from the client at the time of dispatch: 1 = Entidad, 2 = Intradomiciliario IESS, 3 = No afiliado / Apoyo.
remission_platestring(50) nullableVehicle licence plate from the remission note.
performed_by_user_emailstring(150)Email of the authenticated user who created the dispatch.
voucher_typestring(50) nullableType label of the supporting voucher.
voucher_numberstring(100) nullableReference number of the supporting voucher.
remission_numberstring(100) nullableRemission note reference number.
notestext nullableFree-text observations.

Viewing dispatches

List viewGET /dispatches returns a paginated list (15 per page) of all dispatches ordered by most recent first, with the associated client eager-loaded. Detail viewGET /dispatches/{dispatch} loads the full dispatch record together with:
  • The linked client (name, document)
  • All dispatch lines, each carrying the tank’s serial number, gas type, cylinder capacity, and originating batch reference
These relationships map directly to the Eloquent eager-load chain:
$dispatch->load([
    'client',
    'lines.tankUnit.gasType',
    'lines.tankUnit.capacity',
    'lines.tankUnit.batch',
]);

Only tanks whose status equals DISPONIBLE (value 1) can be dispatched.Serial-selection dispatch — if any selected tank has a different status at the moment the transaction executes (for example, because it was claimed by a concurrent request), DispatchService throws a RuntimeException naming the offending serial, the transaction is rolled back in full, and no cylinders are dispatched.By-quantity dispatch — if the filtered stock cannot satisfy the requested quantity, DispatchService throws a RuntimeException reporting how many tanks are available versus how many were requested, and the transaction is rolled back in full with no cylinders dispatched and no record created.Always confirm tank availability before submitting the form.

Build docs developers (and LLMs) love