Erlaube mehrere OTC-Zahlungsarten
This commit is contained in:
1 parent
5fede22267
commit
99dd615f01
4 files changed
+97
-20
No files matched your search
@@ -12,6 +12,7 @@
|
|||||||
8. `db/migrations/0008_shopify_sync_technical.sql`
|
8. `db/migrations/0008_shopify_sync_technical.sql`
|
||||||
9. `db/migrations/0013_virtual_shortage_lot_allocations.sql`
|
9. `db/migrations/0013_virtual_shortage_lot_allocations.sql`
|
||||||
10. `db/migrations/0014_require_human_lot_activation.sql`
|
10. `db/migrations/0014_require_human_lot_activation.sql`
|
||||||
|
11. `db/migrations/0015_sales_order_payments.sql`
|
||||||
|
|
||||||
## Beispielausfuehrung
|
## Beispielausfuehrung
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE sales_order_payment (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
sales_order_id BIGINT NOT NULL REFERENCES sales_order(id) ON DELETE CASCADE,
|
||||||
|
payment_method_id BIGINT NOT NULL REFERENCES payment_method(id) ON DELETE RESTRICT,
|
||||||
|
amount NUMERIC(14, 2) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
CONSTRAINT chk_sales_order_payment_amount_positive CHECK (amount > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_sales_order_payment_order ON sales_order_payment(sales_order_id);
|
||||||
|
CREATE INDEX idx_sales_order_payment_method ON sales_order_payment(payment_method_id);
|
||||||
@@ -30,7 +30,7 @@ if (!is_array($data)) {
|
|||||||
json_response(400, ['ok' => false, 'error' => 'JSON object expected']);
|
json_response(400, ['ok' => false, 'error' => 'JSON object expected']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$required = ['products', 'totalPrice', 'orderDate', 'paymentMethod', 'billing'];
|
$required = ['products', 'totalPrice', 'orderDate', 'payments', 'billing'];
|
||||||
foreach ($required as $field) {
|
foreach ($required as $field) {
|
||||||
if (!array_key_exists($field, $data)) {
|
if (!array_key_exists($field, $data)) {
|
||||||
json_response(422, ['ok' => false, 'error' => "Missing field: {$field}"]);
|
json_response(422, ['ok' => false, 'error' => "Missing field: {$field}"]);
|
||||||
@@ -40,13 +40,17 @@ foreach ($required as $field) {
|
|||||||
$products = $data['products'];
|
$products = $data['products'];
|
||||||
$totalPrice = parse_number($data['totalPrice']);
|
$totalPrice = parse_number($data['totalPrice']);
|
||||||
$orderDate = trim((string) $data['orderDate']);
|
$orderDate = trim((string) $data['orderDate']);
|
||||||
$paymentMethodCode = trim((string) $data['paymentMethod']);
|
$payments = $data['payments'];
|
||||||
$billing = is_array($data['billing']) ? $data['billing'] : [];
|
$billing = is_array($data['billing']) ? $data['billing'] : [];
|
||||||
|
|
||||||
if (!is_array($products) || count($products) === 0) {
|
if (!is_array($products) || count($products) === 0) {
|
||||||
json_response(422, ['ok' => false, 'error' => 'No products specified']);
|
json_response(422, ['ok' => false, 'error' => 'No products specified']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_array($payments) || count($payments) === 0) {
|
||||||
|
json_response(422, ['ok' => false, 'error' => 'No payment methods specified']);
|
||||||
|
}
|
||||||
|
|
||||||
if ($totalPrice === null || $totalPrice <= 0) {
|
if ($totalPrice === null || $totalPrice <= 0) {
|
||||||
json_response(422, ['ok' => false, 'error' => 'Invalid total price']);
|
json_response(422, ['ok' => false, 'error' => 'Invalid total price']);
|
||||||
}
|
}
|
||||||
@@ -85,6 +89,30 @@ if ($totalQty <= 0) {
|
|||||||
json_response(422, ['ok' => false, 'error' => 'No product quantity specified']);
|
json_response(422, ['ok' => false, 'error' => 'No product quantity specified']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$resolvedPayments = [];
|
||||||
|
$paymentTotal = 0.0;
|
||||||
|
foreach ($payments as $payment) {
|
||||||
|
if (!is_array($payment) || !isset($payment['paymentMethod'], $payment['amount'])) {
|
||||||
|
json_response(422, ['ok' => false, 'error' => 'Payment missing paymentMethod or amount']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentMethodCode = trim((string) $payment['paymentMethod']);
|
||||||
|
$amount = parse_number($payment['amount']);
|
||||||
|
if ($paymentMethodCode === '' || $amount === null || $amount <= 0) {
|
||||||
|
json_response(422, ['ok' => false, 'error' => 'Invalid payment method or amount']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedPayments[] = [
|
||||||
|
'paymentMethodCode' => $paymentMethodCode,
|
||||||
|
'amount' => round((float) $amount, 2),
|
||||||
|
];
|
||||||
|
$paymentTotal = round($paymentTotal + (float) $amount, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs($paymentTotal - (float) $totalPrice) > 0.005) {
|
||||||
|
json_response(422, ['ok' => false, 'error' => 'Payment amounts must equal total price']);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$env = expand_env_values(parse_env_file(__DIR__ . '/../../../../.env'));
|
$env = expand_env_values(parse_env_file(__DIR__ . '/../../../../.env'));
|
||||||
$pdo = connect_database($env);
|
$pdo = connect_database($env);
|
||||||
@@ -92,9 +120,13 @@ try {
|
|||||||
|
|
||||||
$locations = get_default_location_ids($pdo);
|
$locations = get_default_location_ids($pdo);
|
||||||
|
|
||||||
$paymentMethodId = lookup_method_id($pdo, 'payment_method', $paymentMethodCode);
|
$paymentMethodIds = [];
|
||||||
if ($paymentMethodId === null) {
|
foreach ($resolvedPayments as $payment) {
|
||||||
throw new RuntimeException('Invalid payment method');
|
$paymentMethodId = lookup_method_id($pdo, 'payment_method', $payment['paymentMethodCode']);
|
||||||
|
if ($paymentMethodId === null) {
|
||||||
|
throw new RuntimeException('Invalid payment method');
|
||||||
|
}
|
||||||
|
$paymentMethodIds[] = $paymentMethodId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$partyId = find_or_create_party($pdo, $billing);
|
$partyId = find_or_create_party($pdo, $billing);
|
||||||
@@ -102,7 +134,7 @@ try {
|
|||||||
|
|
||||||
$order = create_direct_sales_order($pdo, [
|
$order = create_direct_sales_order($pdo, [
|
||||||
':party_id' => $partyId,
|
':party_id' => $partyId,
|
||||||
':payment_method_id' => $paymentMethodId,
|
':payment_method_id' => count($paymentMethodIds) === 1 ? $paymentMethodIds[0] : null,
|
||||||
':amount_net' => $totalPrice,
|
':amount_net' => $totalPrice,
|
||||||
':total_amount' => $totalPrice,
|
':total_amount' => $totalPrice,
|
||||||
':order_date' => $orderDateTime->format('Y-m-d H:i:s'),
|
':order_date' => $orderDateTime->format('Y-m-d H:i:s'),
|
||||||
@@ -111,6 +143,18 @@ try {
|
|||||||
$orderId = (int) $order['id'];
|
$orderId = (int) $order['id'];
|
||||||
$externalRef = (string) $order['external_ref'];
|
$externalRef = (string) $order['external_ref'];
|
||||||
|
|
||||||
|
$paymentStmt = $pdo->prepare(
|
||||||
|
'INSERT INTO sales_order_payment (sales_order_id, payment_method_id, amount, created_at, updated_at)
|
||||||
|
VALUES (:sales_order_id, :payment_method_id, :amount, NOW(), NOW())'
|
||||||
|
);
|
||||||
|
foreach ($resolvedPayments as $index => $payment) {
|
||||||
|
$paymentStmt->execute([
|
||||||
|
':sales_order_id' => $orderId,
|
||||||
|
':payment_method_id' => $paymentMethodIds[$index],
|
||||||
|
':amount' => $payment['amount'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$remainingTotal = round((float) $totalPrice, 2);
|
$remainingTotal = round((float) $totalPrice, 2);
|
||||||
$lineNo = 0;
|
$lineNo = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -401,16 +401,17 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo '</section>';
|
echo '</section>';
|
||||||
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-payment-title">';
|
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-payment-title">';
|
||||||
echo '<h2 id="otc-payment-title" class="sg-strong sg-form-sections-card__chapter-title">Bezahlung</h2>';
|
echo '<h2 id="otc-payment-title" class="sg-strong sg-form-sections-card__chapter-title">Bezahlung</h2>';
|
||||||
echo '<div class="sg-form-sections-card__field-group">';
|
echo '<div data-otc-payment-rows>';
|
||||||
echo '<label class="sg-label" for="otc-payment-method">Bezahlung</label>';
|
echo '<div class="sg-form-sections-card__field-group" data-otc-payment-row>';
|
||||||
echo '<select class="sg-interaction-element sg-input-single-line" id="otc-payment-method" data-otc-order-payment-method>';
|
echo '<label class="sg-label" for="otc-payment-method-1">Bezahlart</label>';
|
||||||
echo '<option value="twint">Twint</option>';
|
echo '<select class="sg-interaction-element sg-input-single-line" id="otc-payment-method-1" data-otc-payment-method>';
|
||||||
echo '<option value="card">Kreditkarte</option>';
|
echo '<option value="twint">Twint</option><option value="card">Kreditkarte</option><option value="cash">Barzahlung</option><option value="paypal">PayPal</option><option value="bank_transfer">Überweisung</option>';
|
||||||
echo '<option value="cash">Barzahlung</option>';
|
|
||||||
echo '<option value="paypal">PayPal</option>';
|
|
||||||
echo '<option value="bank_transfer">Überweisung</option>';
|
|
||||||
echo '</select>';
|
echo '</select>';
|
||||||
|
echo '<label class="sg-label" for="otc-payment-amount-1">Betrag (CHF)</label>';
|
||||||
|
echo '<input class="sg-interaction-element sg-input-single-line" type="number" id="otc-payment-amount-1" min="0.01" step="0.01" value="0.00" data-otc-payment-amount>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
echo '</div>';
|
||||||
|
echo '<button class="sg-interaction-element sg-button sg-button--active" type="button" data-otc-payment-add>+ Bezahlart hinzufügen</button>';
|
||||||
echo '</section>';
|
echo '</section>';
|
||||||
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-billing-title">';
|
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-billing-title">';
|
||||||
echo '<h2 id="otc-billing-title" class="sg-strong sg-form-sections-card__chapter-title">Rechnungsadresse</h2>';
|
echo '<h2 id="otc-billing-title" class="sg-strong sg-form-sections-card__chapter-title">Rechnungsadresse</h2>';
|
||||||
@@ -1001,7 +1002,8 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " const totalPriceInput = form.querySelector('[data-otc-order-total-price]');";
|
echo " const totalPriceInput = form.querySelector('[data-otc-order-total-price]');";
|
||||||
echo " const orderDateInput = form.querySelector('[data-otc-order-date]');";
|
echo " const orderDateInput = form.querySelector('[data-otc-order-date]');";
|
||||||
echo " const orderDateDefaultValue = orderDateInput ? orderDateInput.value : '';";
|
echo " const orderDateDefaultValue = orderDateInput ? orderDateInput.value : '';";
|
||||||
echo " const paymentMethodInput = form.querySelector('[data-otc-order-payment-method]');";
|
echo " const paymentRows = form.querySelector('[data-otc-payment-rows]');";
|
||||||
|
echo " const addPaymentButton = form.querySelector('[data-otc-payment-add]');";
|
||||||
echo " const firstNameInput = form.querySelector('[data-otc-order-first-name]');";
|
echo " const firstNameInput = form.querySelector('[data-otc-order-first-name]');";
|
||||||
echo " const lastNameInput = form.querySelector('[data-otc-order-last-name]');";
|
echo " const lastNameInput = form.querySelector('[data-otc-order-last-name]');";
|
||||||
echo " const streetInput = form.querySelector('[data-otc-order-street]');";
|
echo " const streetInput = form.querySelector('[data-otc-order-street]');";
|
||||||
@@ -1061,7 +1063,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " const isValid = totalQty > 0";
|
echo " const isValid = totalQty > 0";
|
||||||
echo " && totalPrice > 0";
|
echo " && totalPrice > 0";
|
||||||
echo " && !!getFieldValue(orderDateInput)";
|
echo " && !!getFieldValue(orderDateInput)";
|
||||||
echo " && !!getFieldValue(paymentMethodInput)";
|
echo " && paymentRows.querySelectorAll('[data-otc-payment-row]').length > 0";
|
||||||
echo " && !!getFieldValue(firstNameInput)";
|
echo " && !!getFieldValue(firstNameInput)";
|
||||||
echo " && !!getFieldValue(lastNameInput)";
|
echo " && !!getFieldValue(lastNameInput)";
|
||||||
echo " && !!getFieldValue(streetInput)";
|
echo " && !!getFieldValue(streetInput)";
|
||||||
@@ -1075,8 +1077,8 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " errorMsg = 'Preis muss größer als 0 sein.';";
|
echo " errorMsg = 'Preis muss größer als 0 sein.';";
|
||||||
echo " } else if (!getFieldValue(orderDateInput)) {";
|
echo " } else if (!getFieldValue(orderDateInput)) {";
|
||||||
echo " errorMsg = 'Bestelldatum auswählen.';";
|
echo " errorMsg = 'Bestelldatum auswählen.';";
|
||||||
echo " } else if (!getFieldValue(paymentMethodInput)) {";
|
echo " } else if (getPaymentTotal() !== totalPrice) {";
|
||||||
echo " errorMsg = 'Zahlungsart auswählen.';";
|
echo " errorMsg = 'Die Zahlungsbeträge müssen dem Gesamtbetrag entsprechen.';";
|
||||||
echo " } else if (!getFieldValue(firstNameInput) || !getFieldValue(lastNameInput) || !getFieldValue(streetInput) || !getFieldValue(houseNumberInput) || !getFieldValue(zipInput) || !getFieldValue(cityInput)) {";
|
echo " } else if (!getFieldValue(firstNameInput) || !getFieldValue(lastNameInput) || !getFieldValue(streetInput) || !getFieldValue(houseNumberInput) || !getFieldValue(zipInput) || !getFieldValue(cityInput)) {";
|
||||||
echo " errorMsg = 'Alle Rechnungsadress-Felder ausfüllen.';";
|
echo " errorMsg = 'Alle Rechnungsadress-Felder ausfüllen.';";
|
||||||
echo " }";
|
echo " }";
|
||||||
@@ -1091,6 +1093,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " };";
|
echo " };";
|
||||||
echo " const resetForm = (preserveSuccess = false) => {";
|
echo " const resetForm = (preserveSuccess = false) => {";
|
||||||
echo " form.reset();";
|
echo " form.reset();";
|
||||||
|
echo " Array.from(paymentRows.querySelectorAll('[data-otc-payment-row]')).slice(1).forEach((row) => row.remove());";
|
||||||
echo " clearError();";
|
echo " clearError();";
|
||||||
echo " if (!preserveSuccess) { clearSuccess(); }";
|
echo " if (!preserveSuccess) { clearSuccess(); }";
|
||||||
echo " if (!preserveSuccess) { updateFormState(); }";
|
echo " if (!preserveSuccess) { updateFormState(); }";
|
||||||
@@ -1122,7 +1125,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " toggleOverlay(false);";
|
echo " toggleOverlay(false);";
|
||||||
echo " }";
|
echo " }";
|
||||||
echo " });";
|
echo " });";
|
||||||
echo " [totalPriceInput, orderDateInput, paymentMethodInput, firstNameInput, lastNameInput, streetInput, houseNumberInput, zipInput, cityInput].forEach((input) => {";
|
echo " [totalPriceInput, orderDateInput, firstNameInput, lastNameInput, streetInput, houseNumberInput, zipInput, cityInput].forEach((input) => {";
|
||||||
echo " if (!input) { return; }";
|
echo " if (!input) { return; }";
|
||||||
echo " input.addEventListener('input', updateFormState);";
|
echo " input.addEventListener('input', updateFormState);";
|
||||||
echo " input.addEventListener('change', updateFormState);";
|
echo " input.addEventListener('change', updateFormState);";
|
||||||
@@ -1131,6 +1134,23 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " input.addEventListener('input', updateFormState);";
|
echo " input.addEventListener('input', updateFormState);";
|
||||||
echo " input.addEventListener('change', updateFormState);";
|
echo " input.addEventListener('change', updateFormState);";
|
||||||
echo " });";
|
echo " });";
|
||||||
|
echo " const getPaymentTotal = () => Math.round(Array.from(paymentRows.querySelectorAll('[data-otc-payment-amount]')).reduce((sum, input) => sum + (parseFloat(input.value || '0') || 0), 0) * 100) / 100;";
|
||||||
|
echo " const bindPaymentRow = (row) => {";
|
||||||
|
echo " row.querySelectorAll('select, input').forEach((input) => { input.addEventListener('input', updateFormState); input.addEventListener('change', updateFormState); });";
|
||||||
|
echo " };";
|
||||||
|
echo " const paymentOptions = '<option value=\"twint\">Twint</option><option value=\"card\">Kreditkarte</option><option value=\"cash\">Barzahlung</option><option value=\"paypal\">PayPal</option><option value=\"bank_transfer\">Überweisung</option>';";
|
||||||
|
echo " const addPaymentRow = () => {";
|
||||||
|
echo " const number = paymentRows.querySelectorAll('[data-otc-payment-row]').length + 1;";
|
||||||
|
echo " const row = document.createElement('div');";
|
||||||
|
echo " row.className = 'sg-form-sections-card__field-group';";
|
||||||
|
echo " row.dataset.otcPaymentRow = '';";
|
||||||
|
echo " row.innerHTML = '<label class=\"sg-label\" for=\"otc-payment-method-' + number + '\">Bezahlart</label><select class=\"sg-interaction-element sg-input-single-line\" id=\"otc-payment-method-' + number + '\" data-otc-payment-method>' + paymentOptions + '</select><label class=\"sg-label\" for=\"otc-payment-amount-' + number + '\">Betrag (CHF)</label><input class=\"sg-interaction-element sg-input-single-line\" type=\"number\" id=\"otc-payment-amount-' + number + '\" min=\"0.01\" step=\"0.01\" value=\"0.00\" data-otc-payment-amount>';";
|
||||||
|
echo " paymentRows.appendChild(row);";
|
||||||
|
echo " bindPaymentRow(row);";
|
||||||
|
echo " updateFormState();";
|
||||||
|
echo " };";
|
||||||
|
echo " addPaymentButton.addEventListener('click', addPaymentRow);";
|
||||||
|
echo " bindPaymentRow(paymentRows.querySelector('[data-otc-payment-row]'));";
|
||||||
echo " form.addEventListener('submit', async (event) => {";
|
echo " form.addEventListener('submit', async (event) => {";
|
||||||
echo " event.preventDefault();";
|
echo " event.preventDefault();";
|
||||||
echo " let submissionSucceeded = false;";
|
echo " let submissionSucceeded = false;";
|
||||||
@@ -1142,7 +1162,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " products: products,";
|
echo " products: products,";
|
||||||
echo " totalPrice: parseFloat(totalPriceInput.value || '0') || 0,";
|
echo " totalPrice: parseFloat(totalPriceInput.value || '0') || 0,";
|
||||||
echo " orderDate: orderDateInput ? orderDateInput.value : '',";
|
echo " orderDate: orderDateInput ? orderDateInput.value : '',";
|
||||||
echo " paymentMethod: paymentMethodInput ? paymentMethodInput.value : '',";
|
echo " payments: Array.from(paymentRows.querySelectorAll('[data-otc-payment-row]')).map((row) => ({ paymentMethod: row.querySelector('[data-otc-payment-method]').value, amount: parseFloat(row.querySelector('[data-otc-payment-amount]').value || '0') || 0 })),";
|
||||||
echo " billing: {";
|
echo " billing: {";
|
||||||
echo " firstName: getFieldValue(firstNameInput),";
|
echo " firstName: getFieldValue(firstNameInput),";
|
||||||
echo " lastName: getFieldValue(lastNameInput),";
|
echo " lastName: getFieldValue(lastNameInput),";
|
||||||
|
|||||||
Reference in new issue
Block a user