jobs_partial_ship.php 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. <?php
  2. require_once __DIR__ . '/../lib/identity.php';
  3. require_once __DIR__ . '/../lib/jobs.php';
  4. [$actor, $vendor_id] = resolve_request_actor();
  5. if ($actor === 'ICG') {
  6. http_response_code(403);
  7. echo 'Partial ship is a vendor action';
  8. return;
  9. }
  10. $pdo = db();
  11. $job_id = (int) ($_POST['job_id'] ?? 0);
  12. $partial = (int) ($_POST['partial_qty'] ?? 0);
  13. if ($job_id <= 0 || $partial <= 0) {
  14. http_response_code(400);
  15. echo 'Bad parameters';
  16. return;
  17. }
  18. $stmt = $pdo->prepare('SELECT * FROM jobs WHERE id = ?');
  19. $stmt->execute([$job_id]);
  20. $job = $stmt->fetch();
  21. if (!$job) {
  22. http_response_code(404);
  23. echo 'Job not found';
  24. return;
  25. }
  26. if ((int) $job['vendor_id'] !== $vendor_id) {
  27. http_response_code(403);
  28. echo 'Wrong vendor';
  29. return;
  30. }
  31. if (!in_array($job['status'], ['', 'Finished'], true)) {
  32. http_response_code(400);
  33. echo 'Can only partial-ship from open or finished status';
  34. return;
  35. }
  36. $current_qty = (int) $job['qty'];
  37. if ($partial >= $current_qty) {
  38. http_response_code(400);
  39. echo 'Partial quantity must be less than current quantity (' . $current_qty . ')';
  40. return;
  41. }
  42. // BEGIN IMMEDIATE + retry mirrors the apply_job_change pattern.
  43. $attempts = 0;
  44. while (true) {
  45. $attempts++;
  46. try {
  47. $pdo->exec('BEGIN IMMEDIATE');
  48. break;
  49. } catch (PDOException $e) {
  50. if ($attempts >= 50 || !is_busy_error($e)) throw $e;
  51. usleep(100000);
  52. }
  53. }
  54. try {
  55. $remaining = $current_qty - $partial;
  56. // The shipped portion as its own row.
  57. $ins = $pdo->prepare(
  58. "INSERT INTO jobs (vendor_id, job, material, description, qty, due_date, ack, status)
  59. VALUES (?, ?, ?, ?, ?, ?, '', 'Shipped')"
  60. );
  61. $ins->execute([
  62. $job['vendor_id'], $job['job'], $job['material'], $job['description'],
  63. $partial, $job['due_date']
  64. ]);
  65. $new_id = (int) $pdo->lastInsertId();
  66. // Deduct from the original; keep its current status.
  67. $upd = $pdo->prepare(
  68. "UPDATE jobs SET qty = ?, updated_at = datetime('now') WHERE id = ?"
  69. );
  70. $upd->execute([$remaining, $job_id]);
  71. // Audit trail: new row's status, and the qty deduction on the original.
  72. $hist = $pdo->prepare(
  73. 'INSERT INTO job_history (job_id, field, old_value, new_value, actor)
  74. VALUES (?, ?, ?, ?, ?)'
  75. );
  76. $hist->execute([$new_id, 'status', '', 'Shipped', $actor]);
  77. $hist->execute([$job_id, 'qty', (string) $current_qty, (string) $remaining, $actor]);
  78. $pdo->exec('COMMIT');
  79. echo 'Success';
  80. } catch (Throwable $e) {
  81. $pdo->exec('ROLLBACK');
  82. throw $e;
  83. }