From be0dfb7f636171b6303315fad860cc907a415d8f Mon Sep 17 00:00:00 2001 From: thanawat saiyota Date: Fri, 17 Jul 2026 12:39:56 +0700 Subject: [PATCH] add: New Gen Price & Gen PriceSlot --- src/lib/components/gen-price-panel.svelte | 246 ++++++++++++++++++ src/lib/core/handlers/messageHandler.ts | 19 +- src/lib/core/services/sheetService.ts | 75 +++++- src/lib/core/stores/genLayoutStore.ts | 25 +- .../sheet/price/[country]/+page.svelte | 7 + 5 files changed, 361 insertions(+), 11 deletions(-) create mode 100644 src/lib/components/gen-price-panel.svelte diff --git a/src/lib/components/gen-price-panel.svelte b/src/lib/components/gen-price-panel.svelte new file mode 100644 index 0000000..013dda6 --- /dev/null +++ b/src/lib/components/gen-price-panel.svelte @@ -0,0 +1,246 @@ + + +
+
+
+

{label}

+

+ Generate and push {mode === 'price' ? 'price' : 'price-slot'} profile files to Machine +

+
+
+ + + {#if isOurs && status === 'complete' && files.length > 0} + + {/if} + + +
+
+ + {#if isOurs && status === 'receiving'} +
+ 0 + ? (files.length / $genLayoutBatch.total_files) * 100 + : 0} + max={100} + class="h-2" + /> +

+ Receiving files: {files.length} / {$genLayoutBatch.total_files} +

+
+ {/if} + + {#if pushing} +
+ 0 ? (pushProgress.current / pushProgress.total) * 100 : 0} + max={100} + class="h-2" + /> +

+ Pushing to Android: {pushProgress.current} / {pushProgress.total} +

+
+ {/if} + + {#if isOurs && status === 'complete' && files.length > 0} +
+

Generated {files.length} file(s):

+
+ {#each files as file} +
+ {file.file.split('/').pop()} +
+ {/each} +
+
+ {/if} + + {#if isOurs && status === 'error'} +
+

{$genLayoutBatch.error}

+
+ {/if} +
+ + + + + Reboot Machine? + + New {mode} prices only take effect after a full device reboot. The machine + will restart — this takes a few minutes. + + +
+ + +
+
+
diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts index 276a71f..a43efb3 100644 --- a/src/lib/core/handlers/messageHandler.ts +++ b/src/lib/core/handlers/messageHandler.ts @@ -261,14 +261,23 @@ const handlers: Record void> = { // Handle gen-service responses if (from === 'gen-service') { switch (level) { - case 'batch_start': + case 'batch_start': { + const genRef = p.ref ?? 'gen-layout'; handleGenLayoutBatchStart({ batch_id: p.batch_id, total_files: p.total_files, - total_size_bytes: p.total_size_bytes + total_size_bytes: p.total_size_bytes, + ref: genRef }); - addNotification(`INFO:Gen Layout started (${p.total_files} files)`); + const genLabel = + genRef === 'gen-price' + ? 'Gen Price' + : genRef === 'gen-priceslot' + ? 'Gen PriceSlot' + : 'Gen Layout'; + addNotification(`INFO:${genLabel} started (${p.total_files} files)`); break; + } case 'file': handleGenLayoutFile({ batch_id: p.batch_id, @@ -287,11 +296,11 @@ const handlers: Record void> = { batch_id: p.batch_id, total_files: p.total_files }); - addNotification('INFO:Gen Layout complete'); + addNotification('INFO:Generation complete'); break; case 'ERROR': handleGenLayoutError(msg); - addNotification(`ERR:Gen Layout error: ${msg}`); + addNotification(`ERR:Generation error: ${msg}`); break; default: logger.info('[GenService] Received:', level, msg); diff --git a/src/lib/core/services/sheetService.ts b/src/lib/core/services/sheetService.ts index 2034fb7..7cb0d05 100644 --- a/src/lib/core/services/sheetService.ts +++ b/src/lib/core/services/sheetService.ts @@ -325,7 +325,7 @@ export async function requestGenLayout(country: string): Promise { }; } - setGenLayoutGenerating(); + setGenLayoutGenerating('gen-layout'); logger.info('[sheetService] Sending gen-layout request for country:', country); @@ -344,6 +344,79 @@ export async function requestGenLayout(country: string): Promise { }); } +/** + * Trigger gen-service to (re)generate the machine price profile from Grist. + * Backend: taobin_admin app.py `gen-price` branch -> gen_price_supra.gen_price. + * Produces profile__master.json, commits to recipe-repo, streams files back + * (ref='gen-price') for the frontend to push to the machine. + */ +export async function requestGenPrice(country: string): Promise { + const 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 + }; + } + + setGenLayoutGenerating('gen-price'); + + logger.info('[sheetService] Sending gen-price request for country:', country); + + return await sendMessage({ + type: 'command', + payload: { + user_info, + srv_name: 'gen-service', + values: { + file_layout: 'sheet', + file_desc: 'sheet', + country: country, + param: 'gen-price-supra_app' + } + } + }); +} + +/** + * Trigger gen-service to (re)generate the per-slot price profiles from Grist. + * Backend: taobin_admin app.py `gen-priceslot` branch -> gen_price_supra.gen_priceslot. + * Produces profile__slot_.json (ref='gen-priceslot'). + */ +export async function requestGenPriceSlot(country: string): Promise { + const 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 + }; + } + + setGenLayoutGenerating('gen-priceslot'); + + logger.info('[sheetService] Sending gen-priceslot request for country:', country); + + return await sendMessage({ + type: 'command', + payload: { + user_info, + srv_name: 'gen-service', + values: { + file_layout: 'sheet', + file_desc: 'sheet', + country: country, + param: 'gen-priceslot-supra_app' + } + } + }); +} + /** * Request price data from sheet for specific product codes * NOTE: Can only send once per type (price). Use hasSheetPriceBeenSent to check. diff --git a/src/lib/core/stores/genLayoutStore.ts b/src/lib/core/stores/genLayoutStore.ts index d0a5e7c..ccf811c 100644 --- a/src/lib/core/stores/genLayoutStore.ts +++ b/src/lib/core/stores/genLayoutStore.ts @@ -7,12 +7,20 @@ export interface GenLayoutFile { file_index: number; } +// Kind of gen-service batch. Distinguishes which files were generated so the +// push logic can map them to the correct machine path. +// gen-layout -> layout files under taobin_project/ +// gen-price -> profile__master.json -> /sdcard/coffeevending/profile/ +// gen-priceslot -> profile__slot_.json -> .../price/ +export type GenKind = 'gen-layout' | 'gen-price' | 'gen-priceslot'; + export interface GenLayoutBatch { batch_id: string; total_files: number; total_size_bytes: number; status: 'idle' | 'generating' | 'receiving' | 'complete' | 'error'; files: GenLayoutFile[]; + ref: GenKind; error?: string; } @@ -29,7 +37,8 @@ const initialState: GenLayoutBatch = { total_files: 0, total_size_bytes: 0, status: 'idle', - files: [] + files: [], + ref: 'gen-layout' }; export const genLayoutBatch = writable(initialState); @@ -52,15 +61,17 @@ export function handleGenLayoutBatchStart(payload: { batch_id: string; total_files: number; total_size_bytes: number; + ref?: GenKind; }) { genLayoutBatch.set({ batch_id: payload.batch_id, total_files: payload.total_files, total_size_bytes: payload.total_size_bytes, status: 'receiving', - files: [] + files: [], + ref: payload.ref ?? get(genLayoutBatch).ref ?? 'gen-layout' }); - logger.info('[GenLayout] Batch started:', payload.batch_id, 'total files:', payload.total_files); + logger.info('[GenLayout] Batch started:', payload.batch_id, 'ref:', payload.ref, 'total files:', payload.total_files); } export function handleGenLayoutFile(payload: { @@ -79,7 +90,10 @@ export function handleGenLayoutFile(payload: { if (payload.is_chunked) { const fileIndex = payload.file_index; - const partIndex = payload.part_index ?? 0; + // Backend sends part_index 1-based (1..N); normalize to 0-based so it + // matches the 0..totalParts-1 assembly loop below. Without this, part 1 + // was stored empty and the last chunk was dropped (files > 1MB corrupted). + const partIndex = (payload.part_index ?? 1) - 1; const totalParts = payload.total_parts ?? 1; // Get or create tracker for this file @@ -242,9 +256,10 @@ export function resetGenLayoutBatch() { chunkedFiles.clear(); } -export function setGenLayoutGenerating() { +export function setGenLayoutGenerating(ref: GenKind = 'gen-layout') { genLayoutBatch.update((batch) => ({ ...batch, + ref, status: 'generating' })); } diff --git a/src/routes/(authed)/sheet/price/[country]/+page.svelte b/src/routes/(authed)/sheet/price/[country]/+page.svelte index 3667b69..96a1a8a 100644 --- a/src/routes/(authed)/sheet/price/[country]/+page.svelte +++ b/src/routes/(authed)/sheet/price/[country]/+page.svelte @@ -24,6 +24,7 @@ import * as Table from '$lib/components/ui/table/index.js'; import Spinner from '$lib/components/ui/spinner/spinner.svelte'; import { Plus, RefreshCw, Save } from '@lucide/svelte/icons'; + import GenPricePanel from '$lib/components/gen-price-panel.svelte'; let selectedCountry = $state($page.params.country || get(departmentStore) || ''); let search = $state(''); @@ -211,6 +212,12 @@ + {#if selectedCountry} +
+ +
+ {/if} +