Function bodies 601 total
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}`);
});
}
}
// 구매 데이터 상세 분석 (가장 깔끔한 시트)
confetchSheet 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 === 'numbergetPreview 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 rPowered 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: thupdateConfig 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.fetconstructor 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 ›