← back to kkokio88-creator__Z-CMS

Function bodies 601 total

All specs Real LLM only Function bodies
fetchRawPreview function · javascript · L3-L19 (17 LOC)
server/analyze-raw.cjs
async function fetchRawPreview(sheetName, rows = 15) {
  return new Promise((resolve) => {
    const url = `/api/sheets/preview?url=1GUo9wmwmm14zhb_gtpoNrDBfJ5DqEm5pf4jpRsto-JI&sheet=${encodeURIComponent(sheetName)}`;

    const req = http.request({
      hostname: 'localhost',
      port: 3001,
      path: url,
      method: 'GET'
    }, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => resolve(JSON.parse(body)));
    });
    req.end();
  });
}
fetchFullData function · javascript · L21-L46 (26 LOC)
server/analyze-raw.cjs
async function fetchFullData(sheetName) {
  return new Promise((resolve) => {
    const data = JSON.stringify({
      spreadsheetUrl: '1GUo9wmwmm14zhb_gtpoNrDBfJ5DqEm5pf4jpRsto-JI',
      sheetName
    });

    const req = http.request({
      hostname: 'localhost',
      port: 3001,
      path: '/api/sheets/fetch-data',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      }
    }, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => resolve(JSON.parse(body)));
    });

    req.write(data);
    req.end();
  });
}
analyze function · javascript · L48-L164 (117 LOC)
server/analyze-raw.cjs
async function analyze() {
  console.log('');
  console.log('╔══════════════════════════════════════════════════════════════════════════╗');
  console.log('║           Z-CMS Google Sheets 상세 데이터 구조 분석                      ║');
  console.log('╚══════════════════════════════════════════════════════════════════════════╝');

  const sheets = ['매출', '판매', '구매', '경비', '폐기'];

  for (const sheet of sheets) {
    console.log(`\n\n${'═'.repeat(76)}`);
    console.log(`📊 [${sheet}] 시트 원본 데이터`);
    console.log('═'.repeat(76));

    const preview = await fetchRawPreview(sheet);

    if (preview.success) {
      console.log('\n📋 헤더 행:');
      console.log('   ' + (preview.headers || []).slice(0, 8).join(' | '));

      console.log('\n📄 데이터 미리보기:');
      (preview.data || []).forEach((row, i) => {
        const rowStr = (row || []).slice(0, 6).map(c => String(c || '').substring(0, 15)).join(' | ');
        console.log(`   ${i+1}. ${rowStr}`);
      });
    }
  }

  // 구매 데이터 상세 분석 (가장 깔끔한 시트)
  con
fetchSheet function · javascript · L3-L28 (26 LOC)
server/analyze-sheets.cjs
async function fetchSheet(sheetName) {
  return new Promise((resolve) => {
    const data = JSON.stringify({
      spreadsheetUrl: '1GUo9wmwmm14zhb_gtpoNrDBfJ5DqEm5pf4jpRsto-JI',
      sheetName
    });

    const req = http.request({
      hostname: 'localhost',
      port: 3001,
      path: '/api/sheets/fetch-data',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      }
    }, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => resolve(JSON.parse(body)));
    });

    req.write(data);
    req.end();
  });
}
analyzeStructure function · javascript · L30-L109 (80 LOC)
server/analyze-sheets.cjs
async function analyzeStructure() {
  const sheets = ['매출', '판매', '구매', '경비', '폐기'];

  console.log('╔════════════════════════════════════════════════════════════════╗');
  console.log('║          Z-CMS Google Sheets 데이터 구조 분석                  ║');
  console.log('╚════════════════════════════════════════════════════════════════╝\n');

  for (const sheet of sheets) {
    const result = await fetchSheet(sheet);
    if (result.success) {
      console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
      console.log(`📊 [${sheet}] 시트 - ${result.rowCount}행`);
      console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);

      console.log('\n📋 컬럼 구조:');
      const headers = result.headers || [];
      headers.forEach((h, i) => {
        if (h) {
          const values = result.data.slice(0, 10).map(r => r[h]).filter(v => v !== '' && v !== undefined);
          const isNumeric = values.length > 0 && values.every(v => typeof v === 'number
getPreview function · javascript · L3-L13 (11 LOC)
server/check-sheet-structure.cjs
async function getPreview(sheetName) {
  return new Promise((resolve) => {
    const url = `/api/sheets/preview?url=1GUo9wmwmm14zhb_gtpoNrDBfJ5DqEm5pf4jpRsto-JI&sheet=${encodeURIComponent(sheetName)}`;

    http.get({ hostname: 'localhost', port: 3001, path: url }, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => resolve(JSON.parse(body)));
    });
  });
}
main function · javascript · L15-L138 (124 LOC)
server/check-sheet-structure.cjs
async function main() {
  const sheets = ['매출', '판매', '구매', '경비', '폐기'];

  console.log('\n');
  console.log('╔══════════════════════════════════════════════════════════════════════════╗');
  console.log('║                    Google Sheets 구조 수정 가이드                        ║');
  console.log('╚══════════════════════════════════════════════════════════════════════════╝');

  for (const sheet of sheets) {
    const result = await getPreview(sheet);

    console.log(`\n${'═'.repeat(76)}`);
    console.log(`📊 [${sheet}] 시트`);
    console.log('═'.repeat(76));

    if (result.success) {
      console.log('\n🔴 현재 구조 (처음 6행):');
      console.log('┌' + '─'.repeat(74) + '┐');

      // 헤더
      const headerPreview = (result.headers || []).slice(0, 5).map(h =>
        String(h || '').substring(0, 12).padEnd(12)
      ).join(' | ');
      console.log(`│ 1행(헤더): ${headerPreview.substring(0, 60).padEnd(61)}│`);

      // 데이터
      (result.data || []).slice(0, 5).forEach((row, i) => {
        const r
Powered by Repobility — scan your code at https://repobility.com
runMigration function · javascript · L28-L72 (45 LOC)
server/migrate-purchases-schema.js
async function runMigration() {
  // Create Supabase client with service role key (has admin privileges)
  const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);

  const migrations = [
    'ALTER TABLE purchases ADD COLUMN IF NOT EXISTS supplier_name varchar(200) DEFAULT \'\';',
    'ALTER TABLE purchases DROP COLUMN IF EXISTS inbound_price;',
    'ALTER TABLE purchases DROP COLUMN IF EXISTS inbound_total;'
  ];

  console.log('Starting Supabase migration...');
  console.log(`URL: ${SUPABASE_URL}`);
  console.log(`Migrations to run: ${migrations.length}\n`);

  try {
    // Execute each SQL statement
    for (let i = 0; i < migrations.length; i++) {
      const sql = migrations[i];
      console.log(`[${i + 1}/${migrations.length}] Executing: ${sql}`);

      // Use rpc to execute raw SQL
      // Note: Supabase doesn't directly support raw SQL execution via REST
      // Instead, we'll need to use pg client or execute via dashboard

      // For now, log what would be executed
  
apiCall function · javascript · L4-L30 (27 LOC)
server/real-data-analysis.cjs
function apiCall(method, path, body = null) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname: 'localhost',
      port: 3001,
      path,
      method,
      headers: { 'Content-Type': 'application/json' }
    };

    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        try {
          resolve(JSON.parse(data));
        } catch (e) {
          resolve(data);
        }
      });
    });

    req.on('error', reject);
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}
fetchSheetData function · javascript · L33-L38 (6 LOC)
server/real-data-analysis.cjs
async function fetchSheetData(sheetName) {
  return apiCall('POST', '/api/sheets/fetch-data', {
    spreadsheetUrl: '1GUo9wmwmm14zhb_gtpoNrDBfJ5DqEm5pf4jpRsto-JI',
    sheetName
  });
}
analyzePurchaseData function · javascript · L41-L75 (35 LOC)
server/real-data-analysis.cjs
function analyzePurchaseData(data) {
  const items = {};
  let totalAmount = 0;

  data.forEach(row => {
    const item = row['품목별'] || row['품목명'];
    const code = row['품목코드'];
    const amount = row['합계'] || row['공급가액'] || 0;
    const qty = row['수량'] || 0;
    const unitPrice = row['단가'] || 0;

    if (item) {
      if (!items[item]) {
        items[item] = { code, totalQty: 0, totalAmount: 0, count: 0, avgPrice: 0 };
      }
      items[item].totalQty += qty;
      items[item].totalAmount += amount;
      items[item].count += 1;
    }
    totalAmount += amount;
  });

  // 평균 단가 계산
  Object.values(items).forEach(i => {
    i.avgPrice = i.totalQty > 0 ? Math.round(i.totalAmount / i.totalQty) : 0;
  });

  // 상위 품목 정렬
  const topByAmount = Object.entries(items)
    .sort((a, b) => b[1].totalAmount - a[1].totalAmount)
    .slice(0, 10)
    .map(([name, data]) => ({ name, ...data }));

  return { totalAmount, itemCount: Object.keys(items).length, topByAmount, items };
}
analyzeSalesData function · javascript · L77-L110 (34 LOC)
server/real-data-analysis.cjs
function analyzeSalesData(data) {
  const byCustomer = {};
  const byItem = {};
  let totalCount = 0;

  data.forEach(row => {
    const customer = row['거래처별'];
    const item = row['품목명'];
    const code = row['품목코드'];

    if (customer) {
      byCustomer[customer] = (byCustomer[customer] || 0) + 1;
    }
    if (item && code) {
      if (!byItem[code]) {
        byItem[code] = { name: item, count: 0 };
      }
      byItem[code].count += 1;
    }
    totalCount++;
  });

  const topCustomers = Object.entries(byCustomer)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 5)
    .map(([name, count]) => ({ name, count }));

  const topItems = Object.entries(byItem)
    .sort((a, b) => b[1].count - a[1].count)
    .slice(0, 10)
    .map(([code, data]) => ({ code, ...data }));

  return { totalCount, customerCount: Object.keys(byCustomer).length, topCustomers, topItems };
}
analyzeWasteData function · javascript · L112-L149 (38 LOC)
server/real-data-analysis.cjs
function analyzeWasteData(data) {
  const dailyWaste = [];

  data.forEach(row => {
    const date = row['생산일'];
    const total = row['생산수량'] || row['생산수량\n(EA)'];
    const regular = row['일반반찬'] || 0;
    const preProcess = row['전전처리'] || 0;
    const frozen = row['냉동국'] || 0;
    const sauce = row['소스'] || 0;
    const bibimbap = row['비빔밥'] || 0;

    if (date && typeof date === 'string' && date.match(/\d{4}-\d{2}-\d{2}/)) {
      dailyWaste.push({
        date,
        total: total || 0,
        regular,
        preProcess,
        frozen,
        sauce,
        bibimbap
      });
    }
  });

  // 최근 데이터 정렬
  dailyWaste.sort((a, b) => b.date.localeCompare(a.date));

  const totalProduction = dailyWaste.reduce((sum, d) => sum + (d.total || 0), 0);
  const avgDaily = dailyWaste.length > 0 ? Math.round(totalProduction / dailyWaste.length) : 0;

  return {
    recordCount: dailyWaste.length,
    totalProduction,
    avgDaily,
    recent: dailyWaste.slice(0, 10)
  };
}
analyzeRevenueData function · javascript · L151-L181 (31 LOC)
server/real-data-analysis.cjs
function analyzeRevenueData(data) {
  const dailyRevenue = [];

  data.forEach(row => {
    const date = row['생산일'];
    const jasa = row['자사\n(권장판매가)'] || row['자사'] || 0;
    const coupang = row['쿠팡\n(공급가)'] || row['쿠팡'] || 0;
    const kurly = row['컬리\n(공급가)'] || row['컬리'] || 0;
    const total = row['매출 총액'] || row['매출총액'] || 0;

    if (date && typeof date === 'string' && date.match(/\d{4}-\d{2}-\d{2}/)) {
      dailyRevenue.push({ date, jasa, coupang, kurly, total });
    }
  });

  dailyRevenue.sort((a, b) => b.date.localeCompare(a.date));

  const totalRevenue = dailyRevenue.reduce((sum, d) => sum + (d.total || 0), 0);
  const byChannel = {
    jasa: dailyRevenue.reduce((sum, d) => sum + (d.jasa || 0), 0),
    coupang: dailyRevenue.reduce((sum, d) => sum + (d.coupang || 0), 0),
    kurly: dailyRevenue.reduce((sum, d) => sum + (d.kurly || 0), 0)
  };

  return {
    recordCount: dailyRevenue.length,
    totalRevenue,
    byChannel,
    recent: dailyRevenue.slice(0, 10)
  };
}
runAnalysis function · javascript · L184-L400 (217 LOC)
server/real-data-analysis.cjs
async function runAnalysis() {
  console.log('\n');
  console.log('╔══════════════════════════════════════════════════════════════════════════╗');
  console.log('║              Z-CMS 실제 데이터 기반 종합 분석                            ║');
  console.log('║                    2026년 기초데이터 분석                                ║');
  console.log('╚══════════════════════════════════════════════════════════════════════════╝');

  // 1. 데이터 수집
  console.log('\n📥 데이터 수집 중...\n');

  const [매출Result, 판매Result, 구매Result, 폐기Result] = await Promise.all([
    fetchSheetData('매출'),
    fetchSheetData('판매'),
    fetchSheetData('구매'),
    fetchSheetData('폐기')
  ]);

  const 매출 = 매출Result.success ? 매출Result.data : [];
  const 판매 = 판매Result.success ? 판매Result.data : [];
  const 구매 = 구매Result.success ? 구매Result.data : [];
  const 폐기 = 폐기Result.success ? 폐기Result.data : [];

  console.log(`   ✓ 매출: ${매출.length}건`);
  console.log(`   ✓ 판매: ${판매.length}건`);
  console.log(`   ✓ 구매: ${구매.length}건`);
  console.log(`   ✓ 폐
Want this analysis on your repo? https://repobility.com/scan/
EcountAdapter class · typescript · L22-L417 (396 LOC)
server/src/adapters/EcountAdapter.ts
export class EcountAdapter {
  private _config: EcountConfig | null = null;
  private sessionId: string | null = null;
  private loginPromise: Promise<boolean> | null = null;
  private apiCache = new Map<string, { data: any; expiry: number }>();
  private readonly CACHE_TTL = 5 * 60 * 1000; // 5분

  // dotenv.config() 이후에 env를 읽도록 lazy 로딩
  private get config(): EcountConfig {
    if (!this._config) {
      this._config = {
        COM_CODE: process.env.ECOUNT_COM_CODE || '',
        USER_ID: process.env.ECOUNT_USER_ID || '',
        API_KEY: process.env.ECOUNT_API_KEY || '',
        ZONE: process.env.ECOUNT_ZONE || 'CD',
      };
    }
    return this._config;
  }

  private set config(value: EcountConfig) {
    this._config = value;
  }

  updateConfig(config: Partial<EcountConfig>): void {
    this.config = { ...this.config, ...config };
    this.sessionId = null;
  }

  getConfig(): Omit<EcountConfig, 'API_KEY'> {
    return {
      COM_CODE: this.config.COM_CODE,
      USER_ID: th
updateConfig method · typescript · L46-L49 (4 LOC)
server/src/adapters/EcountAdapter.ts
  updateConfig(config: Partial<EcountConfig>): void {
    this.config = { ...this.config, ...config };
    this.sessionId = null;
  }
getConfig method · typescript · L51-L57 (7 LOC)
server/src/adapters/EcountAdapter.ts
  getConfig(): Omit<EcountConfig, 'API_KEY'> {
    return {
      COM_CODE: this.config.COM_CODE,
      USER_ID: this.config.USER_ID,
      ZONE: this.config.ZONE,
    };
  }
getBaseUrl method · typescript · L59-L61 (3 LOC)
server/src/adapters/EcountAdapter.ts
  private getBaseUrl(): string {
    return `https://oapi${this.config.ZONE}.ecount.com/OAPI/V2`;
  }
login method · typescript · L63-L93 (31 LOC)
server/src/adapters/EcountAdapter.ts
  async login(): Promise<boolean> {
    const url = `${this.getBaseUrl()}/OAPILogin`;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          COM_CODE: this.config.COM_CODE,
          USER_ID: this.config.USER_ID,
          API_CERT_KEY: this.config.API_KEY,
          LAN_TYPE: 'ko-KR',
          ZONE: this.config.ZONE,
        }),
      });

      const data: any = await response.json();

      if (data.Data?.Datas?.SESSION_ID) {
        this.sessionId = data.Data.Datas.SESSION_ID;
        console.log('ECOUNT login successful');
        return true;
      }

      console.error('ECOUNT login failed:', data);
      return false;
    } catch (error) {
      console.error('ECOUNT login error:', error);
      return false;
    }
  }
ensureLogin method · typescript · L95-L102 (8 LOC)
server/src/adapters/EcountAdapter.ts
  private async ensureLogin(): Promise<boolean> {
    if (this.sessionId) return true;
    if (this.loginPromise) return this.loginPromise;
    this.loginPromise = this.login().finally(() => {
      this.loginPromise = null;
    });
    return this.loginPromise;
  }
getCacheKey method · typescript · L104-L106 (3 LOC)
server/src/adapters/EcountAdapter.ts
  private getCacheKey(endpoint: string, params: Record<string, unknown>): string {
    return `${endpoint}:${JSON.stringify(params)}`;
  }
invalidateCache method · typescript · L108-L116 (9 LOC)
server/src/adapters/EcountAdapter.ts
  public invalidateCache(pattern?: string): void {
    if (!pattern) {
      this.apiCache.clear();
      return;
    }
    for (const key of this.apiCache.keys()) {
      if (key.includes(pattern)) this.apiCache.delete(key);
    }
  }
Same scanner, your repo: https://repobility.com — Repobility
callApi method · typescript · L118-L166 (49 LOC)
server/src/adapters/EcountAdapter.ts
  private async callApi<T>(endpoint: string, params: Record<string, unknown> = {}, retryCount: number = 0): Promise<T[]> {
    const loginSuccess = await this.ensureLogin();
    if (!loginSuccess) {
      throw new Error('ECOUNT login failed');
    }

    const cacheKey = this.getCacheKey(endpoint, params);
    const cached = this.apiCache.get(cacheKey);
    if (cached && Date.now() < cached.expiry) {
      return cached.data;
    }

    const url = `${this.getBaseUrl()}${endpoint}?SESSION_ID=${this.sessionId}`;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          ...params,
          PageSize: 5000,
        }),
      });

      const data = (await response.json()) as EcountApiResponse<T>;

      if (data.Error?.Code === '999') {
        if (retryCount >= 2) {
          console.error(`ECOUNT API max retries exceeded for ${endpoint}`);
          return [];
        }
tryEndpoints method · typescript · L169-L186 (18 LOC)
server/src/adapters/EcountAdapter.ts
  private async tryEndpoints<T>(
    endpoints: { path: string; params: Record<string, unknown> }[],
    name: string
  ): Promise<T[]> {
    for (const { path, params } of endpoints) {
      try {
        const result = await this.callApi<T>(path, params);
        if (result.length > 0) {
          console.log(`✓ ${name}: ${path} returned ${result.length} items`);
          return result;
        }
      } catch (e) {
        // Continue to next endpoint
      }
    }
    console.log(`✗ ${name}: No working endpoint found`);
    return [];
  }
fetchSales method · typescript · L188-L204 (17 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchSales(dateFrom: string, dateTo: string): Promise<EcountSaleRaw[]> {
    // ECOUNT OAPI V2: 다양한 판매 조회 엔드포인트 시도
    const endpoints = [
      { path: '/SaleIO/GetListSaleIO', params: { IO_DATE_FROM: dateFrom, IO_DATE_TO: dateTo } },
      { path: '/Sale/GetListSale', params: { SALE_DATE_FROM: dateFrom, SALE_DATE_TO: dateTo } },
      {
        path: '/SaleSlip/GetListSaleSlip',
        params: { SALE_DATE_FROM: dateFrom, SALE_DATE_TO: dateTo },
      },
      {
        path: '/SaleSlipIO/GetListSaleSlipIO',
        params: { IO_DATE_FROM: dateFrom, IO_DATE_TO: dateTo },
      },
      { path: '/Sale/GetSaleList', params: { FROM_DATE: dateFrom, TO_DATE: dateTo } },
    ];
    return this.tryEndpoints<EcountSaleRaw>(endpoints, '판매');
  }
fetchPurchases method · typescript · L206-L228 (23 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchPurchases(dateFrom: string, dateTo: string): Promise<EcountPurchaseRaw[]> {
    // ECOUNT OAPI V2: 다양한 구매/발주 조회 엔드포인트 시도
    const endpoints = [
      {
        path: '/PurchaseIO/GetListPurchaseIO',
        params: { IO_DATE_FROM: dateFrom, IO_DATE_TO: dateTo },
      },
      {
        path: '/Purchases/GetListPurchases',
        params: { PURCHASE_DATE_FROM: dateFrom, PURCHASE_DATE_TO: dateTo },
      },
      {
        path: '/PurchasesSlip/GetListPurchasesSlip',
        params: { PURCHASE_DATE_FROM: dateFrom, PURCHASE_DATE_TO: dateTo },
      },
      {
        path: '/PurchaseOrder/GetListPurchaseOrder',
        params: { ORDER_DATE_FROM: dateFrom, ORDER_DATE_TO: dateTo },
      },
      { path: '/PO/GetListPO', params: { FROM_DATE: dateFrom, TO_DATE: dateTo } },
    ];
    return this.tryEndpoints<EcountPurchaseRaw>(endpoints, '구매');
  }
fetchInventory method · typescript · L230-L239 (10 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchInventory(): Promise<EcountInventoryRaw[]> {
    // ECOUNT OAPI V2: 재고 현황 조회 (창고+품목별) - 이미 작동함
    const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
    return this.callApi<EcountInventoryRaw>(
      '/InventoryBalance/GetListInventoryBalanceStatusByLocation',
      {
        BASE_DATE: today,
      }
    );
  }
fetchProduction method · typescript · L241-L262 (22 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchProduction(dateFrom: string, dateTo: string): Promise<EcountProductionRaw[]> {
    // ECOUNT OAPI V2: 다양한 생산 조회 엔드포인트 시도
    const endpoints = [
      {
        path: '/ProductionIO/GetListProductionIO',
        params: { IO_DATE_FROM: dateFrom, IO_DATE_TO: dateTo },
      },
      {
        path: '/Production/GetListProduction',
        params: { PROD_DATE_FROM: dateFrom, PROD_DATE_TO: dateTo },
      },
      {
        path: '/ProductionSlip/GetListProductionSlip',
        params: { PROD_DATE_FROM: dateFrom, PROD_DATE_TO: dateTo },
      },
      {
        path: '/ProductionOrder/GetListProductionOrder',
        params: { ORDER_DATE_FROM: dateFrom, ORDER_DATE_TO: dateTo },
      },
    ];
    return this.tryEndpoints<EcountProductionRaw>(endpoints, '생산');
  }
fetchBom method · typescript · L264-L273 (10 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchBom(): Promise<EcountBomRaw[]> {
    // ECOUNT OAPI V2: 다양한 BOM 조회 엔드포인트 시도
    const endpoints = [
      { path: '/BOM/GetListBOM', params: {} },
      { path: '/InventoryBasic/GetBasicBOM', params: {} },
      { path: '/BOM/GetBOMList', params: {} },
      { path: '/ProductBOM/GetListProductBOM', params: {} },
    ];
    return this.tryEndpoints<EcountBomRaw>(endpoints, 'BOM');
  }
fetchProducts method · typescript · L275-L278 (4 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchProducts(): Promise<any[]> {
    // ECOUNT OAPI V2: 품목 마스터 조회 - 권한 있음
    return this.callApi<any>('/InventoryBasic/GetBasicProductsList', {});
  }
Repobility · code-quality intelligence platform · https://repobility.com
fetchPurchaseOrders method · typescript · L281-L294 (14 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchPurchaseOrders(dateFrom: string, dateTo: string): Promise<any[]> {
    const endpoints = [
      {
        path: '/Purchases/GetPurchasesOrderList',
        params: { ORDER_DATE_FROM: dateFrom, ORDER_DATE_TO: dateTo },
      },
      {
        path: '/PurchaseOrder/GetListPurchaseOrder',
        params: { ORDER_DATE_FROM: dateFrom, ORDER_DATE_TO: dateTo },
      },
      { path: '/PO/GetListPO', params: { FROM_DATE: dateFrom, TO_DATE: dateTo } },
    ];
    return this.tryEndpoints<any>(endpoints, '발주서');
  }
fetchInventoryByLocation method · typescript · L297-L308 (12 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchInventoryByLocation(baseDate?: string): Promise<any[]> {
    const date = baseDate || new Date().toISOString().slice(0, 10).replace(/-/g, '');
    const endpoints = [
      {
        path: '/InventoryBalance/GetListInventoryBalanceStatusByLocation',
        params: { BASE_DATE: date },
      },
      { path: '/InventoryBalance/GetInventoryBalanceByWH', params: { BASE_DATE: date } },
      { path: '/Inventory/GetListInventoryByWarehouse', params: { BASE_DATE: date } },
    ];
    return this.tryEndpoints<any>(endpoints, '창고별재고');
  }
fetchAttendance method · typescript · L311-L322 (12 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchAttendance(dateFrom: string, dateTo: string): Promise<any[]> {
    const endpoints = [
      { path: '/TimeMgmt/GetAttendanceList', params: { FROM_DATE: dateFrom, TO_DATE: dateTo } },
      { path: '/TimeMgmt/GetListClockInOut', params: { FROM_DATE: dateFrom, TO_DATE: dateTo } },
      {
        path: '/Attendance/GetListAttendance',
        params: { ATT_DATE_FROM: dateFrom, ATT_DATE_TO: dateTo },
      },
      { path: '/HR/GetAttendanceRecord', params: { FROM_DATE: dateFrom, TO_DATE: dateTo } },
    ];
    return this.tryEndpoints<any>(endpoints, '출퇴근');
  }
fetchPayroll method · typescript · L325-L332 (8 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchPayroll(yearMonth: string): Promise<any[]> {
    const endpoints = [
      { path: '/Payroll/GetListPayroll', params: { YEAR_MONTH: yearMonth } },
      { path: '/HR/GetPayrollList', params: { YEAR_MONTH: yearMonth } },
      { path: '/Salary/GetListSalary', params: { PAY_MONTH: yearMonth } },
    ];
    return this.tryEndpoints<any>(endpoints, '급여');
  }
fetchCustomers method · typescript · L335-L342 (8 LOC)
server/src/adapters/EcountAdapter.ts
  async fetchCustomers(): Promise<any[]> {
    const endpoints = [
      { path: '/SalesBasic/GetBasicCustomerList', params: {} },
      { path: '/Customer/GetListCustomer', params: {} },
      { path: '/Vendor/GetListVendor', params: {} },
    ];
    return this.tryEndpoints<any>(endpoints, '거래처');
  }
syncAllData method · typescript · L344-L401 (58 LOC)
server/src/adapters/EcountAdapter.ts
  async syncAllData() {
    const today = new Date();
    const threeMonthsAgo = new Date(today);
    threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);

    const dateFrom = threeMonthsAgo.toISOString().slice(0, 10).replace(/-/g, '');
    const dateTo = today.toISOString().slice(0, 10).replace(/-/g, '');
    const currentYearMonth = today.toISOString().slice(0, 7).replace(/-/g, '');

    console.log(`Syncing ECOUNT data from ${dateFrom} to ${dateTo}...`);

    const [
      sales,
      purchases,
      inventory,
      production,
      bom,
      purchaseOrders,
      inventoryByLocation,
      attendance,
      customers,
    ] = await Promise.all([
      this.fetchSales(dateFrom, dateTo),
      this.fetchPurchases(dateFrom, dateTo),
      this.fetchInventory(),
      this.fetchProduction(dateFrom, dateTo),
      this.fetchBom(),
      this.fetchPurchaseOrders(dateFrom, dateTo),
      this.fetchInventoryByLocation(),
      this.fetchAttendance(dateFrom, dateTo),
      this.fet
constructor method · typescript · L17-L19 (3 LOC)
server/src/adapters/GeminiAdapter.ts
  constructor() {
    // Lazy initialization - will be done on first API call
  }
ensureInitialized method · typescript · L21-L33 (13 LOC)
server/src/adapters/GeminiAdapter.ts
  private ensureInitialized(): void {
    if (this.initialized) return;
    this.initialized = true;

    const apiKey = process.env.GEMINI_API_KEY;
    if (apiKey && apiKey !== 'your_gemini_api_key') {
      this.genAI = new GoogleGenerativeAI(apiKey);
      this.model = this.genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
      console.log('Gemini API initialized');
    } else {
      console.warn('Gemini API key not configured - using mock responses');
    }
  }
Powered by Repobility — scan your code at https://repobility.com
generate method · typescript · L35-L59 (25 LOC)
server/src/adapters/GeminiAdapter.ts
  async generate(prompt: string): Promise<GeminiResponse> {
    this.ensureInitialized();

    if (!this.model) {
      return this.generateMockResponse(prompt);
    }

    try {
      const result = await this.model.generateContent(prompt);
      const response = result.response;
      const text = response.text();

      return {
        text,
        usage: {
          promptTokens: 0,
          completionTokens: 0,
          totalTokens: 0,
        },
      };
    } catch (error) {
      console.error('Gemini API error:', error);
      return this.generateMockResponse(prompt);
    }
  }
analyzeAnomaly method · typescript · L61-L84 (24 LOC)
server/src/adapters/GeminiAdapter.ts
  async analyzeAnomaly(data: {
    itemName: string;
    expected: number;
    actual: number;
    diffPercent: number;
    historicalContext?: string;
  }): Promise<string> {
    const prompt = `당신은 제조업 생산 분석 전문가입니다. 다음 BOM 차이를 분석해주세요:

품목: ${data.itemName}
표준량: ${data.expected}
실제 투입량: ${data.actual}
차이율: ${data.diffPercent.toFixed(1)}%
${data.historicalContext ? `과거 맥락: ${data.historicalContext}` : ''}

다음 형식으로 간결하게 분석해주세요 (한국어, 2-3문장):
1. 가능한 원인
2. 권장 조치

응답:`;

    const response = await this.generate(prompt);
    return response.text;
  }
predictInventory method · typescript · L86-L91 (6 LOC)
server/src/adapters/GeminiAdapter.ts
  async predictInventory(data: {
    materialName: string;
    currentStock: number;
    avgDailyUsage: number;
    recentTrend: 'increasing' | 'decreasing' | 'stable';
  }): Promise<{ expectedQty: number; reasoning: string }> {
analyzeProfitability method · typescript · L118-L137 (20 LOC)
server/src/adapters/GeminiAdapter.ts
  async analyzeProfitability(data: {
    channel: string;
    revenue: number;
    cost: number;
    margin: number;
    trend: string;
  }): Promise<string> {
    const prompt = `당신은 수익성 분석 전문가입니다. 다음 채널의 수익성을 분석해주세요:

채널: ${data.channel}
매출: ${data.revenue.toLocaleString()}원
비용: ${data.cost.toLocaleString()}원
마진율: ${data.margin.toFixed(1)}%
추세: ${data.trend}

간결한 인사이트를 제공해주세요 (한국어, 2-3문장):`;

    const response = await this.generate(prompt);
    return response.text;
  }
analyzeCostStructure method · typescript · L139-L152 (14 LOC)
server/src/adapters/GeminiAdapter.ts
  async analyzeCostStructure(data: {
    salesAmount: number;
    rawMaterialCost: number;
    subMaterialCost: number;
    laborCost: number;
    expenseAmount: number;
    targetRatios: {
      rawMaterial?: number;
      subMaterial?: number;
      labor?: number;
      expense?: number;
    };
    trend?: 'improving' | 'stable' | 'deteriorating';
  }): Promise<{ analysis: string; recommendations: string[] }> {
analyzeLaborCost method · typescript · L194-L200 (7 LOC)
server/src/adapters/GeminiAdapter.ts
  async analyzeLaborCost(data: {
    totalLaborCost: number;
    employeeCount: number;
    overtimeHours: number;
    productionVolume: number;
    previousPeriodCost?: number;
  }): Promise<{ analysis: string; efficiencyScore: number; suggestions: string[] }> {
analyzePurchaseOrders method · typescript · L242-L248 (7 LOC)
server/src/adapters/GeminiAdapter.ts
  async analyzePurchaseOrders(data: {
    totalAmount: number;
    orderCount: number;
    topSuppliers: { name: string; amount: number }[];
    urgentOrders: number;
    leadTimeAvg: number;
  }): Promise<{ analysis: string; riskLevel: 'low' | 'medium' | 'high'; suggestions: string[] }> {
generateCoordinatorSummary method · typescript · L282-L303 (22 LOC)
server/src/adapters/GeminiAdapter.ts
  async generateCoordinatorSummary(domainInsights: {
    bomWaste?: string;
    inventory?: string;
    profitability?: string;
  }): Promise<string> {
    const parts = [];
    if (domainInsights.bomWaste) parts.push(`BOM/폐기물: ${domainInsights.bomWaste}`);
    if (domainInsights.inventory) parts.push(`재고: ${domainInsights.inventory}`);
    if (domainInsights.profitability) parts.push(`수익성: ${domainInsights.profitability}`);

    const prompt = `당신은 생산 관리 총괄 분석가입니다. 다음 도메인별 인사이트를 종합하여 경영진을 위한 핵심 요약을 제공해주세요:

${parts.join('\n\n')}

다음 형식으로 응답해주세요 (한국어):
1. 핵심 현황 (1문장)
2. 우선 조치 사항 (1-2개)
3. 예상 영향 (간략히)`;

    const response = await this.generate(prompt);
    return response.text;
  }
Want this analysis on your repo? https://repobility.com/scan/
generateMockResponse method · typescript · L305-L353 (49 LOC)
server/src/adapters/GeminiAdapter.ts
  private generateMockResponse(prompt: string): GeminiResponse {
    let mockText = '';

    if (prompt.includes('BOM') || prompt.includes('차이')) {
      mockText =
        '최근 원자재 품질 변동으로 인한 투입량 증가로 판단됩니다. 공급업체 품질 검수를 강화하고, 작업 표준서 업데이트를 권장합니다.';
    } else if (prompt.includes('재고') || prompt.includes('예측')) {
      mockText = JSON.stringify({
        expectedQty: 150,
        reasoning: '계절적 수요 증가와 최근 출고 패턴을 고려한 예측입니다.',
      });
    } else if (prompt.includes('원가') || prompt.includes('비용')) {
      mockText = JSON.stringify({
        analysis:
          '원재료비 비중이 높아 원가 구조 개선이 필요합니다. 목표 대비 노무비는 양호한 수준입니다.',
        recommendations: [
          '원재료 대체재 검토 또는 대량 구매 협상',
          '생산 공정 효율화로 부재료 사용 절감',
          '경비 항목 중 불필요한 지출 검토',
        ],
      });
    } else if (prompt.includes('노무비') || prompt.includes('인건비')) {
      mockText = JSON.stringify({
        analysis: '초과근무 비율이 다소 높으나 생산성은 양호합니다.',
        efficiencyScore: 75,
        suggestions: ['야간 근무 인력 배치 최적화', '자동화 투자를 통한 노
isConfigured method · typescript · L355-L357 (3 LOC)
server/src/adapters/GeminiAdapter.ts
  isConfigured(): boolean {
    return this.model !== null;
  }
generateOptimistPosition method · typescript · L366-L381 (16 LOC)
server/src/adapters/GeminiAdapter.ts
  async generateOptimistPosition(data: {
    topic: string;
    contextData: unknown;
    domain: string;
    catsCommand: {
      context: string;
      task: string;
      successCriteria: string;
    };
  }): Promise<{
    position: string;
    reasoning: string;
    evidence: string[];
    confidence: number;
    suggestedActions: string[];
  }> {
page 1 / 13next ›