242 lines
16 KiB
PHP
242 lines
16 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/app.php';
|
|
require_permission('manufacturing');
|
|
|
|
$errors = [];
|
|
$qualityOptions = manufacturing_quality_options();
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
verify_csrf();
|
|
|
|
$rawProductId = (int)($_POST['raw_product_id'] ?? 0);
|
|
$finishedProductId = (int)($_POST['finished_product_id'] ?? 0);
|
|
$finishedQty = (float)($_POST['finished_qty'] ?? 0);
|
|
$actualRawQty = (float)($_POST['actual_raw_qty'] ?? 0);
|
|
$qualityStatus = (string)($_POST['quality_status'] ?? 'accepted');
|
|
$status = (string)($_POST['status'] ?? 'completed');
|
|
$notes = trim((string)($_POST['notes'] ?? ''));
|
|
|
|
$rawProduct = fetch_record('product', $rawProductId);
|
|
$finishedProduct = fetch_record('product', $finishedProductId);
|
|
|
|
if (!$rawProduct || (($rawProduct['payload_data']['category'] ?? '') !== 'مواد خام')) {
|
|
$errors[] = 'اختر مادة خام صحيحة لعملية التصنيع.';
|
|
}
|
|
if (!$finishedProduct || (($finishedProduct['payload_data']['category'] ?? '') === 'مواد خام')) {
|
|
$errors[] = 'اختر منتجًا نهائيًا صحيحًا.';
|
|
}
|
|
if ($rawProductId > 0 && $rawProductId === $finishedProductId) {
|
|
$errors[] = 'لا يمكن أن تكون المادة الخام والمنتج النهائي نفس الصنف.';
|
|
}
|
|
if ($finishedQty <= 0 || $actualRawQty <= 0) {
|
|
$errors[] = 'أدخل كميات صحيحة للإنتاج والاستهلاك الفعلي.';
|
|
}
|
|
if (!array_key_exists($qualityStatus, $qualityOptions)) {
|
|
$errors[] = 'حالة الجودة غير صحيحة.';
|
|
}
|
|
if (!in_array($status, ['draft', 'completed'], true)) {
|
|
$errors[] = 'حالة أمر التصنيع غير صحيحة.';
|
|
}
|
|
|
|
if (!$errors && $rawProduct && $finishedProduct) {
|
|
$rawPayload = $rawProduct['payload_data'];
|
|
$finishedPayload = $finishedProduct['payload_data'];
|
|
$producedQty = $qualityStatus === 'rejected' ? 0.0 : $finishedQty;
|
|
|
|
db()->beginTransaction();
|
|
try {
|
|
$manufacturingId = create_record('manufacturing_order', 'أمر تصنيع ' . $finishedProduct['title'], next_code('MO', 'manufacturing_order'), [
|
|
'raw_product_id' => (int)$rawProduct['id'],
|
|
'raw_product_name' => $rawProduct['title'],
|
|
'raw_sku' => $rawPayload['sku'] ?? $rawProduct['code'],
|
|
'raw_unit' => $rawPayload['unit'] ?? 'وحدة',
|
|
'finished_product_id' => (int)$finishedProduct['id'],
|
|
'finished_product_name' => $finishedProduct['title'],
|
|
'finished_sku' => $finishedPayload['sku'] ?? $finishedProduct['code'],
|
|
'finished_unit' => $finishedPayload['unit'] ?? 'وحدة',
|
|
'finished_qty' => $finishedQty,
|
|
'actual_raw_qty' => $actualRawQty,
|
|
'produced_qty' => $producedQty,
|
|
'quality_status' => $qualityStatus,
|
|
'quality_label' => manufacturing_quality_label($qualityStatus),
|
|
'conversion_ratio' => $actualRawQty > 0 ? round($finishedQty / $actualRawQty, 4) : 0,
|
|
'notes' => $notes,
|
|
'created_date' => date('Y-m-d H:i'),
|
|
'completed_at' => $status === 'completed' ? date('Y-m-d H:i') : null,
|
|
'created_by' => current_user()['username'] ?? 'system',
|
|
], $status);
|
|
|
|
$order = fetch_record('manufacturing_order', $manufacturingId);
|
|
if ($status === 'completed' && $order) {
|
|
$rawStock = adjust_product_stock($rawProduct, -$actualRawQty, 'manufacturing_consume', $order['code'], 'manufacturing_order', $manufacturingId, 'استهلاك خامات لأمر التصنيع ' . $order['code']);
|
|
$payload = $order['payload_data'];
|
|
$payload['raw_stock_after'] = $rawStock['after'];
|
|
|
|
if ($producedQty > 0) {
|
|
$finishedStock = adjust_product_stock($finishedProduct, $producedQty, 'manufacturing_output', $order['code'], 'manufacturing_order', $manufacturingId, 'إضافة إنتاج نهائي لأمر التصنيع ' . $order['code']);
|
|
$payload['finished_stock_after'] = $finishedStock['after'];
|
|
} else {
|
|
$currentFinishedStock = (float)($finishedPayload['stock_qty'] ?? 0);
|
|
$payload['finished_stock_after'] = $currentFinishedStock;
|
|
}
|
|
|
|
update_record_payload($manufacturingId, $payload, 'completed');
|
|
}
|
|
|
|
db()->commit();
|
|
set_flash('success', $status === 'completed' ? 'تم إكمال أمر التصنيع وتحديث المخزون تلقائيًا.' : 'تم حفظ أمر التصنيع كمسودة.');
|
|
redirect('manufacturing.php?id=' . $manufacturingId);
|
|
} catch (Throwable $e) {
|
|
db()->rollBack();
|
|
$errors[] = 'تعذر حفظ أمر التصنيع، تأكد من توفر المخزون الخام ثم حاول مرة أخرى.';
|
|
}
|
|
}
|
|
}
|
|
|
|
$rawMaterials = raw_material_dataset();
|
|
$finishedProducts = finished_product_dataset();
|
|
$orders = fetch_records('manufacturing_order');
|
|
$detail = isset($_GET['id']) ? fetch_record('manufacturing_order', (int)$_GET['id']) : null;
|
|
$todayCount = today_record_count('manufacturing_order');
|
|
$completedCount = 0;
|
|
$draftCount = 0;
|
|
$totalProducedQty = 0.0;
|
|
foreach ($orders as $order) {
|
|
$payload = $order['payload_data'];
|
|
if (($order['status'] ?? '') === 'completed') {
|
|
$completedCount++;
|
|
}
|
|
if (($order['status'] ?? '') === 'draft') {
|
|
$draftCount++;
|
|
}
|
|
$totalProducedQty += (float)($payload['produced_qty'] ?? 0);
|
|
}
|
|
|
|
render_header('التصنيع', 'تسجيل أوامر تصنيع تخصم الخامات وتضيف المنتجات النهائية تلقائيًا مع متابعة الجودة.', 'manufacturing');
|
|
?>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-6 col-xl-3"><div class="stat-card"><div class="stat-label">أوامر التصنيع</div><div class="stat-value"><?= e((string)count($orders)) ?></div><div class="stat-note">إجمالي السجل</div></div></div>
|
|
<div class="col-6 col-xl-3"><div class="stat-card"><div class="stat-label">اليوم</div><div class="stat-value"><?= e((string)$todayCount) ?></div><div class="stat-note">أوامر اليوم</div></div></div>
|
|
<div class="col-6 col-xl-3"><div class="stat-card"><div class="stat-label">مكتملة</div><div class="stat-value"><?= e((string)$completedCount) ?></div><div class="stat-note">تم ترحيلها للمخزون</div></div></div>
|
|
<div class="col-6 col-xl-3"><div class="stat-card stat-card-soft"><div class="stat-label">الإنتاج الناتج</div><div class="stat-value"><?= e((string)$totalProducedQty) ?></div><div class="stat-note">إجمالي الكمية المقبولة</div></div></div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-xl-5">
|
|
<div class="panel-card">
|
|
<div class="section-header compact"><div><h1 class="section-title mb-1">إنشاء أمر تصنيع</h1><p class="section-copy">اختر الخامة والمنتج النهائي وسجّل الكمية الفعلية المستخدمة وحالة الجودة.</p></div></div>
|
|
<?php if (!$rawMaterials || !$finishedProducts): ?>
|
|
<div class="empty-inline mb-3">يلزم وجود مادة خام ومنتج نهائي واحد على الأقل في صفحة الأصناف قبل بدء التصنيع.</div>
|
|
<?php endif; ?>
|
|
<?php if ($errors): ?>
|
|
<div class="alert alert-danger py-2"><?php foreach ($errors as $error): ?><div><?= e($error) ?></div><?php endforeach; ?></div>
|
|
<?php endif; ?>
|
|
<form method="post" class="vstack gap-3">
|
|
<input type="hidden" name="csrf_token" value="<?= e(csrf_token()) ?>">
|
|
<div>
|
|
<label class="form-label">المادة الخام</label>
|
|
<select class="form-select" name="raw_product_id" required>
|
|
<option value="">اختر مادة خام</option>
|
|
<?php foreach ($rawMaterials as $product): ?>
|
|
<option value="<?= (int)$product['id'] ?>"><?= e($product['name']) ?> — <?= e($product['sku']) ?> (<?= e((string)$product['stock_qty']) ?>)</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">المنتج النهائي</label>
|
|
<select class="form-select" name="finished_product_id" required>
|
|
<option value="">اختر منتجًا نهائيًا</option>
|
|
<?php foreach ($finishedProducts as $product): ?>
|
|
<option value="<?= (int)$product['id'] ?>"><?= e($product['name']) ?> — <?= e($product['sku']) ?> (<?= e((string)$product['stock_qty']) ?>)</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="row g-3">
|
|
<div class="col-md-6"><label class="form-label">الكمية المنتجة</label><input type="number" step="0.01" min="0.01" class="form-control" name="finished_qty" value="1" required></div>
|
|
<div class="col-md-6"><label class="form-label">الاستهلاك الفعلي للخامة</label><input type="number" step="0.01" min="0.01" class="form-control" name="actual_raw_qty" value="1" required></div>
|
|
</div>
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">الجودة</label>
|
|
<select class="form-select" name="quality_status">
|
|
<?php foreach ($qualityOptions as $key => $label): ?>
|
|
<option value="<?= e($key) ?>"><?= e($label) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">الحالة</label>
|
|
<select class="form-select" name="status">
|
|
<option value="completed">إكمال الآن</option>
|
|
<option value="draft">مسودة</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">ملاحظات الجودة أو التشغيل</label>
|
|
<textarea class="form-control" name="notes" rows="3"></textarea>
|
|
</div>
|
|
<button class="btn btn-dark" type="submit" <?= (!$rawMaterials || !$finishedProducts) ? 'disabled' : '' ?>>حفظ أمر التصنيع</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-7">
|
|
<div class="panel-card mb-4">
|
|
<div class="section-header"><div><h2 class="section-title">سجل أوامر التصنيع</h2><p class="section-copy">الأوامر المكتملة تخصم الخامات وتضيف المنتجات النهائية مباشرة.</p></div></div>
|
|
<?php if ($orders): ?>
|
|
<div class="table-responsive">
|
|
<table class="table align-middle app-table">
|
|
<thead><tr><th>الرقم</th><th>المنتج النهائي</th><th>الخامة</th><th>الناتج</th><th>الحالة</th></tr></thead>
|
|
<tbody>
|
|
<?php foreach ($orders as $order): $payload = $order['payload_data']; ?>
|
|
<tr>
|
|
<td><a class="table-link" href="manufacturing.php?id=<?= (int)$order['id'] ?>"><?= e($order['code']) ?></a></td>
|
|
<td><?= e($payload['finished_product_name'] ?? '') ?><div class="small text-secondary"><?= e((string)($payload['finished_qty'] ?? 0)) ?> <?= e($payload['finished_unit'] ?? '') ?></div></td>
|
|
<td><?= e($payload['raw_product_name'] ?? '') ?><div class="small text-secondary"><?= e((string)($payload['actual_raw_qty'] ?? 0)) ?> <?= e($payload['raw_unit'] ?? '') ?></div></td>
|
|
<td><?= e((string)($payload['produced_qty'] ?? 0)) ?> <?= e($payload['finished_unit'] ?? '') ?><div class="small text-secondary"><?= e(manufacturing_quality_label((string)($payload['quality_status'] ?? 'accepted'))) ?></div></td>
|
|
<td><span class="badge <?= e(status_badge_class((string)$order['status'])) ?>"><?= e(order_status_label((string)$order['status'])) ?></span></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="empty-inline">لا توجد أوامر تصنيع بعد.</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="panel-card">
|
|
<div class="section-header compact"><div><h2 class="section-title">تفاصيل أمر التصنيع</h2><p class="section-copy">يعرض أثر العملية على المخزون الفعلي لكل صنف.</p></div></div>
|
|
<?php if ($detail): $payload = $detail['payload_data']; ?>
|
|
<div class="detail-grid mb-3">
|
|
<div class="detail-block"><span class="detail-label">رقم الأمر</span><strong><?= e($detail['code']) ?></strong></div>
|
|
<div class="detail-block"><span class="detail-label">الحالة</span><strong><?= e(order_status_label((string)$detail['status'])) ?></strong></div>
|
|
<div class="detail-block"><span class="detail-label">الجودة</span><strong><?= e(manufacturing_quality_label((string)($payload['quality_status'] ?? 'accepted'))) ?></strong></div>
|
|
<div class="detail-block"><span class="detail-label">المنشئ</span><strong><?= e($payload['created_by'] ?? '') ?></strong></div>
|
|
</div>
|
|
<div class="subtle-card mb-3">
|
|
<div class="summary-row"><span>المادة الخام</span><strong><?= e($payload['raw_product_name'] ?? '') ?> — <?= e($payload['raw_sku'] ?? '') ?></strong></div>
|
|
<div class="summary-row"><span>الاستهلاك الفعلي</span><strong><?= e((string)($payload['actual_raw_qty'] ?? 0)) ?> <?= e($payload['raw_unit'] ?? '') ?></strong></div>
|
|
<div class="summary-row"><span>المخزون بعد الخصم</span><strong><?= e((string)($payload['raw_stock_after'] ?? '—')) ?></strong></div>
|
|
</div>
|
|
<div class="subtle-card">
|
|
<div class="summary-row"><span>المنتج النهائي</span><strong><?= e($payload['finished_product_name'] ?? '') ?> — <?= e($payload['finished_sku'] ?? '') ?></strong></div>
|
|
<div class="summary-row"><span>الكمية المنتجة</span><strong><?= e((string)($payload['finished_qty'] ?? 0)) ?> <?= e($payload['finished_unit'] ?? '') ?></strong></div>
|
|
<div class="summary-row"><span>الكمية المرحلة فعليًا</span><strong><?= e((string)($payload['produced_qty'] ?? 0)) ?> <?= e($payload['finished_unit'] ?? '') ?></strong></div>
|
|
<div class="summary-row"><span>المخزون بعد الإضافة</span><strong><?= e((string)($payload['finished_stock_after'] ?? '—')) ?></strong></div>
|
|
</div>
|
|
<div class="detail-grid mt-3">
|
|
<div class="detail-block"><span class="detail-label">تاريخ الإنشاء</span><strong><?= e($payload['created_date'] ?? '') ?></strong></div>
|
|
<div class="detail-block"><span class="detail-label">تاريخ الإكمال</span><strong><?= e($payload['completed_at'] ?? '—') ?></strong></div>
|
|
<div class="detail-block"><span class="detail-label">نسبة التحويل</span><strong><?= e((string)($payload['conversion_ratio'] ?? 0)) ?></strong></div>
|
|
<div class="detail-block"><span class="detail-label">مسودات</span><strong><?= e((string)$draftCount) ?></strong></div>
|
|
</div>
|
|
<?php if (!empty($payload['notes'])): ?><div class="mt-3 text-secondary small"><?= e($payload['notes']) ?></div><?php endif; ?>
|
|
<?php else: ?>
|
|
<div class="empty-inline">اختر أمر تصنيع من الجدول لعرض التفاصيل.</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php render_footer(); ?>
|