From 6e7d922853822c02d5dbf01f98e56019fd12bcad Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 14 Oct 2026 14:01:39 +0800 Subject: [PATCH] fix(self-operated): align turnover sales eligibility and known zero stock --- .../query/AnalyticsInventorySql.java | 8 ++- .../analytics/query/InventoryTurnoverSql.java | 28 ++++++----- .../self-operated-inventory-turnover-v1.yaml | 2 + .../query/DashboardSqlSemanticTest.java | 20 ++++++++ .../query/InventoryTurnoverSqlTest.java | 49 +++++++++++++++++-- 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsInventorySql.java b/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsInventorySql.java index 9e95ca229..87d711c6c 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsInventorySql.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/AnalyticsInventorySql.java @@ -14,6 +14,12 @@ public final class AnalyticsInventorySql { +" UNION SELECT customer_code,sku_code FROM ll_selfop_sku_profile WHERE profile_run_id=#{p.inventoryRun} AND"+scope(p);} private static String valid(String field){return field+" IS NOT NULL AND "+field+">=0 AND (quality_status='VALID' OR quality_reason IN ('FIELD_MISSING','NEGATIVE_QUANTITY'))";} private static String quantity(String field){return "CASE WHEN MIN(CASE WHEN "+valid(field)+" THEN 1 ELSE 0 END)=1 THEN SUM("+field+") END";} + static String availableQuantity(String validAvailable){ + // A published full snapshot can contain only a valid, empty defective row. + // Do not confuse that known zero with a missing or invalid normal-quality row. + return "CASE WHEN MIN(CASE WHEN product_quality=1 AND quality_status='VALID' AND physical_quantity=0 AND available_quantity=0 AND frozen_quantity=0 THEN 1 ELSE 0 END)=1 THEN 0 " + +"ELSE MAX(CASE WHEN product_quality=0 AND "+validAvailable+" THEN available_quantity END) END"; + } private static String defectiveQuantity(){return "CASE WHEN MIN(CASE WHEN product_quality NOT IN (0,1) THEN 0 WHEN product_quality=1 THEN CASE WHEN "+valid("physical_quantity")+" THEN 1 ELSE 0 END ELSE 1 END)=1 " +"THEN SUM(CASE WHEN product_quality=1 THEN physical_quantity ELSE 0 END) END";} static String cte(AnalyticsSqlRequest p){return cte(p,false);} @@ -31,7 +37,7 @@ public final class AnalyticsInventorySql { +"FROM candidates c LEFT JOIN ll_selfop_sku_profile s ON s.profile_run_id=#{p.salesRun} AND s.source_account=#{p.account} AND s.warehouse_code=#{p.warehouse} AND s.customer_code=c.customer_code AND s.sku_code=c.sku_code " +"LEFT JOIN ll_selfop_sku_profile i ON i.profile_run_id=#{p.inventoryRun} AND i.source_account=#{p.account} AND i.warehouse_code=#{p.warehouse} AND i.customer_code=c.customer_code AND i.sku_code=c.sku_code)," +" stock AS (SELECT customer_code,sku_code,"+quantity("physical_quantity")+" physical_quantity,"+quantity("frozen_quantity")+" frozen_quantity," - +quantity("transit_quantity")+" transit_quantity,MAX(CASE WHEN product_quality=0 AND "+valid("available_quantity")+" THEN available_quantity END) available_quantity," + +quantity("transit_quantity")+" transit_quantity,"+availableQuantity(valid("available_quantity"))+" available_quantity," +defectiveQuantity()+" defective_physical_quantity,MAX(observed_at_utc) observed_at_utc " +"FROM ll_selfop_inventory_snapshot WHERE "+(dashboard&&!p.stockFresh()?"1=0 /* dashboard unused stock */ AND ":"") +"snapshot_run_id=#{p.inventoryRun} AND"+scope(p)+" GROUP BY customer_code,sku_code)," diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSql.java b/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSql.java index 7df9cc1be..f13f745ca 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSql.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSql.java @@ -62,7 +62,7 @@ public final class InventoryTurnoverSql { } private static String cte(Input p) { String stocks="SELECT snapshot_run_id,customer_code,sku_code,"+amount("physical_quantity")+" physical," - +amount("frozen_quantity")+" frozen,MAX(CASE WHEN product_quality=0 AND "+valid("available_quantity")+" THEN available_quantity END) available," + +amount("frozen_quantity")+" frozen,"+AnalyticsInventorySql.availableQuantity(valid("available_quantity"))+" available," +"CASE WHEN MIN(CASE WHEN product_quality NOT IN (0,1) OR product_quality IS NULL THEN 0 WHEN product_quality=1 AND NOT ("+valid("physical_quantity")+") THEN 0 ELSE 1 END)=1 " +"THEN SUM(CASE WHEN product_quality=1 THEN physical_quantity ELSE 0 END) END defective " +"FROM ll_selfop_inventory_snapshot WHERE "+scope("")+" AND (snapshot_run_id=#{p.inventoryRun} OR snapshot_run_id IN ("+sampleRuns(p)+")) " @@ -83,33 +83,35 @@ public final class InventoryTurnoverSql { for(String field:List.of("physical","available","frozen","defective")) sql.append(",CASE WHEN #{p.stockFresh}=1 AND ip.quantity_unit='PCS' THEN st.").append(field).append(" END ").append(field); sql.append(" FROM profiles c JOIN sku_windows w ON w.customer_code=c.customer_code AND w.sku_code=c.sku_code LEFT JOIN last_sales ls ON ls.customer_code=c.customer_code AND ls.sku_code=c.sku_code LEFT JOIN stocks st ON st.snapshot_run_id=#{p.inventoryRun} AND st.customer_code=c.customer_code AND st.sku_code=c.sku_code ") .append("LEFT JOIN fixed_profiles ip ON ip.profile_run_id=#{p.inventoryRun} AND ip.customer_code=c.customer_code AND ip.sku_code=c.sku_code), sku_calculated AS (SELECT b.*,") - .append("CASE WHEN available=0 THEN 0 WHEN available>0 AND w").append(p.query.days()).append("_outbound>0 AND evidence<=#{p.baseFromUtc} THEN available*#{p.query.days}/w").append(p.query.days()).append("_outbound END coverage_days,") + .append("CASE WHEN available=0 THEN 0 WHEN available>0 AND w").append(p.query.days()).append("_outbound>0 THEN available*#{p.query.days}/w").append(p.query.days()).append("_outbound END coverage_days,") .append("CASE WHEN sample_total>0 AND evidence<=#{p.baseFromUtc} THEN w").append(p.query.days()).append("_outbound*#{p.query.days}/sample_total END turnover,") - .append("CASE WHEN physical=0 THEN 0 WHEN physical>0 AND w"+p.query.days()+"_outbound IS NOT NULL AND evidence<=#{p.baseFromUtc} THEN CASE WHEN w"+p.query.days()+"_outbound=0 THEN 1 ELSE 0 END END no_outbound,") - .append("CASE WHEN physical IS NULL OR available IS NULL OR w").append(p.query.days()).append("_outbound IS NULL OR evidence IS NULL OR evidence>#{p.baseFromUtc} THEN 1 ELSE 0 END unassessable FROM sku_base b), sku_result AS (") + .append("CASE WHEN physical=0 THEN 0 WHEN physical>0 AND w"+p.query.days()+"_outbound IS NOT NULL THEN CASE WHEN w"+p.query.days()+"_outbound=0 THEN 1 ELSE 0 END END no_outbound,") + .append("CASE WHEN physical IS NULL OR available IS NULL OR w").append(p.query.days()).append("_outbound IS NULL THEN 1 ELSE 0 END unassessable FROM sku_base b), sku_result AS (") .append("SELECT b.*,CASE WHEN #{p.query.coverageDaysThreshold,jdbcType=INTEGER} IS NULL THEN NULL WHEN physical=0 OR available=0 THEN 0 WHEN coverage_days IS NOT NULL AND available>0 THEN CASE WHEN coverage_days>#{p.query.coverageDaysThreshold,jdbcType=INTEGER} THEN 1 ELSE 0 END WHEN available>0 AND w") .append(p.query.days()).append("_outbound=0 AND unassessable=0 THEN 0 END slow FROM sku_calculated b) "); return sql.toString(); } private static String skuWindows(Input p) { - // One shared suffix table qualifies absent sales dates. Millisecond evidence - // after a midnight selects the next midnight, never the truncated same date. - var sql=new StringBuilder(", evidence_boundaries AS (SELECT start_utc,LAG(start_utc) OVER (ORDER BY start_utc) previous_start"); + // Published warehouse coverage qualifies absent sales dates independently of + // SKU observation age. Actual invalid rows below revoke their zero allowance. + var sql=new StringBuilder(", window_coverage AS (SELECT "); + boolean first=true; for(int n:p.query().windows()) for(String kind:List.of("complete","known","partial")) { String coverage=switch(kind){case "complete"->"coverage=1";case "known"->"coverage>0";default->"coverage=2";}; - sql.append(",SUM(CASE WHEN business_date>=CAST(#{p.from").append(java.util.Set.of(7,15,30).contains(n)?Integer.toString(n):"Base").append("} AS DATE) AND ").append(coverage) - .append(" THEN 1 ELSE 0 END) OVER (ORDER BY start_utc ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) w").append(n).append('_').append(kind); + if(!first)sql.append(',');first=false; + sql.append("SUM(CASE WHEN business_date>=CAST(#{p.from").append(java.util.Set.of(7,15,30).contains(n)?Integer.toString(n):"Base").append("} AS DATE) AND ").append(coverage) + .append(" THEN 1 ELSE 0 END) w").append(n).append('_').append(kind); } sql.append(" FROM calendar), sku_zero_counts AS (SELECT c.customer_code,c.sku_code"); for(int n:p.query().windows()) for(String kind:List.of("complete","known","partial")) sql.append(",CASE WHEN c.unit_valid=1").append("complete".equals(kind)?"":" AND #{p.salesKnown}=1") .append(" THEN COALESCE(b.w").append(n).append('_').append(kind).append(",0) ELSE 0 END w").append(n).append('_').append(kind); - sql.append(" FROM profiles c LEFT JOIN evidence_boundaries b ON c.evidence<=b.start_utc AND (b.previous_start IS NULL OR c.evidence>b.previous_start)), actual_sku_days AS (") + sql.append(" FROM profiles c CROSS JOIN window_coverage b), actual_sku_days AS (") .append("SELECT s.customer_code,s.sku_code,s.business_date,CASE WHEN #{p.salesKnown}=1 AND c.unit_valid=1 AND s.unit_valid=1 THEN s.quantity END observed,") .append("CASE WHEN d.coverage=1 AND c.unit_valid=1 AND s.unit_valid=1 THEN 1 ELSE 0 END complete,") - .append("CASE WHEN d.coverage=1 AND c.unit_valid=1 AND c.evidence<=d.start_utc THEN 1 ELSE 0 END zero_complete,") - .append("CASE WHEN #{p.salesKnown}=1 AND c.unit_valid=1 AND d.coverage>0 AND c.evidence<=d.start_utc THEN 1 ELSE 0 END zero_known,") - .append("CASE WHEN #{p.salesKnown}=1 AND c.unit_valid=1 AND d.coverage=2 AND c.evidence<=d.start_utc THEN 1 ELSE 0 END zero_partial ") + .append("CASE WHEN d.coverage=1 AND c.unit_valid=1 THEN 1 ELSE 0 END zero_complete,") + .append("CASE WHEN #{p.salesKnown}=1 AND c.unit_valid=1 AND d.coverage>0 THEN 1 ELSE 0 END zero_known,") + .append("CASE WHEN #{p.salesKnown}=1 AND c.unit_valid=1 AND d.coverage=2 THEN 1 ELSE 0 END zero_partial ") .append("FROM sales_days s JOIN profiles c ON c.customer_code=s.customer_code AND c.sku_code=s.sku_code JOIN calendar d ON d.business_date=s.business_date), sku_actual_counts AS (SELECT customer_code,sku_code"); for(int n:p.query().windows()) { String range="business_date>=CAST("+windowStart(n)+" AS DATE)"; diff --git a/lastleg-service/src/main/resources/openapi/self-operated-inventory-turnover-v1.yaml b/lastleg-service/src/main/resources/openapi/self-operated-inventory-turnover-v1.yaml index 464a4fdd1..5529a8792 100644 --- a/lastleg-service/src/main/resources/openapi/self-operated-inventory-turnover-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/self-operated-inventory-turnover-v1.yaml @@ -22,6 +22,8 @@ paths: 精确customerCode限定列表与概览;sku、focus及turnoverStatus只筛列表。 按完整候选计算依赖后排序分页,未知末尾,同值按SKU及货主或货主编码稳定排序。 OWNER不计算整体可售天数;销售事实完整即可展示货主实际出库,不受单SKU观察期影响。 + SKU出库及无出库判断同样不受首次观察时间限制:可信完整覆盖的缺销量行可计零;实际异常单位或未覆盖日期保持未知。 + 当前可售天数仍要求有效库存和完整销量窗口;期间件数周转仍要求合格每日库存样本及商品观察证据。 首屏必须current=1且返回概览;后续携带snapshotContext,includeOverview=false省略概览。 不接受attention、salesWindowDays、版本ID或其他视图参数。 parameters: diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java index ce841673c..588b2fcbb 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java @@ -88,6 +88,26 @@ class DashboardSqlSemanticTest { assertEquals(1,topRows.size());var item=(AnalyticsReadRows.Aggregate)topRows.get(0); assertEquals("RECENT",item.skuCode());assertEquals(0,new BigDecimal("10").compareTo(item.windowQuantity()));assertNull(item.sales30Quantity()); } + @Test void emptyValidDefectiveOnlySkuDoesNotBlockAvailableTotalButUnknownsStillDo()throws Exception{ + fixture("NORMAL","120","100",null);fixture("DEFECTIVE-ONLY","0","0",null); + try(var s=connection.createStatement()){ + s.executeUpdate("UPDATE ll_selfop_inventory_snapshot SET product_quality=1 WHERE sku_code='DEFECTIVE-ONLY'"); + } + var stats=report().get(0); + assertEquals(0,count(stats,"available_quantity_unknown")); + assertEquals(0,new BigDecimal("100").compareTo((BigDecimal)stats.get("available_quantity_known"))); + var detail=query(AnalyticsReadMapper.class,"inventoryPage",Map.of("p",windowRequest(D.minusDays(30),D,true,"INVENTORY"),"o",options())) + .stream().map(AnalyticsReadRows.Inventory.class::cast).filter(r->"DEFECTIVE-ONLY".equals(r.skuCode())).findFirst().orElseThrow(); + assertEquals(0,BigDecimal.ZERO.compareTo(detail.availableQuantity())); + // None of these prove a known empty defective-only SKU. + for(String change:List.of("physical_quantity=1","physical_quantity=NULL","quality_status='UNKNOWN',quality_reason='SOURCE_ROW_MISSING'","product_quality=2","product_quality=0,available_quantity=NULL")){ + try(var s=connection.createStatement()){ + s.executeUpdate("UPDATE ll_selfop_inventory_snapshot SET product_quality=1,physical_quantity=0,available_quantity=0,quality_status='VALID',quality_reason=NULL WHERE sku_code='DEFECTIVE-ONLY'"); + s.executeUpdate("UPDATE ll_selfop_inventory_snapshot SET "+change+" WHERE sku_code='DEFECTIVE-ONLY'"); + } + assertEquals(1,count(report().get(0),"available_quantity_unknown"),change); + } + } @Test void combinedReportKeepsStatisticsWhenAllThreeRankingsAreEmpty()throws Exception{ var empty=report();assertEquals(1,empty.size());assertEquals(0,count(empty.get(0),"total_skus")); assertNull(empty.get(0).get("rank_code"));assertNull(empty.get(0).get("physical_quantity_known")); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSqlTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSqlTest.java index b309a398d..682f7d4d1 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSqlTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/InventoryTurnoverSqlTest.java @@ -52,6 +52,15 @@ class InventoryTurnoverSqlTest { decimal(Integer.toString(Math.min(30,n)*10),chart.get("w"+n+"_outbound")); } } + @Test void emptyDefectiveOnlyAvailableIsZeroButMissingNormalAndStaleStockRemainUnknown() throws Exception { + profile(22,"A","EMPTY");stock(22,"A","EMPTY",1,0,0); + var q=request("SKU","ALL","PHYSICAL_DESC",null,null,1,20); + decimal("0",query("page",input(q,30,0,true)).get(0).get("available")); + assertNull(query("page",input(q,30,0,false)).get(0).get("available")); + stock(22,"A","EMPTY",0,0,0); + execute("UPDATE ll_selfop_inventory_snapshot SET available_quantity=NULL,quality_status='UNKNOWN',quality_reason='FIELD_MISSING' WHERE product_quality=0"); + assertNull(query("page",input(q,30,0,true)).get(0).get("available")); + } @Test void threeWindowsAndDailyQualitiesAreNotMultiplied() throws Exception { seed("A","SKU",30); var p=input(request("SKU","ALL","PHYSICAL_DESC",null,null,1,20),30,30,true); @@ -294,14 +303,14 @@ class InventoryTurnoverSqlTest { decimal("0",complete.get("w30_outbound"));decimal("30",complete.get("w30_complete")); } } - @Test void zeroEvidenceUsesExactBeijingMidnightIncludingMilliseconds() throws Exception { + @Test void coveredZeroDoesNotDependOnObservationBeforeWindowMidnight() throws Exception { seed("A","SKU",0);execute("DELETE FROM ll_selfop_sku_daily_sales WHERE warehouse_code='W'"); for(int n:List.of(7,15,30)) for(int millis:List.of(-1,0,1)) { LocalDate start=END.minusDays(n-1);var utc=start.atStartOfDay().minusHours(8).plusNanos(millis*1_000_000L); execute("UPDATE ll_selfop_sku_profile SET earliest_evidence_at_utc=TIMESTAMP '"+utc.toString().replace('T',' ')+"'"); var p=input(request("SKU","ALL","PHYSICAL_DESC",null,null,1,20),30,0,true); - var row=reportRows(p).get(0);decimal(String.valueOf(n-(millis>0?1:0)),row.get("w"+n+"_complete")); - if(millis>0)assertNull(row.get("w"+n+"_outbound"));else decimal("0",row.get("w"+n+"_outbound")); + var row=reportRows(p).get(0);decimal(String.valueOf(n),row.get("w"+n+"_complete")); + decimal("0",row.get("w"+n+"_outbound")); execute("INSERT INTO ll_selfop_sku_daily_sales VALUES(11,'demo','W','A','SKU',DATE '"+start+"',0,'PCS')"); row=reportRows(p).get(0);decimal(String.valueOf(n),row.get("w"+n+"_complete"));decimal("0",row.get("w"+n+"_outbound")); execute("DELETE FROM ll_selfop_sku_daily_sales WHERE warehouse_code='W'"); @@ -312,13 +321,45 @@ class InventoryTurnoverSqlTest { var p=input(request("SKU","ALL","PHYSICAL_DESC",null,null,1,20),1,0,true); var afterLast=END.atStartOfDay().minusHours(8).plusNanos(1_000_000); execute("UPDATE ll_selfop_sku_profile SET earliest_evidence_at_utc=TIMESTAMP '"+afterLast.toString().replace('T',' ')+"'"); - var row=reportRows(p).get(0);decimal("0",row.get("w7_complete"));assertNull(row.get("w7_observed"));decimal("0",row.get("w7_partial")); + var row=reportRows(p).get(0);decimal("1",row.get("w7_complete"));decimal("0",row.get("w7_observed"));decimal("0",row.get("w7_partial")); execute("INSERT INTO ll_selfop_sku_daily_sales VALUES(11,'demo','W','A','SKU',DATE '"+END+"',1.234567,'PCS')"); row=reportRows(p).get(0);decimal("1",row.get("w7_complete"));decimal("1.234567",row.get("w7_observed")); execute("UPDATE ll_selfop_sku_profile SET earliest_evidence_at_utc=TIMESTAMP '2026-01-01 00:00:00'"); execute("UPDATE ll_selfop_sku_daily_sales SET quantity_unit='UNKNOWN' WHERE warehouse_code='W'"); row=reportRows(p).get(0);decimal("0",row.get("w7_complete"));assertNull(row.get("w7_observed"));decimal("0",row.get("w7_partial")); } + @Test void noObservationAgeDoesNotHideWindowSalesCoverageOrNoOutboundSummary() throws Exception { + seed("A","SOLD",0);seed("A","UNSOLD",0); + execute("UPDATE ll_selfop_sku_profile SET evidence_run_id=NULL,evidence_type=NULL,earliest_evidence_at_utc=NULL"); + execute("DELETE FROM ll_selfop_sku_daily_sales WHERE sku_code='UNSOLD'"); + var q=request("SKU","ALL","PHYSICAL_DESC",null,30,1,20); + var p=input(q,30,0,true); + var rows=reportRows(p); + var sold=rows.stream().filter(r->"SOLD".equals(r.get("sku_code"))).findFirst().orElseThrow(); + decimal("300",sold.get("w30_outbound"));decimal("60",sold.get("coverage_days"));decimal("0",sold.get("unassessable")); + assertNull(sold.get("turnover"));decimal("0",sold.get("sample_count")); + var unsold=rows.stream().filter(r->"UNSOLD".equals(r.get("sku_code"))).findFirst().orElseThrow(); + decimal("0",unsold.get("w30_outbound"));decimal("1",unsold.get("no_outbound"));decimal("0",unsold.get("unassessable")); + assertNull(unsold.get("coverage_days")); + var summary=query("overview",p).get(0); + decimal("1",summary.get("no_outbound_count"));decimal("0",summary.get("no_outbound_unknown")); + var filtered=reportRows(input(request("SKU","NO_OUTBOUND_IN_WINDOW","PHYSICAL_DESC",null,null,1,20),30,0,true)); + assertEquals(List.of("UNSOLD"),filtered.stream().map(r->r.get("sku_code")).toList()); + summary=query("overview",input(q,7,0,true)).get(0); + decimal("2",summary.get("no_outbound_unknown")); + // Page projection preserves null stock types in H2 when freshness is a bound false. + var stale=query("page",input(q,30,0,false)); + assertTrue(stale.stream().allMatch(r->r.get("no_outbound")==null)); + } + @Test void oneUnknownAvailableQuantityKeepsSubtotalWithoutHidingIndependentNoOutboundCount() throws Exception { + seed("A","KNOWN",0);seed("A","UNKNOWN",0); + execute("DELETE FROM ll_selfop_sku_daily_sales WHERE warehouse_code='W'"); + execute("UPDATE ll_selfop_inventory_snapshot SET available_quantity=NULL,quality_status='UNKNOWN',quality_reason='FIELD_MISSING' WHERE snapshot_run_id=22 AND sku_code='UNKNOWN' AND product_quality=0"); + var summary=query("overview",input(request("SKU","ALL","PHYSICAL_DESC",null,null,1,20),30,0,true)).get(0); + decimal("1220",summary.get("physical"));assertNull(summary.get("available")); + decimal("600",summary.get("available_known"));decimal("1",summary.get("available_unknown")); + decimal("2",summary.get("no_outbound_count"));decimal("0",summary.get("no_outbound_unknown")); + } @Test void presentInvalidSamplesNeverQualifyAndOwnerZeroNeedsEverySku() throws Exception { seed("A","ONE",30);seed("A","TWO",30); String key=" WHERE snapshot_run_id=100 AND sku_code='TWO' AND product_quality=0";