기능 개선 — 조치 시작/완료 시각 직접 입력

정비사가 조치 입력 시 시작·완료 시각을 직접 수정할 수 있도록 변경.
현장 처리 후 나중에 입력하는 경우 실제 조치 시간을 정확히 기록 가능.

- repair.html: 자동기록 안내문구 → datetime-local 입력 필드 2개로 교체
  (페이지 로드 시 현재 시각 기본 설정, 편집 모드에서 기존 값 복원)
- repairs.py: POST/PUT 엔드포인트에 started_at_input / completed_at_input
  Form 파라미터 추가, 미입력 시 datetime.now() 유지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
byun
2026-06-01 15:30:33 +09:00
parent 124ad0d165
commit d429ed627d
2 changed files with 40 additions and 6 deletions

View File

@@ -125,12 +125,21 @@ def get_repair(repair_id: int, db: Session = Depends(get_db),
return _fmt_repair(repair) return _fmt_repair(repair)
def _parse_dt(s: Optional[str]) -> Optional[datetime]:
if not s: return None
try:
return datetime.fromisoformat(s)
except Exception:
return None
@router.post("") @router.post("")
async def create_repair( async def create_repair(
report_ids: str = Form(...), report_ids: str = Form(...),
repair_types: str = Form(...), repair_types: str = Form(...),
description: str = Form(...), description: str = Form(...),
result_status: str = Form("done"), result_status: str = Form("done"),
started_at_input: Optional[str] = Form(None),
completed_at_input: Optional[str] = Form(None),
mechanic_lat: Optional[float] = Form(None), mechanic_lat: Optional[float] = Form(None),
mechanic_lng: Optional[float] = Form(None), mechanic_lng: Optional[float] = Form(None),
photos_before: List[UploadFile] = File(default=[]), photos_before: List[UploadFile] = File(default=[]),
@@ -138,13 +147,14 @@ async def create_repair(
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: models.User = Depends(require_mechanic) current_user: models.User = Depends(require_mechanic)
): ):
now = datetime.now()
rids = json.loads(report_ids) rids = json.loads(report_ids)
repair = models.Repair( repair = models.Repair(
mechanic_id=current_user.id, mechanic_id=current_user.id,
repair_types=json.loads(repair_types), repair_types=json.loads(repair_types),
description=description, description=description,
started_at=datetime.now(), started_at=_parse_dt(started_at_input) or now,
completed_at=datetime.now(), completed_at=_parse_dt(completed_at_input) or now,
result_status=result_status, result_status=result_status,
mechanic_lat=mechanic_lat, mechanic_lat=mechanic_lat,
mechanic_lng=mechanic_lng, mechanic_lng=mechanic_lng,
@@ -175,6 +185,8 @@ async def update_repair(
repair_types: str = Form(...), repair_types: str = Form(...),
description: str = Form(...), description: str = Form(...),
result_status: str = Form("done"), result_status: str = Form("done"),
started_at_input: Optional[str] = Form(None),
completed_at_input: Optional[str] = Form(None),
mechanic_lat: Optional[float] = Form(None), mechanic_lat: Optional[float] = Form(None),
mechanic_lng: Optional[float] = Form(None), mechanic_lng: Optional[float] = Form(None),
photos_before: List[UploadFile] = File(default=[]), photos_before: List[UploadFile] = File(default=[]),
@@ -192,7 +204,9 @@ async def update_repair(
repair.repair_types = json.loads(repair_types) repair.repair_types = json.loads(repair_types)
repair.description = description repair.description = description
repair.result_status = result_status repair.result_status = result_status
repair.completed_at = datetime.now() if started_at_input:
repair.started_at = _parse_dt(started_at_input) or repair.started_at
repair.completed_at = _parse_dt(completed_at_input) or datetime.now()
if mechanic_lat is not None: repair.mechanic_lat = mechanic_lat if mechanic_lat is not None: repair.mechanic_lat = mechanic_lat
if mechanic_lng is not None: repair.mechanic_lng = mechanic_lng if mechanic_lng is not None: repair.mechanic_lng = mechanic_lng

View File

@@ -81,8 +81,15 @@
</div> </div>
</div> </div>
<div class="alert alert-info" style="margin-bottom:14px;"> <div class="form-row" style="margin-bottom:14px;">
🕐 조치 시작 시간: <strong id="startedAt"></strong> (자동 기록) <div class="form-group" style="margin-bottom:0">
<label>🕐 조치 시작 시각 <span style="font-size:11px;color:var(--gray4);font-weight:400">(직접 수정 가능)</span></label>
<input type="datetime-local" id="startedAt" style="width:100%">
</div>
<div class="form-group" style="margin-bottom:0">
<label>🏁 조치 완료 시각 <span style="font-size:11px;color:var(--gray4);font-weight:400">(직접 수정 가능)</span></label>
<input type="datetime-local" id="completedAt" style="width:100%">
</div>
</div> </div>
<div id="gpsStatus" class="alert alert-info" style="margin-bottom:14px;"> <div id="gpsStatus" class="alert alert-info" style="margin-bottom:14px;">
@@ -139,8 +146,15 @@ const chargerId = params.get('charger_id'); // 신규 모드
const initReportId = params.get('report_id'); const initReportId = params.get('report_id');
const isEditMode = !!repairId; const isEditMode = !!repairId;
// datetime-local 입력값 포맷: "YYYY-MM-DDTHH:mm"
function toLocalDtInput(date) {
const d = new Date(date);
const pad = n => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
const startTime = new Date(); const startTime = new Date();
document.getElementById('startedAt').textContent = startTime.toLocaleString('ko-KR'); document.getElementById('startedAt').value = toLocalDtInput(startTime);
document.getElementById('completedAt').value = toLocalDtInput(startTime);
const selectedReports = new Set(); const selectedReports = new Set();
if (initReportId) selectedReports.add(parseInt(initReportId)); if (initReportId) selectedReports.add(parseInt(initReportId));
@@ -222,6 +236,8 @@ async function loadEdit() {
// 폼 미리채우기 — 조치유형 동적 로드 후 체크 복원 // 폼 미리채우기 — 조치유형 동적 로드 후 체크 복원
await loadRepairTypes(repair.repair_types || []); await loadRepairTypes(repair.repair_types || []);
document.getElementById('description').value = repair.description || ''; document.getElementById('description').value = repair.description || '';
if (repair.started_at) document.getElementById('startedAt').value = toLocalDtInput(repair.started_at);
if (repair.completed_at) document.getElementById('completedAt').value = toLocalDtInput(repair.completed_at);
const sel = document.getElementById('resultStatus'); const sel = document.getElementById('resultStatus');
if (repair.result_status && sel.querySelector(`option[value="${repair.result_status}"]`)) if (repair.result_status && sel.querySelector(`option[value="${repair.result_status}"]`))
sel.value = repair.result_status; sel.value = repair.result_status;
@@ -318,6 +334,10 @@ async function submitForm(isDone) {
fd.append('repair_types', JSON.stringify(types)); fd.append('repair_types', JSON.stringify(types));
fd.append('description', desc); fd.append('description', desc);
fd.append('result_status', resultStatus); fd.append('result_status', resultStatus);
const startedAtVal = document.getElementById('startedAt').value;
const completedAtVal = document.getElementById('completedAt').value;
if (startedAtVal) fd.append('started_at_input', startedAtVal);
if (completedAtVal) fd.append('completed_at_input', completedAtVal);
if (lat) fd.append('mechanic_lat', lat); if (lat) fd.append('mechanic_lat', lat);
if (lng) fd.append('mechanic_lng', lng); if (lng) fd.append('mechanic_lng', lng);
Array.from(document.getElementById('photosBefore').files).forEach(f => fd.append('photos_before', f)); Array.from(document.getElementById('photosBefore').files).forEach(f => fd.append('photos_before', f));