Show all 8092 crossbeam rows
This commit is contained in:
@@ -50,6 +50,7 @@ SITE_SYNC_JOBS = {}
|
|||||||
SITE_SYNC_LOCK = threading.Lock()
|
SITE_SYNC_LOCK = threading.Lock()
|
||||||
DB_INIT_LOCK = threading.Lock()
|
DB_INIT_LOCK = threading.Lock()
|
||||||
DB_SCHEMA_READY = False
|
DB_SCHEMA_READY = False
|
||||||
|
DB_WRITE_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def configure_sqlite_connection(conn):
|
def configure_sqlite_connection(conn):
|
||||||
@@ -64,6 +65,24 @@ def open_db_connection(timeout=30):
|
|||||||
return configure_sqlite_connection(conn)
|
return configure_sqlite_connection(conn)
|
||||||
|
|
||||||
|
|
||||||
|
def run_db_write(write_fn, retries=4, delay=0.2):
|
||||||
|
last_error = None
|
||||||
|
for attempt in range(retries):
|
||||||
|
try:
|
||||||
|
with DB_WRITE_LOCK:
|
||||||
|
with open_db_connection() as conn:
|
||||||
|
ensure_db_schema(conn)
|
||||||
|
return write_fn(conn)
|
||||||
|
except sqlite3.OperationalError as error:
|
||||||
|
last_error = error
|
||||||
|
if 'locked' not in str(error).lower() or attempt == retries - 1:
|
||||||
|
raise
|
||||||
|
time.sleep(delay * (attempt + 1))
|
||||||
|
if last_error:
|
||||||
|
raise last_error
|
||||||
|
raise RuntimeError('DB write failed')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def init_db(conn):
|
def init_db(conn):
|
||||||
global DB_SCHEMA_READY
|
global DB_SCHEMA_READY
|
||||||
@@ -1152,16 +1171,22 @@ def _parse_budget_predeck(rows):
|
|||||||
|
|
||||||
|
|
||||||
def _parse_budget_crossbeam(rows):
|
def _parse_budget_crossbeam(rows):
|
||||||
result = {'height': '', 'length': '', 'quantity': '', 'remarks': ''}
|
result = []
|
||||||
if len(rows) < 2:
|
if len(rows) < 2:
|
||||||
return result
|
return result
|
||||||
values = rows[1] + [''] * max(0, 4 - len(rows[1]))
|
for row in rows[1:]:
|
||||||
result.update({
|
if not row:
|
||||||
'height': _as_text(values[0]).strip(),
|
continue
|
||||||
'length': _as_text(values[1]).strip(),
|
first = _as_text(row[0]).strip()
|
||||||
'quantity': _as_text(values[2]).strip(),
|
if not first or '조회된 내용이 없습니다' in first:
|
||||||
'remarks': _as_text(values[3]).strip(),
|
continue
|
||||||
})
|
values = row + [''] * max(0, 4 - len(row))
|
||||||
|
result.append({
|
||||||
|
'height': _as_text(values[0]).strip(),
|
||||||
|
'length': _as_text(values[1]).strip(),
|
||||||
|
'quantity': _as_text(values[2]).strip(),
|
||||||
|
'remarks': _as_text(values[3]).strip(),
|
||||||
|
})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -4004,25 +4029,13 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
q = parse_qs(parsed.query)
|
q = parse_qs(parsed.query)
|
||||||
page_name = q.get('page', ['const'])[0]
|
page_name = q.get('page', ['const'])[0]
|
||||||
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
||||||
with open_db_connection() as conn:
|
if refresh:
|
||||||
ensure_db_schema(conn)
|
search_text = q.get('searchText', [''])[0]
|
||||||
if refresh:
|
result = fetch_erp_project_codes(page_name, search_text)
|
||||||
search_text = q.get('searchText', [''])[0]
|
sync_info = run_db_write(lambda conn: replace_erp_project_code_cache(conn, result['page'], result['rows']))
|
||||||
result = fetch_erp_project_codes(page_name, search_text)
|
with open_db_connection() as conn:
|
||||||
sync_info = replace_erp_project_code_cache(conn, result['page'], result['rows'])
|
ensure_db_schema(conn)
|
||||||
cached = get_erp_project_code_cache(conn, result['page'])
|
cached = get_erp_project_code_cache(conn, result['page'])
|
||||||
return self._json(
|
|
||||||
200,
|
|
||||||
{
|
|
||||||
'count': len(cached['rows']),
|
|
||||||
'rows': cached['rows'],
|
|
||||||
'source': 'erp_cache',
|
|
||||||
'page': cached['sourcePage'],
|
|
||||||
'syncedAt': sync_info['syncedAt'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
cached = get_erp_project_code_cache(conn, page_name)
|
|
||||||
return self._json(
|
return self._json(
|
||||||
200,
|
200,
|
||||||
{
|
{
|
||||||
@@ -4030,21 +4043,35 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
'rows': cached['rows'],
|
'rows': cached['rows'],
|
||||||
'source': 'erp_cache',
|
'source': 'erp_cache',
|
||||||
'page': cached['sourcePage'],
|
'page': cached['sourcePage'],
|
||||||
'syncedAt': cached['syncedAt'],
|
'syncedAt': sync_info['syncedAt'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with open_db_connection() as conn:
|
||||||
|
ensure_db_schema(conn)
|
||||||
|
cached = get_erp_project_code_cache(conn, page_name)
|
||||||
|
return self._json(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
'count': len(cached['rows']),
|
||||||
|
'rows': cached['rows'],
|
||||||
|
'source': 'erp_cache',
|
||||||
|
'page': cached['sourcePage'],
|
||||||
|
'syncedAt': cached['syncedAt'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if parsed.path == '/api/erp-contract-detail':
|
if parsed.path == '/api/erp-contract-detail':
|
||||||
q = parse_qs(parsed.query)
|
q = parse_qs(parsed.query)
|
||||||
page_name = q.get('page', ['const'])[0]
|
page_name = q.get('page', ['const'])[0]
|
||||||
project_code = q.get('projectCode', [''])[0]
|
project_code = q.get('projectCode', [''])[0]
|
||||||
project_name = q.get('projectName', [''])[0]
|
project_name = q.get('projectName', [''])[0]
|
||||||
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
||||||
|
if refresh:
|
||||||
|
detail = fetch_erp_contract_detail(page_name, project_code, project_name)
|
||||||
|
run_db_write(lambda conn: replace_erp_contract_detail_cache(conn, detail))
|
||||||
with open_db_connection() as conn:
|
with open_db_connection() as conn:
|
||||||
ensure_db_schema(conn)
|
ensure_db_schema(conn)
|
||||||
if refresh:
|
|
||||||
detail = fetch_erp_contract_detail(page_name, project_code, project_name)
|
|
||||||
replace_erp_contract_detail_cache(conn, detail)
|
|
||||||
cached = get_erp_contract_detail_cache(conn, page_name, project_code)
|
cached = get_erp_contract_detail_cache(conn, page_name, project_code)
|
||||||
if not cached:
|
if not cached:
|
||||||
return self._json(404, {'ok': False, 'error': '계약정보 캐시가 없습니다.'})
|
return self._json(404, {'ok': False, 'error': '계약정보 캐시가 없습니다.'})
|
||||||
@@ -4062,11 +4089,11 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
project_code = q.get('projectCode', [''])[0]
|
project_code = q.get('projectCode', [''])[0]
|
||||||
project_name = q.get('projectName', [''])[0]
|
project_name = q.get('projectName', [''])[0]
|
||||||
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
||||||
|
if refresh:
|
||||||
|
result = fetch_erp_bridge_overviews(page_name, project_code, project_name)
|
||||||
|
run_db_write(lambda conn: replace_erp_bridge_overview_cache(conn, result))
|
||||||
with open_db_connection() as conn:
|
with open_db_connection() as conn:
|
||||||
ensure_db_schema(conn)
|
ensure_db_schema(conn)
|
||||||
if refresh:
|
|
||||||
result = fetch_erp_bridge_overviews(page_name, project_code, project_name)
|
|
||||||
replace_erp_bridge_overview_cache(conn, result)
|
|
||||||
cached = get_erp_bridge_overview_cache(conn, page_name, project_code)
|
cached = get_erp_bridge_overview_cache(conn, page_name, project_code)
|
||||||
return self._json(
|
return self._json(
|
||||||
200,
|
200,
|
||||||
@@ -4083,11 +4110,11 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
project_code = q.get('projectCode', [''])[0]
|
project_code = q.get('projectCode', [''])[0]
|
||||||
project_name = q.get('projectName', [''])[0]
|
project_name = q.get('projectName', [''])[0]
|
||||||
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
refresh = q.get('refresh', ['0'])[0] in ('1', 'true', 'yes')
|
||||||
|
if refresh:
|
||||||
|
result = fetch_erp_budget_plan(page_name, project_code, project_name)
|
||||||
|
run_db_write(lambda conn: replace_erp_budget_plan_cache(conn, result))
|
||||||
with open_db_connection() as conn:
|
with open_db_connection() as conn:
|
||||||
ensure_db_schema(conn)
|
ensure_db_schema(conn)
|
||||||
if refresh:
|
|
||||||
result = fetch_erp_budget_plan(page_name, project_code, project_name)
|
|
||||||
replace_erp_budget_plan_cache(conn, result)
|
|
||||||
cached = get_erp_budget_plan_cache(conn, page_name, project_code)
|
cached = get_erp_budget_plan_cache(conn, page_name, project_code)
|
||||||
if not cached:
|
if not cached:
|
||||||
return self._json(404, {'ok': False, 'error': '공사시행계획서 캐시가 없습니다.'})
|
return self._json(404, {'ok': False, 'error': '공사시행계획서 캐시가 없습니다.'})
|
||||||
@@ -4271,9 +4298,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
q = parse_qs(parsed.query)
|
q = parse_qs(parsed.query)
|
||||||
page_name = q.get('page', ['const'])[0]
|
page_name = q.get('page', ['const'])[0]
|
||||||
result = fetch_erp_project_codes(page_name)
|
result = fetch_erp_project_codes(page_name)
|
||||||
with open_db_connection() as conn:
|
sync_info = run_db_write(lambda conn: replace_erp_project_code_cache(conn, result['page'], result['rows']))
|
||||||
ensure_db_schema(conn)
|
|
||||||
sync_info = replace_erp_project_code_cache(conn, result['page'], result['rows'])
|
|
||||||
return self._json(
|
return self._json(
|
||||||
200,
|
200,
|
||||||
{
|
{
|
||||||
@@ -4294,9 +4319,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
project_code = q.get('projectCode', [''])[0]
|
project_code = q.get('projectCode', [''])[0]
|
||||||
project_name = q.get('projectName', [''])[0]
|
project_name = q.get('projectName', [''])[0]
|
||||||
detail = fetch_erp_contract_detail(page_name, project_code, project_name)
|
detail = fetch_erp_contract_detail(page_name, project_code, project_name)
|
||||||
|
sync_info = run_db_write(lambda conn: replace_erp_contract_detail_cache(conn, detail))
|
||||||
with open_db_connection() as conn:
|
with open_db_connection() as conn:
|
||||||
ensure_db_schema(conn)
|
ensure_db_schema(conn)
|
||||||
sync_info = replace_erp_contract_detail_cache(conn, detail)
|
|
||||||
cached = get_erp_contract_detail_cache(conn, page_name, project_code)
|
cached = get_erp_contract_detail_cache(conn, page_name, project_code)
|
||||||
return self._json(
|
return self._json(
|
||||||
200,
|
200,
|
||||||
@@ -4317,9 +4342,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
project_code = q.get('projectCode', [''])[0]
|
project_code = q.get('projectCode', [''])[0]
|
||||||
project_name = q.get('projectName', [''])[0]
|
project_name = q.get('projectName', [''])[0]
|
||||||
result = fetch_erp_bridge_overviews(page_name, project_code, project_name)
|
result = fetch_erp_bridge_overviews(page_name, project_code, project_name)
|
||||||
|
sync_info = run_db_write(lambda conn: replace_erp_bridge_overview_cache(conn, result))
|
||||||
with open_db_connection() as conn:
|
with open_db_connection() as conn:
|
||||||
ensure_db_schema(conn)
|
ensure_db_schema(conn)
|
||||||
sync_info = replace_erp_bridge_overview_cache(conn, result)
|
|
||||||
cached = get_erp_bridge_overview_cache(conn, page_name, project_code)
|
cached = get_erp_bridge_overview_cache(conn, page_name, project_code)
|
||||||
return self._json(
|
return self._json(
|
||||||
200,
|
200,
|
||||||
@@ -4341,9 +4366,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
project_code = q.get('projectCode', [''])[0]
|
project_code = q.get('projectCode', [''])[0]
|
||||||
project_name = q.get('projectName', [''])[0]
|
project_name = q.get('projectName', [''])[0]
|
||||||
result = fetch_erp_budget_plan(page_name, project_code, project_name)
|
result = fetch_erp_budget_plan(page_name, project_code, project_name)
|
||||||
|
sync_info = run_db_write(lambda conn: replace_erp_budget_plan_cache(conn, result))
|
||||||
with open_db_connection() as conn:
|
with open_db_connection() as conn:
|
||||||
ensure_db_schema(conn)
|
ensure_db_schema(conn)
|
||||||
sync_info = replace_erp_budget_plan_cache(conn, result)
|
|
||||||
cached = get_erp_budget_plan_cache(conn, page_name, project_code)
|
cached = get_erp_budget_plan_cache(conn, page_name, project_code)
|
||||||
return self._json(200, {'ok': True, 'syncedAt': sync_info['syncedAt'], 'plan': cached})
|
return self._json(200, {'ok': True, 'syncedAt': sync_info['syncedAt'], 'plan': cached})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -669,13 +669,23 @@
|
|||||||
.replace(/(?<=\d)-(?=\d)/g, '-\n');
|
.replace(/(?<=\d)-(?=\d)/g, '-\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCrossbeamRows(crossbeam) {
|
||||||
|
if (Array.isArray(crossbeam)) {
|
||||||
|
return crossbeam.length ? crossbeam : [{ height: '', length: '', quantity: '', remarks: '' }];
|
||||||
|
}
|
||||||
|
if (crossbeam && typeof crossbeam === 'object') {
|
||||||
|
return [crossbeam];
|
||||||
|
}
|
||||||
|
return [{ height: '', length: '', quantity: '', remarks: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
function renderBudgetPlanInlineTables(plan) {
|
function renderBudgetPlanInlineTables(plan) {
|
||||||
if (!plan) return '-';
|
if (!plan) return '-';
|
||||||
const inputDays = plan.inputDays || {};
|
const inputDays = plan.inputDays || {};
|
||||||
const inputRebar = plan.inputRebar || {};
|
const inputRebar = plan.inputRebar || {};
|
||||||
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
||||||
const predeck = plan.predeck || {};
|
const predeck = plan.predeck || {};
|
||||||
const crossbeam = plan.crossbeam || {};
|
const crossbeamRows = normalizeCrossbeamRows(plan.crossbeam);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="inline-plan-wrap">
|
<div class="inline-plan-wrap">
|
||||||
@@ -739,27 +749,33 @@
|
|||||||
</table>
|
</table>
|
||||||
<table class="plan-table plan-table-split">
|
<table class="plan-table plan-table-split">
|
||||||
<tr>
|
<tr>
|
||||||
<th rowspan="2">프리덱</th>
|
<th colspan="4">프리덱</th>
|
||||||
|
<th colspan="4">가로보</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<th>일반부(㎡)</th>
|
<th>일반부(㎡)</th>
|
||||||
<th>중분대(㎡)</th>
|
<th>중분대(㎡)</th>
|
||||||
<th>방호벽부(㎡)</th>
|
<th>방호벽부(㎡)</th>
|
||||||
<th>비고</th>
|
<th>비고</th>
|
||||||
<th rowspan="2">가로보</th>
|
|
||||||
<th>형고(m)</th>
|
<th>형고(m)</th>
|
||||||
<th>길이(m)</th>
|
<th>길이(m)</th>
|
||||||
<th>수량(EA)</th>
|
<th>수량(EA)</th>
|
||||||
<th>비고</th>
|
<th>비고</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
${crossbeamRows.map((row, index) => `
|
||||||
<td>${escapeHtml(predeck.generalArea || '-')}</td>
|
<tr>
|
||||||
<td>${escapeHtml(predeck.medianArea || '-')}</td>
|
${index === 0 ? `
|
||||||
<td>${escapeHtml(predeck.barrierArea || '-')}</td>
|
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.generalArea || '-')}</td>
|
||||||
<td class="plan-left">${escapeHtml(predeck.remarks || '-')}</td>
|
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.medianArea || '-')}</td>
|
||||||
<td>${escapeHtml(crossbeam.height || '-')}</td>
|
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.barrierArea || '-')}</td>
|
||||||
<td>${escapeHtml(crossbeam.length || '-')}</td>
|
<td rowspan="${crossbeamRows.length}" class="plan-left">${escapeHtml(predeck.remarks || '-')}</td>
|
||||||
<td>${escapeHtml(crossbeam.quantity || '-')}</td>
|
` : ''}
|
||||||
<td class="plan-left">${escapeHtml(crossbeam.remarks || '-')}</td>
|
<td>${escapeHtml(row.height || '-')}</td>
|
||||||
</tr>
|
<td>${escapeHtml(row.length || '-')}</td>
|
||||||
|
<td>${escapeHtml(row.quantity || '-')}</td>
|
||||||
|
<td class="plan-left">${escapeHtml(row.remarks || '-')}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -920,7 +936,7 @@
|
|||||||
const inputRebar = plan.inputRebar || {};
|
const inputRebar = plan.inputRebar || {};
|
||||||
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
||||||
const predeck = plan.predeck || {};
|
const predeck = plan.predeck || {};
|
||||||
const crossbeam = plan.crossbeam || {};
|
const crossbeamRows = normalizeCrossbeamRows(plan.crossbeam);
|
||||||
|
|
||||||
planModalTitle.textContent = `${bridgeName || plan.projectName || '프로젝트'} 공사시행계획서`;
|
planModalTitle.textContent = `${bridgeName || plan.projectName || '프로젝트'} 공사시행계획서`;
|
||||||
planModalBody.innerHTML = `
|
planModalBody.innerHTML = `
|
||||||
@@ -948,10 +964,25 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="plan-section-title">교량제원 - 프리덱 / 가로보</div>
|
<div class="plan-section-title">교량제원 - 프리덱 / 가로보</div>
|
||||||
<table class="plan-table">
|
<table class="plan-table">
|
||||||
<tr><th>프리덱 일반부</th><td>${escapeHtml(predeck.generalArea || '-')}</td><th>가로보 형고</th><td>${escapeHtml(crossbeam.height || '-')}</td></tr>
|
<tr><th colspan="2">프리덱</th><th>형고(m)</th><th>길이(m)</th><th>수량(EA)</th><th>비고</th></tr>
|
||||||
<tr><th>프리덱 중분대</th><td>${escapeHtml(predeck.medianArea || '-')}</td><th>가로보 길이</th><td>${escapeHtml(crossbeam.length || '-')}</td></tr>
|
${crossbeamRows.map((row, index) => `
|
||||||
<tr><th>프리덱 방호벽부</th><td>${escapeHtml(predeck.barrierArea || '-')}</td><th>가로보 수량</th><td>${escapeHtml(crossbeam.quantity || '-')}</td></tr>
|
<tr>
|
||||||
<tr><th>프리덱 비고</th><td>${escapeHtml(predeck.remarks || '-')}</td><th>가로보 비고</th><td>${escapeHtml(crossbeam.remarks || '-')}</td></tr>
|
${index === 0 ? `<th>일반부(㎡)</th><td>${escapeHtml(predeck.generalArea || '-')}</td>` : ''}
|
||||||
|
${index === 1 ? `<th>중분대(㎡)</th><td>${escapeHtml(predeck.medianArea || '-')}</td>` : ''}
|
||||||
|
${index === 2 ? `<th>방호벽부(㎡)</th><td>${escapeHtml(predeck.barrierArea || '-')}</td>` : ''}
|
||||||
|
${index >= 3 ? `<th>비고</th><td>${escapeHtml(predeck.remarks || '-')}</td>` : ''}
|
||||||
|
<td>${escapeHtml(row.height || '-')}</td>
|
||||||
|
<td>${escapeHtml(row.length || '-')}</td>
|
||||||
|
<td>${escapeHtml(row.quantity || '-')}</td>
|
||||||
|
<td>${escapeHtml(row.remarks || '-')}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
${crossbeamRows.length < 4 ? `
|
||||||
|
${crossbeamRows.length <= 0 ? '' : ''}
|
||||||
|
${crossbeamRows.length < 2 ? `<tr><th>중분대(㎡)</th><td>${escapeHtml(predeck.medianArea || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||||||
|
${crossbeamRows.length < 3 ? `<tr><th>방호벽부(㎡)</th><td>${escapeHtml(predeck.barrierArea || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||||||
|
${crossbeamRows.length < 4 ? `<tr><th>비고</th><td>${escapeHtml(predeck.remarks || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||||||
|
` : ''}
|
||||||
</table>
|
</table>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user