Combine report field and price checks into one review workspace
This commit is contained in:
1 parent
2888d6edbc
commit
698d91367c
8 files changed
+273
-87
No files matched your search
@@ -11,7 +11,7 @@ const review=(items,revision=1)=>({request_id:requestId,report_date:task.report_
|
||||
const plain=value=>JSON.parse(JSON.stringify(value));
|
||||
const priceTask={request_id:'c'.repeat(32),report_date:'2026-09-16',status:'needs_review',
|
||||
job_id:'fixture-price-september',can_retry:false};
|
||||
const priceReview=(price=null)=>({case_id:'fixture-price-case',case_status:'open',revision:1,
|
||||
const priceReview=(price=null)=>({case_id:'fixture-price-case',case_status:'open',business_date:priceTask.report_date,revision:1,
|
||||
completed_items:price===null?0:1,total_items:1,
|
||||
items:[{item_id:1,company_key:'Fixture Company',rate_code:'FIXTURE',effective_rate_amount:100,
|
||||
real_price:price,affected_records:1,affected_rooms:1,affected_room_nights:2,candidate_prices:[]}],
|
||||
@@ -48,6 +48,9 @@ test('cross-month field and price dates restore, stay clickable after polling, a
|
||||
assert.equal(h.state.dailyPriceReviewJobId,priceTask.job_id);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,false);
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,true);
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,false);
|
||||
assert.equal(h.element('#manual-review-context').textContent,'2026-09-16 · manual_review.prices');
|
||||
assert.equal(h.element('#arr-download-pending').hidden,false);
|
||||
assert.equal(h.element('#arr-download-date').value,'2026-09-16');
|
||||
assert.doesNotMatch(h.element('#arr-download-pending-list').innerHTML,/ disabled/);
|
||||
await h.loadARRDownloadTask();
|
||||
@@ -56,10 +59,94 @@ test('cross-month field and price dates restore, stay clickable after polling, a
|
||||
await h.selectARRPendingReview(requestId);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,true);
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,false);
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,false);
|
||||
assert.equal(h.element('#manual-review-context').textContent,'2026-10-07 · manual_review.fields');
|
||||
assert.equal(h.element('#arr-download-pending').hidden,false);
|
||||
assert.equal(h.state.arrDataReviewDrafts['1:BLOCK_CODE'],'OCTOBER-DRAFT');
|
||||
assert.equal(h.calls.every(call=>call.method==='GET'),true,'restoring reviews never downloads or changes prices');
|
||||
});
|
||||
|
||||
test('history price entry uses the same workspace and keeps field drafts while switching bodies',async()=>{
|
||||
const h=harness(url=>pendingAPI(url));
|
||||
h.state.arrDownloadPendingReviews=[task,priceTask];
|
||||
await h.acceptARRDownloadTask(task,{sync:false});
|
||||
h.trackARRDataReviewDraft(h.row('1:BLOCK_CODE','FIELD-DRAFT').input);
|
||||
await h.openDailyPriceReview(priceTask.job_id,false);
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,false);
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,true);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,false);
|
||||
assert.equal(h.element('#manual-review-context').textContent,'2026-09-16 · manual_review.prices');
|
||||
assert.equal(h.element('#arr-download-pending').hidden,false);
|
||||
h.renderARRDataReview();
|
||||
h.renderDailyPriceReview();
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,true,'routine renders cannot switch the selected body');
|
||||
await h.selectARRPendingReview(requestId);
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,false);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,true);
|
||||
assert.equal(h.state.arrDataReviewDrafts['1:BLOCK_CODE'],'FIELD-DRAFT');
|
||||
assert.equal(h.calls.every(call=>call.method==='GET'),true);
|
||||
});
|
||||
|
||||
test('terminal reviews hide the workspace without hiding other dates requiring attention',async()=>{
|
||||
const h=harness(url=>pendingAPI(url));
|
||||
h.state.arrDownloadPendingReviews=[task,priceTask];
|
||||
await h.acceptARRDownloadTask(task,{sync:false});
|
||||
await h.openDailyPriceReview(priceTask.job_id,false);
|
||||
for(const case_status of ['completed','cancelled','failed']) {
|
||||
h.state.dailyPriceReview={...priceReview(),case_status};
|
||||
h.renderDailyPriceReview();
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,true);
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,true);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,true);
|
||||
assert.equal(h.element('#arr-download-pending').hidden,false);
|
||||
}
|
||||
h.state.arrDownloadTask={...task,status:'succeeded'};
|
||||
h.state.manualReviewMode='fields';
|
||||
h.renderARRDataReview();
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,true);
|
||||
});
|
||||
|
||||
test('opening fields directly cannot hide an unsaved price edit',async()=>{
|
||||
const h=harness(url=>pendingAPI(url));
|
||||
await h.acceptARRDownloadTask(task,{sync:false});
|
||||
await h.openDailyPriceReview(priceTask.job_id,false);
|
||||
const input={value:'0',closest:()=>({dataset:{dailyPriceReviewItemId:'1'}})};
|
||||
h.element('#daily-price-review-body').querySelectorAll=()=>[input];
|
||||
const count=h.calls.length;
|
||||
await h.loadARRDataReview();
|
||||
assert.equal(h.calls.length,count);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,false);
|
||||
assert.equal(h.element('#arr-data-review-panel').hidden,true);
|
||||
assert.equal(input.value,'0');
|
||||
});
|
||||
|
||||
test('a source context change clears the workspace and ignores an older price response',async()=>{
|
||||
let finishPrice;
|
||||
const h=harness(url=>{
|
||||
if(url.includes('/review?')) return new Promise(resolve=>{finishPrice=()=>resolve(response(200,priceReview()));});
|
||||
if(url==='/api/arr-downloads') return response(200,{context_id:'new-context',ready:true,
|
||||
default_date:'2026-09-17',pending_reviews:[],latest_task:null});
|
||||
throw new Error('unexpected request '+url);
|
||||
});
|
||||
h.state.arrDownloadContextId='old-context';
|
||||
h.state.arrDownloadStorageKey=scopedKey('old-context');
|
||||
h.state.dailyPriceReview=priceReview();
|
||||
h.state.dailyPriceReviewJobId=priceTask.job_id;
|
||||
h.state.dailyPriceReviewOffset=50;
|
||||
const loading=h.loadDailyPriceReview();
|
||||
await h.initARRDownload();
|
||||
assert.equal(h.state.manualReviewMode,'');
|
||||
assert.equal(h.state.dailyPriceReview,null);
|
||||
assert.equal(h.state.dailyPriceReviewJobId,'');
|
||||
assert.equal(h.state.dailyPriceReviewOffset,0);
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,true);
|
||||
finishPrice();
|
||||
await loading;
|
||||
assert.equal(h.state.dailyPriceReview,null);
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,true);
|
||||
assert.equal(h.calls.every(call=>call.method==='GET'),true);
|
||||
});
|
||||
|
||||
test('an authoritative empty pending list overrides legacy and stale current review state',async()=>{
|
||||
let cleared=false;
|
||||
const h=harness(url=>url==='/api/arr-downloads'
|
||||
@@ -96,6 +183,7 @@ for(const status of ['succeeded','failed']) {
|
||||
assert.equal(h.state.arrDownloadTask.status,status);
|
||||
assert.equal(h.state.arrDownloadPendingReviews.length,0);
|
||||
assert.equal(h.element('#arr-download-pending').hidden,true);
|
||||
assert.equal(h.element('#manual-review-workspace').hidden,true);
|
||||
assert.equal(h.element('#daily-price-review-panel').hidden,true);
|
||||
assert.equal(h.calls.some(call=>call.url.includes('/review?')),false);
|
||||
assert.equal(h.calls.every(call=>call.method==='GET'),true);
|
||||
@@ -148,11 +236,13 @@ test('pending date labels have Chinese, English and Thai translations',()=>{
|
||||
const context=vm.createContext({window:{},document:{readyState:'loading',addEventListener(){}}});
|
||||
vm.runInContext(fs.readFileSync(path.resolve(__dirname,'../../arr_web/static/i18n.js'),'utf8'),context);
|
||||
const catalog=context.window.ARRI18n.CATALOG;
|
||||
for(const key of ['arr_download.pending_dates','arr_download.pending_date','arr_download.pending_price','daily_review.save_before_switch']) {
|
||||
for(const key of ['arr_download.pending_dates','arr_download.pending_date','arr_download.pending_price','daily_review.save_before_switch',
|
||||
'manual_review.title','manual_review.fields','manual_review.prices','manual_review.context']) {
|
||||
assert.equal(catalog[key].length,3);
|
||||
catalog[key].forEach(text=>assert.equal(typeof text==='string' && text.length>0,true));
|
||||
}
|
||||
assert.equal(catalog['arr_download.pending_dates'][0],'待处理日期');
|
||||
assert.equal(catalog['arr_download.pending_dates'][0],'需处理日期');
|
||||
assert.equal(catalog['manual_review.title'][0],'人工核对');
|
||||
assert.equal(catalog['arr_download.pending_price'][0],'待复核价格');
|
||||
});
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ function harness(respond) {
|
||||
};
|
||||
const context=vm.createContext({Headers, console, Date, Intl, Uint8Array,
|
||||
document:{hidden:false,querySelector:element},
|
||||
window:{ARRI18n:{t:key=>key,text:value=>value,errorMessage:code=>code,formatInteger:value=>String(value),formatMonth:value=>value},crypto:webcrypto,
|
||||
window:{ARRI18n:{t:(key,values)=>key==='manual_review.context'?`${values.date} · ${values.type}`:key,text:value=>value,errorMessage:code=>code,formatInteger:value=>String(value),formatMonth:value=>value},crypto:webcrypto,
|
||||
setTimeout(){return 1;},clearTimeout(){},location:{replace(){}}},
|
||||
localStorage:{setItem:(k,v)=>storage.set(k,v),getItem:k=>{storageReads.push(k);return storage.get(k);},removeItem:k=>storage.delete(k)},
|
||||
fetch:async (url, options) => {calls.push({url,method:options.method||'GET',body:options.body,csrf:options.headers.get('X-ARR-CSRF')});return respond(url,options,calls);},
|
||||
});
|
||||
const exports='state, api, submitARRDownload, rememberARRIntent, acceptARRDownloadTask, handleARRDownloadDateChange, initARRDownload, loadARRDownloadTask, loadARRDataReview, renderARRDataReview, arrDataReviewCanFinalize, saveARRDataReviewItem, finalizeARRDataReview, trackARRDataReviewDraft, parseARRDataReviewValue, selectARRPendingReview, strictReviewPrice, reviewPriceInput, renderDailyPriceReview, saveDailyPriceReviewItem, loadDailyPriceReview, dailyPriceReviewCanFinalize, dailyPriceReviewHasUnsavedChanges, finalizeDailyPriceReview, confirmDailyPriceReviewCancel';
|
||||
const exports='state, api, submitARRDownload, rememberARRIntent, acceptARRDownloadTask, handleARRDownloadDateChange, initARRDownload, loadARRDownloadTask, loadARRDataReview, renderARRDataReview, arrDataReviewCanFinalize, saveARRDataReviewItem, finalizeARRDataReview, trackARRDataReviewDraft, parseARRDataReviewValue, selectARRPendingReview, strictReviewPrice, reviewPriceInput, renderDailyPriceReview, saveDailyPriceReviewItem, loadDailyPriceReview, openDailyPriceReview, dailyPriceReviewCanFinalize, dailyPriceReviewHasUnsavedChanges, finalizeDailyPriceReview, confirmDailyPriceReviewCancel';
|
||||
vm.runInContext(source.replace(boot,` globalThis.subject = {${exports}};\n})();`),context);
|
||||
const subject=context.subject;
|
||||
Object.assign(subject.state,{arrDownloadReady:true,arrDownloadLoaded:true,arrDownloadStorageKey:'test',arrDownloadUsername:'operator',csrf:'fixture'});
|
||||
|
||||
Reference in new issue
Block a user