Supra_App/src/lib/core/services/sheetService.ts
2026-07-01 08:53:13 +07:00

507 lines
12 KiB
TypeScript

import { logger } from '$lib/core/utils/logger';
import { sendCommandRequest, sendMessage } from '../handlers/ws_messageSender';
import { get } from 'svelte/store';
import { auth } from '../stores/auth';
import {
productCodesLoading,
hasSheetPriceBeenSent,
markSheetPriceAsSent,
sheetPriceLoading,
streamingRawData,
setPendingProductCodesCountry,
setPendingPriceSlotsCountry,
priceSlotsLoading,
resetPriceSlotsCountry
} from '../stores/sheetStore';
import type { PriceSlot } from '../stores/sheetStore';
import { setGenLayoutGenerating } from '../stores/genLayoutStore';
type SheetCellUpdate = { value: string; coord: { row: number; col: number } };
type SheetRowUpdate = { row_index: number; cells: SheetCellUpdate[] };
type SheetRowCreate = { header?: string[]; cells: string[] };
export async function requestCatalogs(country: string): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
param: 'catalogs'
});
}
/**
* Register a newly created catalog as a Grist table so it shows in the overview
* and menus can be added to it. `catalog` is the .skt filename produced by
* /api/catalog-create (e.g. "page_catalog_group_pro_summer_splash.skt").
*/
export async function addCatalog(
country: string,
catalogName: string,
catalog: string
): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
catalog_name: catalogName,
param: 'add/catalog'
});
}
export async function requestPriceSlots(country: string): Promise<boolean> {
setPendingPriceSlotsCountry(country);
resetPriceSlotsCountry(country);
return requestPriceSlotOption(country, 'PriceSlot');
}
export async function requestPriceSlot(country: string, slotNumber: number): Promise<boolean> {
setPendingPriceSlotsCountry(country);
return requestPriceSlotOption(country, `PriceSlot${slotNumber}`);
}
async function requestPriceSlotOption(country: string, option: string): Promise<boolean> {
const request_id = crypto.randomUUID();
streamingRawData.update((data) => ({
...data,
priceslot: {
request_id,
country,
chunks: [],
rawParts: []
}
}));
priceSlotsLoading.set(true);
const values = {
country: country,
param: 'price',
option,
stream: true,
request_id
};
console.log('[sheetService] Sending PriceSlot request:', values);
const sent = await sendCommandRequest('sheet', values);
console.log('[sheetService] PriceSlot request sent:', sent);
if (!sent) {
priceSlotsLoading.set(false);
}
return sent;
}
export async function refreshPriceSlotList(country: string): Promise<boolean> {
return requestPriceSlotOption(country, 'PriceSlot');
}
export async function updatePriceSlot(
country: string,
slot: PriceSlot,
content: SheetRowUpdate[]
): Promise<boolean> {
// console.log('[sheetService] Sending PriceSlot update:', {
// country,
// slot: slot.slot,
// name: slot.name,
// description: slot.description,
// kind: slot.kind,
// rows: content.length,
// param: 'update/price',
// option: `PriceSlot${slot.slot}`
// });
const sent = await sendCommandRequest('sheet', {
country: country,
content: content,
param: 'update/price',
option: `PriceSlot${slot.slot}`
});
console.log('[sheetService] PriceSlot update sent:', {
country,
slot: slot.slot,
sent
});
return sent;
}
export async function addPriceSlot(
country: string,
slot: PriceSlot,
content: SheetRowCreate[]
): Promise<boolean> {
console.log('[sheetService] Sending PriceSlot create:', {
country,
slot: slot.slot,
name: slot.name,
description: slot.description,
kind: slot.kind,
rows: content.length,
param: 'add/price',
option: `PriceSlot${slot.slot}`
});
const sent = await sendCommandRequest('sheet', {
country: country,
content: content,
param: 'add/price',
option: `PriceSlot${slot.slot}`
});
console.log('[sheetService] PriceSlot create sent:', {
country,
slot: slot.slot,
sent
});
return sent;
}
export async function addPriceSlotRows(
country: string,
slot: PriceSlot,
content: SheetRowCreate[]
): Promise<boolean> {
if (!content || content.length === 0) return true;
const sent = await sendCommandRequest('sheet', {
country: country,
content: content,
param: 'add/price',
option: `PriceSlot${slot.slot}`
});
console.log('[sheetService] PriceSlot rows add sent:', {
country,
slot: slot.slot,
rows: content.length,
sent
});
return sent;
}
export async function deletePriceSlotRows(
country: string,
slot: PriceSlot,
rowIds: number[]
): Promise<boolean> {
if (!rowIds || rowIds.length === 0) return true;
const sent = await sendCommandRequest('sheet', {
country: country,
content: rowIds.map((target_id) => ({ target_id })),
param: 'delete/price',
option: `PriceSlot${slot.slot}`
});
console.log('[sheetService] PriceSlot rows delete sent:', {
country,
slot: slot.slot,
rows: rowIds.length,
sent
});
return sent;
}
export async function enterRoom(country: string, catalog: string): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
param: 'enter'
});
}
export async function sendHeartbeat(country: string, catalog: string): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
param: 'heartbeat'
});
}
export async function exitRoom(country: string, catalog: string): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
param: 'exit'
});
}
export async function requestCatalogMenu(country: string, catalog: string): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
param: 'catalog/menu'
});
}
export async function updateMenu(
country: string,
catalog: string,
content: any[]
): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
content: content,
param: 'update/menu'
});
}
export async function addMenu(country: string, catalog: string, content: any[]): Promise<boolean> {
logger.info('[sheetService] Adding menu:', { country, catalog, content });
const sent = await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
content: content,
param: 'add/menu'
});
logger.info('[sheetService] Add menu sent:', sent);
return sent;
}
export async function deleteMenu(
country: string,
catalog: string,
targetIds: number[]
): Promise<boolean> {
const content = targetIds.map((id) => ({ target_id: id }));
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
content: content,
param: 'delete/menu'
});
}
export async function swapMenu(
country: string,
catalog: string,
swaps: { source_id: number; target_id: number }[]
): Promise<boolean> {
return await sendCommandRequest('sheet', {
country: country,
catalog: catalog,
content: swaps,
param: 'swap/menu'
});
}
export async function requestListMenu(country: string, boxid?: string): Promise<boolean> {
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
};
}
productCodesLoading.set(true);
setPendingProductCodesCountry(country);
logger.info('[sheetService] Sending list_menu request for country:', country, 'boxid:', boxid);
return await sendMessage({
type: 'list_menu',
payload: {
user_info,
country,
boxid: boxid || undefined
}
});
}
export async function requestGenLayout(country: string): Promise<boolean> {
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();
logger.info('[sheetService] Sending gen-layout 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: 'new-inter-v3-multi-promotion-other_catalog-supra_app'
}
}
});
}
/**
* Request price data from sheet for specific product codes
* NOTE: Can only send once per type (price). Use hasSheetPriceBeenSent to check.
*/
export async function requestSheetPrice(
country: string,
productCodes: string[],
force = false
): Promise<boolean> {
// Check if already sent
if (!force && hasSheetPriceBeenSent('price')) {
logger.warn('[sheetService] Price request already sent, skipping');
return false;
}
if (!productCodes || productCodes.length === 0) {
logger.warn('[sheetService] No product codes to request price for');
return false;
}
// Generate request_id (UUID v4)
const request_id = crypto.randomUUID();
// Store request_id and country in streamingRawData for tracking
streamingRawData.update((data) => ({
...data,
price: {
request_id,
country,
chunks: [],
rawParts: []
}
}));
sheetPriceLoading.set(true);
// Convert to array of objects (backend expects objects, not strings)
const content = productCodes.map((code) => ({ product_code: code }));
logger.info(
'[sheetService] Sending sheet price request for country:',
country,
'codes:',
productCodes.length,
'request_id:',
request_id
);
const sent = await sendCommandRequest('sheet', {
country: country,
content: content,
param: 'price',
option: 'price',
stream: true,
request_id
});
console.log('[sheetService] Sheet price request sent:', { country, request_id, sent });
if (sent) {
markSheetPriceAsSent('price');
} else {
sheetPriceLoading.set(false);
}
return sent;
}
export async function requestAllSheetPrice(country: string, force = false): Promise<boolean> {
if (!force && hasSheetPriceBeenSent('price')) {
console.warn('[sheetService] Price request already sent, skipping');
return false;
}
const request_id = crypto.randomUUID();
streamingRawData.update((data) => ({
...data,
price: {
request_id,
country,
chunks: [],
rawParts: []
}
}));
sheetPriceLoading.set(true);
console.log('[sheetService] Sending all sheet price request:', { country, request_id });
const sent = await sendCommandRequest('sheet', {
country,
content: [],
param: 'price',
option: 'price',
stream: true,
request_id
});
if (sent) {
markSheetPriceAsSent('price');
} else {
sheetPriceLoading.set(false);
}
return sent;
}
/**
* Update price data in sheet
* content: [{ row_index: number, cells: [{ value: string, coord: { row: number, col: number } }] }]
*/
export async function updateSheetPrice(
country: string,
content: { row_index: number; cells: { value: string; coord: { row: number; col: number } }[] }[]
): Promise<boolean> {
if (!content || content.length === 0) {
logger.warn('[sheetService] No content to update');
return false;
}
logger.info(
'[sheetService] Updating sheet price for country:',
country,
'items:',
content.length
);
return await sendCommandRequest('sheet', {
country: country,
content: content,
param: 'update/price'
});
}
/**
* Add new price rows to sheet (for product codes that don't exist in price sheet)
* content: [{ cells: [product_code, name_en, name_th, ..., price, ...] }]
*/
export async function addSheetPrice(
country: string,
content: { cells: string[] }[]
): Promise<boolean> {
if (!content || content.length === 0) {
logger.warn('[sheetService] No content to add');
return false;
}
logger.info(
'[sheetService] Adding price rows for country:',
country,
'items:',
content.length,
content
);
return await sendCommandRequest('sheet', {
country: country,
content: content,
param: 'add/price'
});
}