347 lines
10 KiB
PHP
347 lines
10 KiB
PHP
<?php
|
|
session_start();
|
|
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 in
|
|
</h1>
|
|
<a href="<?php echo $server_url?>ics/manage_stock_in.php" class="btn btn-primary">
|
|
<span><i class="ti ti-plus"></i></span>
|
|
<span>ADD Stock In</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" required></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_in()'>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_in">
|
|
<thead class="table-primary border-light">
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Product</th>
|
|
<th>Lot Number</th>
|
|
<th>Serial Number</th>
|
|
<th style="text-align: right;">Quantity</th>
|
|
<th style="text-align: right;">Unit Price</th>
|
|
<th>Source</th>
|
|
<th class="th_loc_zone">Zone</th>
|
|
<th class="th_loc_aisle">Aisle</th>
|
|
<th class="th_loc_rack">Rack</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_rack = '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_rack').text(label_rack);
|
|
} else {
|
|
$('.th_loc_zone, .th_loc_aisle').hide();
|
|
$('.th_loc_rack').text(label_rack);
|
|
}
|
|
}
|
|
|
|
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['rack'] || ''}</td>`;
|
|
}
|
|
return `<td class="py-3">${item['rack'] || ''}</td>`;
|
|
}
|
|
|
|
function escape_html(value) {
|
|
return String(value ?? '').replace(/[&<>"']/g, function(char) {
|
|
return {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
}[char];
|
|
});
|
|
}
|
|
|
|
function source_label(source) {
|
|
source = source || '';
|
|
if (!source) return 'Manual';
|
|
return source.charAt(0).toUpperCase() + source.slice(1).replace(/_/g, ' ');
|
|
}
|
|
|
|
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_in_all ? alasql.tables.stock_in_all.data : [];
|
|
|
|
if (!selected) {
|
|
alasql.tables.stock_in.data = rows;
|
|
} else {
|
|
alasql.tables.stock_in.data = rows.filter(function(item) {
|
|
var source = item.source || '';
|
|
return selected === '__manual__' ? !source : source === selected;
|
|
});
|
|
}
|
|
|
|
var table_id = 'stock_in';
|
|
var total_records = alasql.tables.stock_in.data.length;
|
|
var page = generate_pagination(table_id, total_records);
|
|
$(`table#${table_id} tfoot`).html(page);
|
|
change_page_stock_in(1);
|
|
}
|
|
|
|
function retrieve_stock_in() {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/stock_in.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_in_all');
|
|
alasql('CREATE TABLE IF NOT EXISTS stock_in');
|
|
alasql.tables.stock_in_all.data = res.output || [];
|
|
|
|
populate_source_filter(alasql.tables.stock_in_all.data);
|
|
apply_source_filter();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function change_page_stock_in(page_num) {
|
|
|
|
var offset = (page_num - 1) * prop_limit;
|
|
|
|
var page_data = alasql(`SELECT * FROM stock_in LIMIT ${prop_limit} OFFSET ${offset}`);
|
|
|
|
var body = ``;
|
|
|
|
var warehouse = $(`select#warehouse`).val();
|
|
|
|
$.each(page_data, function(key, item) {
|
|
body += `<tr>
|
|
<td class="py-3">${format_date(item["date"])}</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_number(item['quantity'], 2)}</span>
|
|
<span class="text-muted small" style="min-width:28px;">${item['uom'] || ''}</span>
|
|
</div>
|
|
</td>
|
|
<td class="py-3 text-end">${item['price'] > 0 ? format_number(item['price'], 2) : '<span class="text-muted">—</span>'}</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">
|
|
<a href="<?php echo $server_url?>ics/manage_stock_in.php?id=${item['id']}&wh=${warehouse}" class=""><i class="ti ti-eye fs-5"></i></a>
|
|
<a href="javascript:void(0);" class="link-danger"
|
|
onclick="delete_stock_in($(this),${item['id']})">
|
|
<i class="ti ti-trash ms-2 fs-5"></i>
|
|
</a>
|
|
</td>
|
|
</tr>`;
|
|
})
|
|
|
|
$("table#stock_in > 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) {
|
|
var option = ``;
|
|
$.each(res.output, function(key, item) {
|
|
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
|
|
})
|
|
$(`select#warehouse`).html(option);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
function delete_stock_in(element, id) {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/delete_stock_in.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'delete',
|
|
data : {
|
|
id: id,
|
|
wh: $(`select#warehouse`).val()
|
|
},
|
|
onSuccess: function(res) {
|
|
element.closest('tr').remove();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
$(async function() {
|
|
try {
|
|
await load_company_setting();
|
|
await retrieve_warehouse();
|
|
await retrieve_stock_in();
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
});
|
|
|
|
$(document).on('change', '#source', apply_source_filter);
|
|
$(document).on('change', '#warehouse', retrieve_stock_in);
|
|
|
|
// ── 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_rack = s.location_label_rack || (advanced ? 'Rack' : '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_in();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|