import * as vscode from 'vscode'; import { writeSheetRange, type SheetValues } from '../sheets'; import { logError, logInfo } from '../../utils'; import { classifyAll } from './signalClassifier'; import { readStocksStore } from './stocksStore'; import type { ClassifiedStock } from './types'; /** * 분류된 종목 리스트 → Google Sheets 동기화. * * invest_results/gs_update_api.js 의 시트 레이아웃을 유지: * - Sheet1!A1:O = 스윙/중기 * - Sheet2!A1:O = 장기투자 * - Sheet3!A1:O = 저평가우량주 * (시트 이름은 spreadsheet 의 1/2/3번째 탭 — 사용자가 본인 spreadsheet 에 미리 만들어 둠.) * * spreadsheet ID 는 설정 `g1nation.stocks.spreadsheetId` 에서 읽음. 미설정이면 skip 안내. * OAuth token 은 calendar 와 공유 (oauth.ts SCOPE 에 spreadsheets 이미 포함). */ const HEADER = ['종목명', '심볼', '상장일', '현재가', '적정주가', '매수권장가', '3/4 필터', '매수 신호', 'ROE', 'PBR', '영업이익률', '유보율', 'PER', 'EPS', '특이사항']; function buildSheetRows(classified: ClassifiedStock[]): SheetValues { const rows: SheetValues = [HEADER]; for (const s of classified) { rows.push([ s.이름, s.심볼, s.상장일 ?? '', s.현재가 ?? 0, s.적정주가 ?? '', s.매수권장가 ?? '', s.filterPass ? 'Pass' : 'Fail', s.signalText, s['ROE(25E)'] ?? '', s.PBR ?? '', s['영업이익률(25E)'] ?? '', s.유보율 ?? '', s['PER(25E)'] ?? '', s['EPS(25E)'] ?? '', s.특이사항 ?? '', ]); } return rows; } /** * 3 시트 일괄 동기화. 결과는 시트별 성공/실패 카운트로 반환 — caller 가 webview 에 표시. * * Sheet 이름은 1/2/3번째 탭 인덱스가 아니라 *이름* 으로 range 를 짜야 안전. * 사용자가 본인 spreadsheet 의 탭 이름을 코드 기본값과 맞추도록 안내: * 'Sheet1' / 'Sheet2' / 'Sheet3' — 또는 설정으로 override 가능. */ export async function syncToSheets( context: vscode.ExtensionContext, ): Promise<{ ok: boolean; errors: string[]; updatedRanges: string[] }> { const cfg = vscode.workspace.getConfiguration('g1nation'); const spreadsheetId = (cfg.get('stocks.spreadsheetId') || '').trim(); if (!spreadsheetId) { return { ok: false, errors: ['Settings 에 g1nation.stocks.spreadsheetId 가 설정되지 않았습니다.'], updatedRanges: [], }; } const sheetSwing = (cfg.get('stocks.sheetSwing') || 'Sheet1').trim(); const sheetLong = (cfg.get('stocks.sheetLong') || 'Sheet2').trim(); const sheetUltra = (cfg.get('stocks.sheetUltraLow') || 'Sheet3').trim(); const store = readStocksStore(); const groups = classifyAll(store); const errors: string[] = []; const updatedRanges: string[] = []; const tasks: Array<{ tab: string; rows: SheetValues; label: string }> = [ { tab: sheetSwing, rows: buildSheetRows(groups.swing), label: '스윙/중기' }, { tab: sheetLong, rows: buildSheetRows(groups.long), label: '장기투자' }, { tab: sheetUltra, rows: buildSheetRows(groups.ultraLow), label: '저평가우량주' }, ]; for (const t of tasks) { if (t.rows.length <= 1) continue; const range = `${t.tab}!A1:O${t.rows.length}`; try { const r = await writeSheetRange(context, spreadsheetId, range, t.rows); if (r.ok) { updatedRanges.push(r.updatedRange); logInfo(`Stocks sheets sync: ${t.label} OK.`, { range: r.updatedRange, cells: r.updatedCells }); } else { errors.push(`${t.label}: ${r.error}`); logError(`Stocks sheets sync: ${t.label} 실패.`, { error: r.error }); } } catch (e: any) { errors.push(`${t.label}: ${e?.message ?? String(e)}`); } } return { ok: errors.length === 0, errors, updatedRanges }; }