diff --git a/.project-docs/30-worklog/tasks/20261008-production-review-9e7b.md b/.project-docs/30-worklog/tasks/20261008-production-review-9e7b.md index 7f327bb..c222ade 100644 --- a/.project-docs/30-worklog/tasks/20261008-production-review-9e7b.md +++ b/.project-docs/30-worklog/tasks/20261008-production-review-9e7b.md @@ -289,3 +289,13 @@ Read: memory-index, project-positioning, current-state latest September sections - Confirmed actual acquisition, not a demonstrated frozen page: authenticated local API and read-only queue show new9/15 request6430210d593fc4754dcb8bc292d6c6e3 downloading, started2026-10-08T14:55:58Z. Saved search has85 reservations and hasMore=false. Capture responses advanced from135 at15:01:21Z to157 at15:02:20Z to175 at15:03:04Z; at15:03:06Z completed58 reservation details,58 rate queries and58 profile reads plus initial search, all175 upstream200, no transport failures. Median response2.17s and latest20 mean2.43s; collection is sequential with final source recheck, explaining minutes of sustained download. No source result/final handoff yet. Queue updated_at records the phase start rather than a progress heartbeat, so its unchanged value is not evidence of a stall. - Read-only frontend audit: successful task GET returning downloading correctly preserves that message. A separate unresolved-fetch weakness can hold loading forever (no API timeout); agent reproduced it with an isolated memory-only harness, but it is not the demonstrated cause here because saved acquisition is actively advancing. Record as a future resilience candidate, not a fix claim. Chrome computer-control surface is unavailable, so no claim of directly inspecting the user's Chrome page or its network requests. Backend acquisition evidence is independent of browser. - Outcome/boundaries: explain85 reservations and sequential supplemental reads, with observed progress rather than claiming success or exact completion time. Existing active task left running without restart, retry, new Oracle request initiated by this investigation, cancellation, price/report mutation or code changes. No automated follow-up scheduled. No regression suite needed for a read-only investigation; project-document boundary/structure and whitespace checks apply. Promotion candidate for integration-owned product follow-up: expose completed/total acquisition progress and bound local status-query waits; no business-rule change or new authorization is implied. + +## Same-task Follow-up: Unified Manual Check Layout + +- User requests moving pending dates below the former field-completion heading, renaming that date area “需处理日期”, and combining field completion/price review under “人工核对”. Concurrent Task Gate Passed for same task/feature/codex/owned checkout/branch/base, no peers and no unknown edits. Project Context Loaded: retained required project memory, ADR004/006/007, architecture/domain/evidence/reflection/commitments/stale context; refreshed active task and planning entry/gate. Goal: one date selector area and one manual-check workspace while preserving independent date requests and original field/price rules. Modules: static index/styles/app/i18n and existing JS/UI checks. Constraints: no Oracle acquisition or business value changes, no resets/finalization of real tasks, preserve source drafts and unsaved-price safeguards. Design skill loaded; preserve the existing design, adapting only grouping/labels, responsive spacing and focus. Planning Gate Passed. +- Plan: remove pending-date controls from the download card and create a dedicated “需处理日期” section preceding a shared “人工核对” workspace. Retain field and price content as distinct conditional bodies inside that workspace with explicit subtype guidance, one visible parent heading, existing controls/API/IDs and audited state. Validate source↔price switching, existing guards and empty/terminal states, then verify actual local page and screenshot without entering real prices or confirming generation. Static assets can activate without restarting a service or disturbing active acquisition. +- Outcome: independent “需处理日期” card now occupies the former field-review position below the overview. One “人工核对” parent contains mutually exclusive field-completion and price-review bodies, with current date/type guidance and one visible primary heading. Existing field/price IDs, save/confirm/cancel/pagination and audited endpoints remain. Opening either kind scrolls to the unified heading; cached field→history price→same field correctly restores the field body. Context switches clear the shared mode and cached price display, and late price reads cannot repopulate the old context. No source/pricing/Finance business rule or input value changed. +- Verification:74 related JavaScript cases passed, including4 new shared-workspace cases and strengthened existing cross-month assertions; independent read-only audit reran47 field/review JS cases and found no new blocker.12 existing static UI cases passed, including a new hierarchy check proving the dates sit outside the download form/conditional bodies and both review bodies share the parent. Final root rerun of those12 passed after the CSS correction; node syntax, whitespace and documentation gates passed. Meaningful state checks cover history entry, cached return, terminal hiding while other dates remain, source-context recovery and unsaved0 protection. Existing same-page price redraw/language-change draft limitations are not claimed resolved by this layout change. +- Actual local UI: reloaded only the independent verification tab, then selected9/17 fields→history9/16 prices→same cached9/17 fields→9/15 fields→9/16 prices. At each step the date/type matched and only one review body was visible.9/15 retains its1 missing-room item,9/16 retains its one blank price key0/1,9/17 retains0 active field items; no save, cancellation or generation was triggered. All3 complete review objects match the pre-layout authenticated baseline exactly. Private before/after JSON evidence and screenshots are in `production-validation-20261007/unified-manual-check-{before,after}-20261008.json` and `unified-manual-check-layout-20261008.png`; raw material stays outside Git. +- Responsive result: observed CSS widths375/767/1024/1440 verified. Initial767 check found7px page overflow from the absolute screen-reader label inside the price table; setting the new parent to position:relative bounds that label, and final375/767 checks now have scrollWidth equal clientWidth. Wide tables retain their existing contained scrolling. Temporary viewport overrides were reset. The preserved visual palette/typography was preferred over the design search's unrelated liquid-glass/marketing recommendations. Existing user tabs were not reloaded; result tab retained. Static changes are served by the running8875 instance, without a service restart, new acquisition, hotel write, real review mutation, report generation or remote push. +- Promotion candidate: integration-owned product/data-flow docs should describe independent “需处理日期” navigation followed by a single “人工核对” workspace, keeping source completeness and price decisions as distinct business stages. No conflict with ADR006/007 or additional business-policy confirmation. Remaining action for users is refreshing an existing page to load the new layout, then continuing original pending tasks. diff --git a/arr_web/static/app.js b/arr_web/static/app.js index 6f66686..428f043 100644 --- a/arr_web/static/app.js +++ b/arr_web/static/app.js @@ -26,6 +26,7 @@ arrDownloadUsername: "", arrDownloadContextId: "", arrDownloadPendingReviews: [], + manualReviewMode: "", arrDataReview: null, arrDataReviewRequestId: "", arrDataReviewLoading: false, @@ -391,10 +392,12 @@ $("#arr-download-date").value = task.report_date; await acceptARRDownloadTask(task, { sync: false }); if (arrDownloadNeedsDataReview()) { + state.manualReviewMode = "fields"; state.dailyPriceReview = null; state.dailyPriceReviewJobId = ""; + renderARRDataReview(); renderDailyPriceReview(null); - $("#arr-data-review-panel").scrollIntoView({ behavior: "smooth", block: "start" }); + $("#manual-review-workspace").scrollIntoView({ behavior: "smooth", block: "start" }); $("#arr-data-review-panel").focus({ preventScroll: true }); } else if (task.job_id && ["needs_review", "succeeded", "failed"].includes(task.status)) { state.jobsMonth = task.report_date.slice(0, 7); @@ -577,7 +580,13 @@ state.arrDownloadDisconnected = false; state.arrDownloadSynced = ""; state.arrDownloadPendingReviews = []; + state.manualReviewMode = ""; + state.dailyPriceReview = null; + state.dailyPriceReviewJobId = ""; + state.dailyPriceReviewOffset = 0; + state.dailyPriceReviewLoading = false; resetARRDataReview({ clearDrafts: true }); + renderDailyPriceReview(null); } const authoritativePendingReviews = Array.isArray(config.pending_reviews); const pendingReviews = authoritativePendingReviews ? config.pending_reviews : config.pending_data_reviews; @@ -731,11 +740,29 @@ node.classList.toggle("is-error", isError); } + function renderManualReviewWorkspace(priceReview = state.dailyPriceReview) { + const fieldsAvailable = Boolean(state.arrDataReviewRequestId && arrDownloadNeedsDataReview() + && state.arrDownloadTask.request_id === state.arrDataReviewRequestId); + const pricesAvailable = Boolean(priceReview && ["open", "generation_failed", "processing"].includes(priceReview.case_status)); + const mode = state.manualReviewMode || (fieldsAvailable ? "fields" : "prices"); + const showFields = mode === "fields" && fieldsAvailable; + const showPrices = mode === "prices" && pricesAvailable; + $("#arr-data-review-panel").hidden = !showFields; + $("#daily-price-review-panel").hidden = !showPrices; + $("#manual-review-workspace").hidden = !showFields && !showPrices; + $("#manual-review-title").textContent = I18N.t("manual_review.title"); + const type = I18N.t(showFields ? "manual_review.fields" : "manual_review.prices"); + const priceTask = state.arrDownloadTask?.job_id === state.dailyPriceReviewJobId ? state.arrDownloadTask : null; + const date = showFields ? state.arrDataReview?.report_date || state.arrDownloadTask?.report_date + : priceReview?.business_date || priceReview?.report_date || priceTask?.report_date + || state.jobs.find((job) => job.job_id === state.dailyPriceReviewJobId)?.arrival_date; + $("#manual-review-context").textContent = date ? I18N.t("manual_review.context", { date, type }) : type; + } + function renderARRDataReview() { const panel = $("#arr-data-review-panel"); const review = state.arrDataReview; - panel.hidden = !state.arrDataReviewRequestId || !arrDownloadNeedsDataReview() - || state.arrDownloadTask.request_id !== state.arrDataReviewRequestId; + renderManualReviewWorkspace(); if (panel.hidden) return; const busy = state.arrDataReviewLoading || state.arrDataReviewMutating; const editable = review?.status === "editing" && !busy && !state.arrDataReviewDisconnected && !state.arrDataReviewFinalizing; @@ -786,12 +813,19 @@ async function loadARRDataReview(requestId = state.arrDownloadTask?.request_id, { scroll = false, showErrors = true } = {}) { if (!requestId || state.arrDataReviewLoading || !arrDownloadNeedsDataReview()) return null; + if (state.manualReviewMode === "prices" && dailyPriceReviewHasUnsavedChanges()) { + const message = I18N.t("daily_review.save_before_switch"); + setDailyPriceReviewMessage(message, true); + showToast(message, true); + return null; + } if (state.arrDataReviewRequestId !== requestId) { resetARRDataReview(); state.arrDataReviewRequestId = requestId; state.arrDataReviewDrafts = { ...state.arrDataReviewDraftsByRequest[requestId] }; } const contextId = state.arrDownloadContextId; + state.manualReviewMode = "fields"; state.arrDataReviewLoading = true; renderARRDataReview(); try { @@ -808,7 +842,7 @@ if (item.confirmed && state.arrDataReviewDrafts[item.item_id] === arrDataReviewInputValue(item)) delete state.arrDataReviewDrafts[item.item_id]; }); if (scroll) { - $("#arr-data-review-panel").scrollIntoView({ behavior: "smooth", block: "start" }); + $("#manual-review-workspace").scrollIntoView({ behavior: "smooth", block: "start" }); $("#arr-data-review-panel").focus({ preventScroll: true }); } return review; @@ -1315,13 +1349,12 @@ const panel = $("#daily-price-review-panel"); const body = $("#daily-price-review-body"); const empty = $("#daily-price-review-empty"); + renderManualReviewWorkspace(review); if (!review) { - panel.hidden = true; body.innerHTML = ""; empty.hidden = true; return; } - panel.hidden = false; const busy = state.dailyPriceReviewLoading || state.dailyPriceReviewMutating; const editable = dailyPriceReviewEditable(review); const retryable = review.case_status === "generation_failed"; @@ -1393,31 +1426,33 @@ async function loadDailyPriceReview(jobId = state.dailyPriceReviewJobId, { reset = false, scroll = false, showErrors = true } = {}) { if (!jobId || state.dailyPriceReviewLoading) return; + state.manualReviewMode = "prices"; if (reset || jobId !== state.dailyPriceReviewJobId) { state.dailyPriceReviewJobId = jobId; state.dailyPriceReviewOffset = 0; state.dailyPriceReview = null; } const expectedJobId = state.dailyPriceReviewJobId; + const contextId = state.arrDownloadContextId; state.dailyPriceReviewLoading = true; renderDailyPriceReview(state.dailyPriceReview); try { const review = await api(`/api/jobs/${encodeURIComponent(expectedJobId)}/review?limit=${DAILY_PRICE_REVIEW_PAGE_SIZE}&offset=${state.dailyPriceReviewOffset}`); - if (state.dailyPriceReviewJobId !== expectedJobId) return; + if (state.dailyPriceReviewJobId !== expectedJobId || state.arrDownloadContextId !== contextId) return; state.dailyPriceReview = review; renderDailyPriceReview(review); if (scroll) { const panel = $("#daily-price-review-panel"); - panel.scrollIntoView({ behavior: "smooth", block: "start" }); + $("#manual-review-workspace").scrollIntoView({ behavior: "smooth", block: "start" }); panel.focus({ preventScroll: true }); } } catch (error) { - if (state.dailyPriceReviewJobId === expectedJobId && showErrors) { + if (state.dailyPriceReviewJobId === expectedJobId && state.arrDownloadContextId === contextId && showErrors) { setDailyPriceReviewMessage(error.message, true); showToast(error.message, true); } } finally { - if (state.dailyPriceReviewJobId === expectedJobId) { + if (state.dailyPriceReviewJobId === expectedJobId && state.arrDownloadContextId === contextId) { state.dailyPriceReviewLoading = false; renderDailyPriceReview(state.dailyPriceReview); } diff --git a/arr_web/static/i18n.js b/arr_web/static/i18n.js index c0c615b..d2ffa07 100644 --- a/arr_web/static/i18n.js +++ b/arr_web/static/i18n.js @@ -125,7 +125,7 @@ "arr_download.needs_data_review": ["等待人工完善报表字段", "Awaiting source field review", "รอตรวจสอบข้อมูลรายงานด้วยตนเอง"], "arr_download.complete_data": ["完善报表字段", "Complete report fields", "กรอกข้อมูลรายงานให้ครบ"], "arr_download.date_independent": ["可选其他日期下载,当前待处理内容会保留。", "Choose another date to download; this review will be kept.", "เลือกวันอื่นเพื่อดาวน์โหลดได้ ข้อมูลที่รอตรวจสอบจะยังคงอยู่"], - "arr_download.pending_dates": ["待处理日期", "Dates awaiting review", "วันที่รอดำเนินการ"], + "arr_download.pending_dates": ["需处理日期", "Dates requiring attention", "วันที่ต้องดำเนินการ"], "arr_download.pending_date": ["待补字段", "Complete fields", "รอกรอกข้อมูล"], "arr_download.pending_price": ["待复核价格", "Review prices", "รอตรวจสอบราคา"], "arr_download.source_failed": ["到店数据获取未完成,请继续原任务重试。", "Arrival data could not be fetched. Resume the original task to retry.", "ยังดึงข้อมูลผู้เข้าพักไม่สำเร็จ โปรดดำเนินงานเดิมต่อเพื่อลองอีกครั้ง"], @@ -147,7 +147,11 @@ "arr_download.date_conflict": ["原任务日期不能修改", "The original task date cannot be changed", "ไม่สามารถเปลี่ยนวันที่ของงานเดิม"], "arr_download.retry_unavailable": ["该任务不能继续重试,请查看任务日志", "This task cannot be retried. Check the task log.", "ไม่สามารถลองงานนี้อีกครั้ง โปรดดูบันทึกงาน"], "arr_download.other_active": ["该日期已有进行中的下载任务", "A download for this date is already in progress", "มีงานดาวน์โหลดสำหรับวันที่นี้กำลังดำเนินการอยู่"], - "data_review.title": ["人工完善报表字段", "Complete report fields", "ตรวจสอบและกรอกข้อมูลรายงาน"], + "manual_review.title": ["人工核对", "Manual review", "ตรวจสอบด้วยตนเอง"], + "manual_review.fields": ["完善报表字段", "Complete report fields", "กรอกข้อมูลรายงานให้ครบ"], + "manual_review.prices": ["价格复核", "Review prices", "ตรวจสอบราคา"], + "manual_review.context": ["{date} · {type}", "{date} · {type}", "{date} · {type}"], + "data_review.title": ["完善报表字段", "Complete report fields", "ตรวจสอบและกรอกข้อมูลรายงาน"], "data_review.copy": ["请按各项提示核实来源与原处理规则。可空字段不必补成有值;核实无此信息后,可明确确认留空。全部确认后可生成日报。", "Check each field's source and original processing requirement. Optional fields need no value when none exists; explicitly confirm this after verification. Generate the daily report after all items are confirmed.", "ตรวจสอบแหล่งข้อมูลและข้อกำหนดเดิมของแต่ละช่อง ช่องที่เว้นว่างได้ไม่จำเป็นต้องกรอกค่า หากตรวจสอบแล้วว่าไม่มีข้อมูล ให้ยืนยันการเว้นว่าง สร้างรายงานรายวันได้เมื่อยืนยันครบทุกช่อง"], "data_review.progress": ["{date} · 已确认 {completed} / {total} 项", "{date} · {completed} of {total} confirmed", "{date} · ยืนยันแล้ว {completed} / {total} รายการ"], "data_review.reservation": ["预订信息", "Reservation", "ข้อมูลการจอง"], @@ -250,7 +254,7 @@ "daily_review.open": ["打开人工价格复核,已填写 {completed} / {total}", "Open manual price review, {completed} of {total} completed", "เปิดการตรวจสอบราคาด้วยตนเอง กรอกแล้ว {completed} / {total}"], "daily_review.progress_short": ["复核 {completed} / {total}", "Review {completed} / {total}", "ตรวจสอบ {completed} / {total}"], "daily_review.step": ["价格复核 / 待处理", "PRICE REVIEW / REQUIRED", "ตรวจสอบราคา / ต้องดำเนินการ"], - "daily_review.title": ["人工价格复核", "Manual price review", "ตรวจสอบราคาด้วยตนเอง"], + "daily_review.title": ["价格复核", "Review prices", "ตรวจสอบราคา"], "daily_review.default_instruction": ["请填写每晚处理价,可填 0。每项填写后点击“暂存”,全部暂存后生成日报。相同价格项只需填写一次。", "Enter the nightly price; 0 is allowed. Save each item, then generate the daily report when all items are saved. Enter each identical price key only once.", "กรอกราคาต่อคืน สามารถกรอก 0 ได้ กดบันทึกแต่ละรายการ แล้วสร้างรายงานรายวันเมื่อบันทึกครบทุกรายการ คีย์ราคาเดียวกันกรอกเพียงครั้งเดียว"], "daily_review.processing_instruction": ["正在使用已冻结的价格清单重放原始 XML;此时无法修改。", "The original XML is being replayed with the frozen price list. Prices cannot be changed now.", "กำลังประมวลผล XML ต้นทางซ้ำด้วยรายการราคาที่ตรึงไว้ ขณะนี้ไม่สามารถแก้ไขราคาได้"], "daily_review.progress_aria": ["人工价格复核进度", "Manual price review progress", "ความคืบหน้าการตรวจสอบราคาด้วยตนเอง"], @@ -268,7 +272,7 @@ "daily_review.rooms_nights": ["房数 / 房晚", "Rooms / room nights", "ห้อง / คืนห้องพัก"], "daily_review.manual_price": ["人工价格", "Manual price", "ราคาที่กำหนดด้วยตนเอง"], "daily_review.price_placeholder": ["请输入价格(可填 0)", "Enter price (0 allowed)", "กรอกราคา (กรอก 0 ได้)"], - "daily_review.save_before_switch": ["当前价格尚未暂存,请先暂存,再切换待处理日期。", "Save the current price edits before switching to another review.", "โปรดบันทึกราคาที่แก้ไขก่อนเปลี่ยนไปตรวจสอบวันที่อื่น"], + "daily_review.save_before_switch": ["当前价格尚未暂存,请先暂存,再切换需处理日期。", "Save the current price edits before switching to another review.", "โปรดบันทึกราคาที่แก้ไขก่อนเปลี่ยนไปตรวจสอบวันที่อื่น"], "daily_review.no_candidates": ["暂无同公司 / Rate Code 固定价", "No fixed price for the same company / Rate Code", "ไม่มีราคาคงที่สำหรับบริษัท / Rate Code เดียวกัน"], "daily_review.price_key": ["价格键", "price key", "คีย์ราคา"], "daily_review.manual_price_label": ["{company} {rateCode} 的人工价格", "Manual price for {company} {rateCode}", "ราคาที่กำหนดด้วยตนเองสำหรับ {company} {rateCode}"], diff --git a/arr_web/static/index.html b/arr_web/static/index.html index c4785fc..0ea404e 100644 --- a/arr_web/static/index.html +++ b/arr_web/static/index.html @@ -74,10 +74,6 @@ 正在连接下载服务… -
请核实并逐项保存缺失或有冲突的字段。允许为空的字段也须明确确认,全部完成后才能生成日报。
-| 预订信息 | 待完善字段 | 核实后的值 | 操作 |
|---|
没有待完善字段。
+请为所有缺少固定价的价格键填写每晚价格。相同键只需填写一次。
-