"Add auto-apply logic for pending bookings in PosDepartmentStepMobile2.vue: implement booking selection, metadata updates, primary/addon mapping, and watchers for booking/vehicle changes."

This commit is contained in:
Jeppe Bundgaard
2025-11-20 16:08:19 +01:00
parent f790532eae
commit b9e752444b
@@ -31,6 +31,7 @@ import PosDepartmentStepMobile2AdditionalItems
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2AdditionalItems.vue";
import PosDepartmentStepMobile2Customer
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Customer.vue";
import { pendingBookings, getVehiclePlateBooking } from "@/components/shop/POSDepartmentProcess.vue";
onMounted(() => {
@@ -61,6 +62,8 @@ onMounted(() => {
// Watch for scroll events to handle long press detection
window.addEventListener('scroll', onScroll, true);
// Try to auto-apply pending booking when Step 2 opens
applyPendingBookingFromSelection();
});
const applyPotentialCustomerNumberChange = (oldId, newId) => {
@@ -77,6 +80,123 @@ watch(customer_id, (newId, oldId) => {
const lastFetchedPrimaryItemProduct = ref<PosProduct | null>(null); // This is used to make sure all the addons are fetched when the last order is selected.
// Keep track so we only apply a booking once (idempotent)
const lastAppliedBookingId = ref<number | null>(null);
const getSelectedPendingBooking = () => {
// Prefer explicit booking id if present
const bookingId = metadata.getBookingId?.() ?? metadata.bookingId?.value ?? null;
let booking = null as any;
if (bookingId) {
booking = pendingBookings.value?.find?.((b: any) => b?.id === bookingId) || null;
if (booking) return booking;
}
// Fallback: try match current primary vehicle registration
const reg = vehicles?.vehicle_1?.value?.reg || null;
if (reg) {
try {
booking = getVehiclePlateBooking(reg);
} catch (e) {
booking = null;
}
}
return booking || null;
}
const fetchProductById = async (id: number): Promise<PosProduct> => {
// Fetch product with final price for current department/customer. Fallback to a minimal shape.
try {
// Note: metadata.getCustomerId exists in flow; fallback to global customer_id if missing
const custId = (metadata.getCustomerId && metadata.getCustomerId()) || (customer_id?.value ? parseInt(customer_id.value as any) : null);
const product = await SessionUser.objects.products.get.single(id, {
department_id: department_id.value,
customer_id: custId,
category_id: null,
final_price: true
});
return product as PosProduct;
} catch (e) {
console.error('Failed to fetch product', id, e);
// Minimal fallback; name/price will be set by caller when available
return { id, name: 'Produkt', price: 0, subscription_allowed: false } as PosProduct;
}
}
const applyPendingBookingFromSelection = async () => {
const booking: any = getSelectedPendingBooking();
if (!booking) return;
if (lastAppliedBookingId.value === booking.id) return; // avoid duplicate application
try {
// Update metadata booking id if not set
if (!metadata.getBookingId || !metadata.getBookingId()) {
metadata.setBookingId && metadata.setBookingId(booking.id);
}
// Set PO number on the order if provided by the booking
if (booking.po && typeof booking.po === 'string' && booking.po.trim() !== '') {
try {
await SessionUser.objects.orders.set.po(order_id.value, booking.po);
console.warn('Applied PO from booking:', booking.po);
} catch (e) {
console.error('Failed to set PO on order from booking', e);
}
}
// Map booking items to primary + addons
const items: any[] = Array.isArray(booking.items) ? booking.items : [];
if (!items.length) {
lastAppliedBookingId.value = booking.id;
return;
}
// Primary product is the first item
const primaryRaw = items[0];
const primaryId = parseInt(primaryRaw?.id);
if (!primaryId || isNaN(primaryId)) {
lastAppliedBookingId.value = booking.id;
return;
}
const primaryProduct = await fetchProductById(primaryId);
// Prefer booking price if provided, otherwise keep fetched price
if (typeof primaryRaw.price === 'number') primaryProduct.price = primaryRaw.price;
primaryProduct.name = primaryRaw?.name || primaryProduct.name;
primaryProduct.quantity = 1; // Primary item quantity is handled as 1 in order creation
// Apply as primary item
transactionItems.setPrimaryItem(primaryProduct as any);
// Remaining items become addons
const addonRawList = items.slice(1);
const addonProducts = await Promise.all(addonRawList.map(async (raw: any) => {
const id = parseInt(raw?.id);
if (!id || isNaN(id)) return null;
const p = await fetchProductById(id);
if (typeof raw.price === 'number') p.price = raw.price;
p.name = raw?.name || p.name;
p.quantity = raw?.quantity || 1;
return p;
}));
const preparedAddons = addonProducts
.filter(p => !!p)
.map(p => transactionItems.convertProductToAddon(p as any, { quantity: (p as any)?.quantity || 1 }));
if (!transactionItems.primaryItem.value) {
// Safety: if primary was cleared/changed while awaiting fetches, set again
transactionItems.setPrimaryItem(primaryProduct as any);
}
// Attach addons to primary
if (transactionItems.primaryItem.value) {
// Keep any existing addons that may have been set previously only if they are different.
transactionItems.primaryItem.value.addons = preparedAddons as any;
}
lastAppliedBookingId.value = booking.id;
console.warn('Applied pending booking to cart (primary + addons):', booking.id);
} catch (e) {
console.error('Failed to apply pending booking to Step 2', e);
}
}
const fetchPrimaryItemProduct = () => {
if (!vehicles.vehicle_1.value.type && !transactionItems.primaryItem.value) {
vehicleSelection.value = true; // Force the user to select a product.
@@ -324,6 +444,14 @@ const onBeforeComplete = () => {
});
}
// Re-try applying booking whenever booking id or vehicle reg changes
watch(() => metadata.bookingId?.value, () => {
applyPendingBookingFromSelection();
});
watch(() => vehicles?.vehicle_1?.value?.reg, () => {
applyPendingBookingFromSelection();
});
// Watch for changes in the primary item and update the vehicle selection if necessary
// Utility: map source addons and carry over quantities from previous addons by product.id
const mapAddonsWithQuantity = (sourceAddons = [], previousAddons = []) =>