Files
wms-app/app/ics/stock_out.php

376 lines
12 KiB
PHP

<?php
require_once __DIR__ . '/../session.php';
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php';?>
<?php require '../include_sidebar.php';?>
<!-- MAIN CONTENT -->
<main id="content" class="content py-15">
<div class="container-fluid">
<div class="row ">
<div class="col-12">
<div class="mb-6 d-flex justify-content-between align-items-center">
<h1 class="fs-3 mb-0">Stock Out
</h1>
<a href="<?php echo $server_url?>ics/manage_stock_out.php" class="btn btn-primary">
<span><i class="ti ti-plus"></i></span>
<span>ADD Stock Out</span></a>
</div>
</div>
</div>
<div class="row g-5">
<div class="mb-3 col-lg-2">
<input class="form-control" value='Warehouse' disabled>
</div>
<div class="mb-3 col-lg-2">
<select name="warehouse" id="warehouse" class="form-select"></select>
</div>
<div class="mb-3 col-lg-2">
<input class="form-control" value='Source' disabled>
</div>
<div class="mb-3 col-lg-2">
<select name="source" id="source" class="form-select">
<option value="">All Sources</option>
</select>
</div>
<div class="mb-3 col-lg-2">
<button class='btn btn-secondary' onclick='retrieve_stock_out()'>Search</button>
</div>
<div class="col-12">
<div class="card">
<div class="card-body p-5">
<h2 class="mb-4 fs-5">Item List</h2>
<div class="table-responsive">
<table class="table table-hover mb-0 table-centered" id="stock_out">
<thead class="table-primary border-light">
<tr>
<th>Date</th>
<th>Reference</th>
<th>Warehouse</th>
<th>Product</th>
<th>Lot Number</th>
<th>Serial Number</th>
<th style="text-align: right;">Quantity</th>
<th>Source</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_bin">Bin</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody></tbody>
<tfoot class="">
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require "../include_ending.php";?>
<script>
var advanced = false;
var label_bin = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function apply_location_labels() {
if (advanced) {
$('.th_loc_zone').text(label_zone).show();
$('.th_loc_aisle').text(label_aisle).show();
$('.th_loc_bin').text(label_bin);
} else {
$('.th_loc_zone, .th_loc_aisle').hide();
$('.th_loc_bin').text(label_bin);
}
}
function location_cells(item) {
if (advanced) {
return `<td class="py-3">${item['zone'] || ''}</td>
<td class="py-3">${item['aisle'] || ''}</td>
<td class="py-3">${item['bin'] || ''}</td>`;
}
return `<td class="py-3">${item['bin'] || ''}</td>`;
}
function escape_html(value) {
return String(value ?? '').replace(/[&<>"']/g, function(char) {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}[char];
});
}
function source_label(source) {
source = source || '';
if (!source) return 'Manual';
return source.split('_').map(function(w) {
return w.charAt(0).toUpperCase() + w.slice(1);
}).join(' ');
}
var source_view_urls = {
'supplier_return': '<?php echo $server_url?>po/manage_supplier_return.php',
'order': '<?php echo $server_url?>order/manage_order.php',
'return': '<?php echo $server_url?>order/manage_return.php',
};
function action_cell(item, warehouse) {
var source = item.source || '';
var source_id = parseInt(item.source_id) || 0;
var view_url;
if (source && source_view_urls[source] && source_id > 0) {
view_url = source_view_urls[source] + '?id=' + source_id;
} else {
view_url = '<?php echo $server_url?>ics/manage_stock_out.php?id=' + item['id'] + '&wh=' + warehouse;
}
var delete_btn = (!source)
? `<a href="javascript:void(0);" class="link-danger" onclick="delete_stock_out($(this),${item['id']},${warehouse})">
<i class="ti ti-trash ms-2 fs-5"></i>
</a>`
: '';
return `<a href="${view_url}"><i class="ti ti-eye fs-5"></i></a>${delete_btn}`;
}
function source_cell(source) {
var label = escape_html(source_label(source));
return source
? `<span class="badge bg-info-subtle text-info">${label}</span>`
: `<span class="badge bg-secondary-subtle text-secondary">${label}</span>`;
}
function populate_source_filter(rows) {
var current = $('#source').val() || '';
var sources = {};
$.each(rows, function(_, item) {
sources[item.source || '__manual__'] = item.source || '';
});
var option = `<option value="">All Sources</option>`;
$.each(Object.keys(sources).sort(), function(_, key) {
var value = sources[key];
var option_value = value || '__manual__';
option += `<option value="${escape_html(option_value)}">${escape_html(source_label(value))}</option>`;
});
$('#source').html(option).val(current);
if ($('#source').val() !== current) $('#source').val('');
}
function apply_source_filter() {
var selected = $('#source').val() || '';
var rows = alasql.tables.stock_out_all ? alasql.tables.stock_out_all.data : [];
if (!selected) {
alasql.tables.stock_out.data = rows;
} else {
alasql.tables.stock_out.data = rows.filter(function(item) {
var source = item.source || '';
return selected === '__manual__' ? !source : source === selected;
});
}
var table_id = 'stock_out';
var total_records = alasql.tables.stock_out.data.length;
var page = generate_pagination(table_id, total_records);
$(`table#${table_id} tfoot`).html(page);
change_page_stock_out(1);
}
function retrieve_stock_out() {
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/stock_out.php",
autoPrepare: true,
checkRequired: 0,
debugMode: 0,
action: 'read',
onSuccess: function(res) {
// Register the array as a table name
alasql('CREATE TABLE IF NOT EXISTS stock_out_all');
alasql('CREATE TABLE IF NOT EXISTS stock_out');
alasql.tables.stock_out_all.data = res.output || [];
populate_source_filter(alasql.tables.stock_out_all.data);
apply_source_filter();
}
});
}
function change_page_stock_out(page_num) {
var offset = (page_num - 1) * prop_limit;
var page_data = alasql(`SELECT * FROM stock_out LIMIT ${prop_limit} OFFSET ${offset}`);
var body = ``;
$.each(page_data, function(key, item) {
// The row's own warehouse, not the filter — with "All Warehouses" the
// filter has no value, and each row lives in its own td_stock_<id> table.
var warehouse = item['warehouse_id'];
body += `<tr>
<td class="py-3">${format_date(item["date"])}</td>
<td class="py-3 fw-semibold">${item["stock_reference"] || "—"}</td>
<td class="py-3">${escape_html(item['warehouse_name'] || '—')}</td>
<td class="py-3">${item["product_sku"]}: ${item['product_name']}</td>
<td class="py-3">${item['lot_number'] || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item['serial_number'] || '<span class="text-muted">—</span>'}</td>
<td class="py-3">
<div class="d-flex justify-content-end align-items-baseline gap-1">
<span>${format_quantity(item['quantity'])}</span>
<span class="text-muted small" style="min-width:28px;">${item['uom'] || ''}</span>
</div>
</td>
<td class="py-3">${source_cell(item['source'])}</td>
${location_cells(item)}
<td class="py-3">
${item['status'] == 1 ? '<span class="badge bg-success">Approved</span>' : '<span class="badge bg-warning text-dark">Draft</span>'}
</td>
<td class="py-3">${action_cell(item, warehouse)}</td>
</tr>`;
})
$("table#stock_out > tbody").html(body);
}
function retrieve_warehouse() {
return ajax_request({
url: "<?php echo $server_url?>inventory/api/engine/warehouse.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
// Default to every warehouse — stock is stored one table per
// warehouse, so a single-warehouse default hides movements that went
// elsewhere and makes a document look only partly processed.
var option = `<option value=''>All Warehouses</option>`;
$.each(res.output, function(key, item) {
option += `<option value='${item.id}'>${escape_html(item.warehouse_name)}</option>`;
})
$(`select#warehouse`).html(option);
}
});
}
function delete_stock_out(element, id, warehouse_id) {
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/delete_stock_out.php",
autoPrepare: true,
checkRequired: 0,
action: 'delete',
data : {
id: id,
wh: warehouse_id
},
onSuccess: function(res) {
element.closest('tr').remove();
}
});
}
$(async function() {
try {
await load_company_setting();
await retrieve_warehouse();
await retrieve_stock_out();
} catch (e) {
console.log(e);
}
});
$(document).on('change', '#source', apply_source_filter);
$(document).on('change', '#warehouse', retrieve_stock_out);
// ── Stock approval ────────────────────────────────────────────────────────
var auto_approve = false; // set from company_setting on load
function load_company_setting() {
return ajax_request({
url: server_url + 'setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output || {};
auto_approve = s.default_stock_status == 1;
advanced = s.advanced_location == 1;
label_bin = s.location_label_bin || (advanced ? 'Bin' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
function approve_stock(id, warehouse_id, type) {
bootbox.confirm({
message: 'Approve this stock record?',
buttons: { confirm: { label: 'Approve', className: 'btn-success' }, cancel: { label: 'Cancel', className: 'btn-secondary' } },
callback: function(result) {
if (!result) return;
ajax_request({
url: server_url + 'ics/api/engine/approve_stock.php',
autoPrepare: true,
checkRequired: 0,
action: 'update',
data: {
id: id,
warehouse: warehouse_id,
type: type
},
onSuccess: function(res) {
retrieve_stock_out();
}
});
}
});
}
</script>
</body>
</html>