From 07977ce8967f75ccfe7896e6857767386416abf6 Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Tue, 26 May 2026 17:08:44 +0700 Subject: [PATCH] change: wip testing price from sheet Signed-off-by: pakintada@gmail.com --- .../recipe-details/recipe-detail.svelte | 33 +++--- .../components/recipe-editor-dialog.svelte | 33 +++++- src/lib/core/handlers/adbPayloadHandler.ts | 14 +++ src/lib/core/handlers/messageHandler.ts | 100 +++++++++++++++++- src/lib/core/handlers/sheetNotiHandler.ts | 68 ++++++++++++ src/lib/core/stores/recipeStore.ts | 11 ++ src/lib/core/types/outMessage.ts | 3 +- src/lib/helpers/formatDate.ts | 18 ++++ src/routes/(authed)/tools/brew/+page.svelte | 6 +- 9 files changed, 268 insertions(+), 18 deletions(-) create mode 100644 src/lib/core/handlers/sheetNotiHandler.ts create mode 100644 src/lib/helpers/formatDate.ts diff --git a/src/lib/components/recipe-details/recipe-detail.svelte b/src/lib/components/recipe-details/recipe-detail.svelte index b1100ea..6e68b03 100644 --- a/src/lib/components/recipe-details/recipe-detail.svelte +++ b/src/lib/components/recipe-details/recipe-detail.svelte @@ -5,6 +5,7 @@ import Input from '$lib/components/ui/input/input.svelte'; import Button from '$lib/components/ui/button/button.svelte'; import Spinner from '$lib/components/ui/spinner/spinner.svelte'; + import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte'; import { Badge } from '$lib/components/ui/badge/index'; import * as Item from '$lib/components/ui/item/index'; import { createEventDispatcher, onDestroy, onMount } from 'svelte'; @@ -265,22 +266,28 @@ -
- - - - +
+
+ + +
+ +
+ + +
-
+
- - + +
+ + +
+ + Disabled +
diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte index c80a281..995bb37 100644 --- a/src/lib/components/recipe-editor-dialog.svelte +++ b/src/lib/components/recipe-editor-dialog.svelte @@ -30,6 +30,7 @@ machineInfoStore, updateMachineStatus } from '$lib/core/stores/machineInfoStore'; + import { formatCustomDate } from '$lib/helpers/formatDate'; const isDesktop = new MediaQuery('(min-width: 768px)'); @@ -125,7 +126,7 @@ ready_to_send_brew.push(new_change); - callback_revert_value_if_not_save = (save: any) => { + callback_revert_value_if_not_save = async (save: any) => { if (!save) { latestRecipeToppingData.set(topping_value_for_revert); console.log('revert change', get(latestRecipeToppingData)); @@ -141,6 +142,16 @@ // currentData['ToppingSet'] = latestRecipeToppingData; // console.log('current data', currentData); + let curr_user = get(auth); + + let user_info: any; + if (curr_user) { + user_info = { + displayName: curr_user.displayName, + email: curr_user.email, + uid: curr_user.uid + }; + } if (get(referenceFromPage) == 'brew') { // send change to machine @@ -164,11 +175,29 @@ data: ready_to_send_brew[0] } }); + + let country = await adb.pull('/sdcard/coffeevending/country/short'); + let box_id = await adb.pull('/sdcard/coffeevending/.bid'); + + // update last change + // format 16-Feb-2026 10:31:18 + let date = new Date(); + let formatted = formatCustomDate(date); + ready_to_send_brew[0].LastChange = formatted; + + sendMessage({ + type: 'save_recipe', + payload: { + user_info, + country: `${country?.toLowerCase() ?? 'unknown'}_${box_id ?? ''}`, + values: ready_to_send_brew[0] + } + }); } else if (get(referenceFromPage) == 'overview') { sendMessage({ type: 'save_recipe', payload: { - user: get(auth)?.displayName ?? 'unknown', + user_info, country: get(departmentStore) ?? 'unknown', values: currentData } diff --git a/src/lib/core/handlers/adbPayloadHandler.ts b/src/lib/core/handlers/adbPayloadHandler.ts index 63cb669..2c84e6d 100644 --- a/src/lib/core/handlers/adbPayloadHandler.ts +++ b/src/lib/core/handlers/adbPayloadHandler.ts @@ -1,6 +1,8 @@ +import { get } from 'svelte/store'; import { updateMachineStatus } from '../stores/machineInfoStore'; import { addNotification } from '../stores/noti'; import { handleIncomingMessages } from './messageHandler'; +import { recipeFromMachineQuery } from '../stores/recipeStore'; type AdbPayload = { type: string; payload: any }; @@ -73,9 +75,21 @@ async function handleAdbPayload(raw_payload: string) { let plist = payload.payload.split('/'); let pd = plist[0] ?? ''; let total_time = plist[1] ?? ''; + let mode_ref = plist[2] ?? ''; // update recipe data store console.log('brewing finish', pd, 'total time', total_time); + + // update recipe from brew now + let recipeDevSnapshot = get(recipeFromMachineQuery) ?? {}; + let recipe01Snap = recipeDevSnapshot['recipe']; + if (recipe01Snap) { + recipe01Snap[pd].total_time = + mode_ref != 'sim' ? total_time : recipe01Snap[pd].total_time; + + recipeDevSnapshot['recipe'] = recipe01Snap; + recipeFromMachineQuery.set(recipeDevSnapshot); + } } break; diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts index c7e2088..c7b3d50 100644 --- a/src/lib/core/handlers/messageHandler.ts +++ b/src/lib/core/handlers/messageHandler.ts @@ -2,6 +2,7 @@ import { get, writable } from 'svelte/store'; import { addNotification, notiStore } from '../stores/noti'; import { currentRecipeVersionsSelector, + lastRequestSheetPrice, materialFromServerQuery, priceRecipeData, recipeData, @@ -9,6 +10,8 @@ import { recipeLoading, recipeOverviewData, recipeStreamMeta, + streamingRawData, + streamingRawMeta, toppingGroupFromServerQuery, toppingListFromServerQuery } from '../stores/recipeStore'; @@ -18,13 +21,16 @@ import { type RecipeVersion } from '$lib/models/recipe_version.model'; import { goto } from '$app/navigation'; import { socketAlreadySendHeartbeat, socketConnectionOfflineCount } from '../stores/websocketStore'; import type { RecipePrice } from '$lib/models/price.model'; -import { sendMessage } from './ws_messageSender'; +import { sendCommandRequest, sendMessage } from './ws_messageSender'; import { auth as authStore } from '../stores/auth'; +import { v4 as uuidv4 } from 'uuid'; +import { handleSheetResponseFromNoti } from './sheetNotiHandler'; export const messages = writable([]); type WSMessage = { type: string; payload: any }; +// MAXIMUM LIMIT = 1814355 const handlers: Record void> = { chat: (p) => messages.update((m) => [...m, p]), ping: (p) => console.log('ping from server'), @@ -206,6 +212,9 @@ const handlers: Record void> = { let msg = p.msg ?? `Notify from ${p.from}`; let target = p.to; + let from_service = p.from ?? ''; + let ref_service = p.ref ?? ''; + if (target) { // let currentUsername = auth.currentUser?.displayName; @@ -249,11 +258,91 @@ const handlers: Record void> = { console.log('get price length: ', content.length); let current_price = get(priceRecipeData); + let lastRequestPriceInstance = get(lastRequestSheetPrice); + let saved_product_code_to_get_from_sheet = []; + let current_meta = get(recipeStreamMeta); + lastRequestPriceInstance[current_meta?.country ?? 'unknown'] = {}; for (const c of content) { current_price[c.ProductCode] = c.NewPrice + (c.StringParam ? `,${c.StringParam}` : ''); + lastRequestPriceInstance[current_meta?.country ?? 'unknown'][c.ProductCode] = ''; + saved_product_code_to_get_from_sheet.push({ + product_code: c.ProductCode + }); } priceRecipeData.set(current_price); + + console.log('check length', saved_product_code_to_get_from_sheet.length); + // set command request to stream mode so + let request_id = uuidv4(); + + lastRequestPriceInstance[request_id] = current_meta?.country ?? ''; + let current_streaming_instance = get(streamingRawData); + current_streaming_instance[request_id] = ''; + streamingRawData.set(current_streaming_instance); + + sendCommandRequest('sheet', { + country: current_meta?.country ?? '', + content: saved_product_code_to_get_from_sheet, + param: 'price', + stream: true, + request_id + }); + + lastRequestSheetPrice.set(lastRequestPriceInstance); + }, + raw_stream: (p) => { + let streamRawInstance = get(streamingRawData); + let sub_type = p.sub_type; + let request_id = p.request_id; + let size_per_chunk = p.size_per_chunk; + let total_chunks = p.total_chunks; + let idx = p.idx; + + switch (sub_type) { + case 'price': + streamingRawMeta.set({ + id: request_id, + total_size: total_chunks, + chunk_size: size_per_chunk, + progress: 0 + }); + break; + case 'chunk_price': + streamingRawMeta.set({ + id: request_id, + total_size: total_chunks, + chunk_size: size_per_chunk, + progress: idx + }); + + let raw_payload = p.raw ?? ''; + streamRawInstance[request_id] += raw_payload; + streamingRawData.set(streamRawInstance); + + break; + case 'end_price': + let lastRequestPriceInstance = get(lastRequestSheetPrice); + let country = lastRequestPriceInstance[request_id]; + + try { + let raw_payload = JSON.parse(streamRawInstance[request_id]); + let ref_from_raw = raw_payload.ref ?? ''; + let from_service_raw = raw_payload.from ?? ''; + let parsed_payload = raw_payload.payload ?? ''; + + if (from_service_raw == 'sheet-service') { + handleSheetResponseFromNoti(parsed_payload, ref_from_raw, country); + delete streamRawInstance[request_id]; + streamingRawData.set(streamRawInstance); + } + } catch (e) { + console.log(`end price process error: ${e}`); + } + + break; + default: + } }, heartbeat: (p) => { socketConnectionOfflineCount.set(0); @@ -270,5 +359,14 @@ export function handleIncomingMessages(raw: string) { addNotification('ERR:No response from server'); return; } + + // raw streaming type + if (msg.type.startsWith('raw_stream')) { + // convert + let sub_type = msg.type.replace('raw_stream_', ''); + msg.payload.sub_type = sub_type; + msg.type = 'raw_stream'; + } + handlers[msg.type]?.(msg.payload); } diff --git a/src/lib/core/handlers/sheetNotiHandler.ts b/src/lib/core/handlers/sheetNotiHandler.ts new file mode 100644 index 0000000..a51b1ba --- /dev/null +++ b/src/lib/core/handlers/sheetNotiHandler.ts @@ -0,0 +1,68 @@ +import { get } from 'svelte/store'; +import { lastRequestSheetPrice } from '../stores/recipeStore'; + +export interface PayloadFromSheet { + header: string[]; + key: string; + payload: GristCell[]; +} + +export interface GristCell { + cells: { + coord: { + col: number; + row: number; + }; + value: string; + }[]; + row_index: number; +} + +const PRICE_SHEET_DEFINITION_BY_COUNTRY: any = { + ltu: { + expect_header: ['Name', 'Price in Euro'], + get_header_idx: (x: string[]) => { + let result = []; + for (const header of PRICE_SHEET_DEFINITION_BY_COUNTRY['ltu'].expect_header) { + let found = x.findIndex((y) => y == header); + result.push(found); + } + + return result; + } + } +}; + +export function handleSheetResponseFromNoti(raw_payload: any, ref: string, country?: string) { + switch (ref) { + case 'price': + let price_contents: PayloadFromSheet[] = raw_payload.content; + let header_idx = PRICE_SHEET_DEFINITION_BY_COUNTRY[country ?? 'unknown'].get_header_idx( + price_contents[0].header + ); + + let lastRequestSheetInstance = get(lastRequestSheetPrice); + let products = lastRequestSheetInstance[country ?? 'unknown']; + + for (let c of price_contents) { + let curr_product_code = c.key; + // price idx should be last + let price_idx = header_idx[header_idx.length - 1]; + let price_rows = c.payload; + // get last because last row will always override + let expected_row = price_rows[price_rows.length - 1]; + let price_col = expected_row.cells[price_idx]; + products[curr_product_code] = price_col; + console.log(`[handleSheetPrice][country] ${curr_product_code} --> ${price_col}`); + } + + lastRequestSheetInstance[country ?? 'unknown'] = products; + lastRequestSheetPrice.set({ + ...lastRequestSheetInstance, + products + }); + + break; + default: + } +} diff --git a/src/lib/core/stores/recipeStore.ts b/src/lib/core/stores/recipeStore.ts index 0a97cc2..ac21a3a 100644 --- a/src/lib/core/stores/recipeStore.ts +++ b/src/lib/core/stores/recipeStore.ts @@ -23,6 +23,17 @@ export const recipeOverviewData = writable(null); export const materialData = writable(); // price from recipe repo export const priceRecipeData = writable<{ [key: string]: any }>({}); +export const lastRequestSheetPrice = writable<{ [key: string]: any }>({}); + +// Streaming raw +export const streamingRawData = writable<{ [key: string]: any }>({}); +export const streamingRawMeta = writable<{ + id: string; + total_size: number; + chunk_size: number; + progress: number; + country?: string; +} | null>(null); // machine recipe export const recipeFromMachine = writable(null); diff --git a/src/lib/core/types/outMessage.ts b/src/lib/core/types/outMessage.ts index afe8c23..bd14e6e 100644 --- a/src/lib/core/types/outMessage.ts +++ b/src/lib/core/types/outMessage.ts @@ -36,9 +36,10 @@ export type OutMessage = | { type: 'save_recipe'; payload: { - user: string; + user_info: any; country: string; values: any; + plugins?: string; }; } | { diff --git a/src/lib/helpers/formatDate.ts b/src/lib/helpers/formatDate.ts new file mode 100644 index 0000000..9015264 --- /dev/null +++ b/src/lib/helpers/formatDate.ts @@ -0,0 +1,18 @@ +export function formatCustomDate(date: Date): string { + const formatter = new Intl.DateTimeFormat('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + + // Extract all the formatted parts into an object + const parts = formatter.formatToParts(date); + const partMap = Object.fromEntries(parts.map((p) => [p.type, p.value])); + + // Construct your exact string: 16-Feb-2026 10:31:18 + return `${partMap.day}-${partMap.month}-${partMap.year} ${partMap.hour}:${partMap.minute}:${partMap.second}`; +} diff --git a/src/routes/(authed)/tools/brew/+page.svelte b/src/routes/(authed)/tools/brew/+page.svelte index 2dfaf9e..1bbba35 100644 --- a/src/routes/(authed)/tools/brew/+page.svelte +++ b/src/routes/(authed)/tools/brew/+page.svelte @@ -53,7 +53,11 @@ if (instance) { console.log('instance passed!'); let dev_recipe = await adb.pull(`${sourceDir}/cfg/recipe_branch_dev.json`); - console.log('dev recipe ok', dev_recipe != undefined, dev_recipe); + console.log('dev recipe ok', dev_recipe != undefined); + if (dev_recipe == undefined || dev_recipe == null || dev_recipe?.length == 0) { + dev_recipe = await adb.pull(`${sourceDir}/coffeethai02.json`); + console.log('dev recipe ok by production', dev_recipe != undefined); + } if (dev_recipe) { if (dev_recipe.length == 0) { // case error, do last retry