$item) { if (!is_array($item)) { throw new Exception("{$doc_label} line #" . ($i + 1) . " is not valid."); } $name = trim((string)($item['product_name'] ?? '')) ?: trim((string)($item['product_sku'] ?? '')); $label = 'Line #' . ($i + 1) . ($name !== '' ? " ({$name})" : ''); $qty = self::number($item['quantity'] ?? 0, "{$label}: quantity"); $price = self::number($item['unit_price'] ?? $item['price'] ?? 0, "{$label}: unit price"); $rate = self::number($item['tax_rate'] ?? 0, "{$label}: tax rate"); $qty = round($qty, 4); if ($qty < self::MIN_QUANTITY) { throw new Exception("{$label}: quantity must be greater than zero."); } if ($qty > self::MAX_QUANTITY) { throw new Exception("{$label}: quantity is too large."); } if ($price < 0) { throw new Exception("{$label}: unit price cannot be negative."); } if ($price > self::MAX_UNIT_PRICE) { throw new Exception("{$label}: unit price cannot exceed " . number_format(self::MAX_UNIT_PRICE, 2) . "."); } if ($rate < 0 || $rate > self::MAX_TAX_RATE) { throw new Exception("{$label}: tax rate must be between 0 and " . self::MAX_TAX_RATE . "%."); } $total = round($qty * $price, 4); $item['quantity'] = $qty; $item['unit_price'] = round($price, 4); $item['tax_rate'] = round($rate, 2); $item['total_price'] = $total; $item['tax_amount'] = round($total * $item['tax_rate'] / 100, 4); $out[] = $item; } return $out; } /** Header amounts (discount, shipping fee): numeric and never negative. */ public static function amount($value, string $label): float { $n = self::number($value, $label); if ($n < 0) { throw new Exception("{$label} cannot be negative."); } if ($n > self::MAX_UNIT_PRICE * 1000) { throw new Exception("{$label} is too large."); } return $n; } /** A discount larger than the goods would turn the document negative. */ public static function discount($value, float $subtotal): float { $discount = self::amount($value, 'Discount'); if ($discount > $subtotal + 0.00005) { throw new Exception('Discount cannot exceed the subtotal.'); } return $discount; } public static function requireId($value, string $message): int { $id = (int)$value; if ($id <= 0) { throw new Exception($message); } return $id; } /** * A department is mandatory once the company uses departments. A company * that has never defined one keeps saving with "No Department". */ public static function requireDepartment(PDO $pdo, int $company_id, $value): int { $id = (int)$value; if ($id > 0) { $sth = $pdo->prepare("SELECT COUNT(*) FROM md_department WHERE company_id = :cid AND id = :id"); $sth->execute([':cid' => $company_id, ':id' => $id]); if ((int)$sth->fetchColumn() === 0) { throw new Exception('The selected department does not exist.'); } return $id; } $sth = $pdo->prepare("SELECT COUNT(*) FROM md_department WHERE company_id = :cid AND status = 1"); $sth->execute([':cid' => $company_id]); if ((int)$sth->fetchColumn() > 0) { throw new Exception('Department is required.'); } return 0; } private static function number($value, string $label): float { if ($value === '' || $value === null) return 0.0; if (!is_numeric($value) || !is_finite((float)$value)) { throw new Exception("{$label} must be a number."); } return (float)$value; } }