docs: plan extended return search filters
This commit is contained in:
@@ -0,0 +1,652 @@
|
||||
# 173/174 Extended Return Search Column and Local Filter Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a persistent search-column selector and DevExpress in-memory auto-filter row to the shared 173/174 popup without changing existing value display, ResultFields mapping, clearing, or selection behavior.
|
||||
|
||||
**Architecture:** Extend the shared SQL builder to distinguish visible searchable columns from hidden technical columns, then extend the shared popup to own column selection and local filtering state. The MyControl and grid hosts continue to own caption translation and asynchronous schema/query lifecycles; each request snapshots the selected real field name so background execution is deterministic.
|
||||
|
||||
**Tech Stack:** C# on .NET Framework 4.0, WinForms, DevExpress `PopupContainerEdit`/`ComboBoxEdit`/`GridControl`/`GridView`, ADO.NET provider factory, SQL Server-compatible derived-table SQL.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Work in `E:\lserp_project\lserp_cs_6.0` on the current `main` branch and preserve unrelated work.
|
||||
- Do not add NuGet dependencies or require a framework newer than .NET Framework 4.0.
|
||||
- Keep 173 module SQL resolution and 174 configured SourceSql resolution unchanged.
|
||||
- Keep ResultFields, ValueMember, TextMember, display-value translation, clear, mouse selection, arrow navigation, and Enter selection unchanged.
|
||||
- Do not load complete lookup data during parent control initialization.
|
||||
- Opening the popup may asynchronously execute only the existing `top 0` structure query.
|
||||
- The selector displays translated result-grid captions but stores real source field names.
|
||||
- Columns whose real names start with `_` are hidden from the selector and excluded from “所有列” database predicates.
|
||||
- A new database search clears old DevExpress local filters and old results.
|
||||
- DevExpress auto-filtering operates only on the returned result table and must not issue a database request.
|
||||
- Preserve the last valid selector choice per popup instance; fall back to “所有列” only after a loaded schema proves that field no longer exists.
|
||||
- The automatic popup-open lookup for an existing business value always searches all visible columns; it must not consume or reset the user's preserved selector choice.
|
||||
- Keep the database result limit at 100 rows and continue using `@lookupKeyword` with existing LIKE escaping.
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
- Modify `插件库/Lskj.Control/Model/ExtendedReturnSupport.cs`: central hidden-column rule and selected-column SQL generation.
|
||||
- Modify `插件库/Lskj.Control/AutoGridLookUp/ExtendedReturnSearchPopup.cs`: selector UI, selection persistence, result/filter reset, and auto-filter row.
|
||||
- Modify `插件库/Lskj.Control/AutoGridLookUp/LabelExtendedReturnSearchEdit.cs`: MyControl schema-only requests, translated selector options, and selected-field request snapshots.
|
||||
- Modify `插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs`: grid schema-only requests, source-aware schema cache, translated selector options, and selected-field request snapshots.
|
||||
- Do not add production source files or change `Lskj.Control.csproj`.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Make selected-column SQL generation a tested shared contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `插件库/Lskj.Control/Model/ExtendedReturnSupport.cs:22-149`
|
||||
- Test temporarily: `E:\lserp_project\codex_extended_return_support_tests.cs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `string sourceSql`, `DataColumnCollection columns`, `int maxRows`, and optional real `string searchField`.
|
||||
- Produces: `IsSearchableColumn(string columnName)` and the overload `BuildSearchSql(string sourceSql, DataColumnCollection columns, int maxRows, string searchField)` while preserving the existing three-argument overload.
|
||||
|
||||
- [ ] **Step 1: Write the failing SQL contract harness**
|
||||
|
||||
Create the temporary test source with this exact behavior:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Data;
|
||||
using Lskj.Control.Model;
|
||||
|
||||
internal static class ExtendedReturnSupportContractTests
|
||||
{
|
||||
private static void Require(bool value, string message)
|
||||
{
|
||||
if (!value) throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
private static void RequireThrows(Action action, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
DataTable schema = new DataTable();
|
||||
schema.Columns.Add("productid");
|
||||
schema.Columns.Add("appellation");
|
||||
schema.Columns.Add("_rowid");
|
||||
|
||||
string allSql = ExtendedReturnSupport.BuildSearchSql(
|
||||
"select productid, appellation, _rowid from p_ProductTab",
|
||||
schema.Columns,
|
||||
100,
|
||||
string.Empty);
|
||||
Require(allSql.Contains("[productid]"), "all columns omitted productid");
|
||||
Require(allSql.Contains("[appellation]"), "all columns omitted appellation");
|
||||
Require(!allSql.Contains("[_rowid]"), "all columns included hidden field");
|
||||
|
||||
string oneSql = ExtendedReturnSupport.BuildSearchSql(
|
||||
"select productid, appellation, _rowid from p_ProductTab",
|
||||
schema.Columns,
|
||||
100,
|
||||
"appellation");
|
||||
Require(oneSql.Contains("[appellation]"), "selected column omitted appellation");
|
||||
Require(!oneSql.Contains("[productid]"), "selected column included productid");
|
||||
Require(!oneSql.Contains("[_rowid]"), "selected column included hidden field");
|
||||
|
||||
Require(!ExtendedReturnSupport.IsSearchableColumn("_rowid"), "hidden field is searchable");
|
||||
Require(ExtendedReturnSupport.IsSearchableColumn("productid"), "business field is not searchable");
|
||||
RequireThrows(delegate
|
||||
{
|
||||
ExtendedReturnSupport.BuildSearchSql("select 1", schema.Columns, 100, "_rowid");
|
||||
}, "hidden selected field did not fail");
|
||||
RequireThrows(delegate
|
||||
{
|
||||
ExtendedReturnSupport.BuildSearchSql("select 1", schema.Columns, 100, "missing");
|
||||
}, "missing selected field did not fail");
|
||||
|
||||
Console.WriteLine("ExtendedReturnSupport contract tests passed.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Compile the harness to prove it fails before implementation**
|
||||
|
||||
Run the production support file and harness through the installed Roslyn compiler with .NET Framework 4.0 references:
|
||||
|
||||
```bat
|
||||
dotnet "C:\Program Files\dotnet\sdk\10.0.201\Roslyn\bincore\csc.dll" /nologo /target:exe /out:"E:\lserp_project\codex_extended_return_support_tests.exe" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Data.dll" "插件库\Lskj.Control\Model\ExtendedReturnSupport.cs" "E:\lserp_project\codex_extended_return_support_tests.cs"
|
||||
```
|
||||
|
||||
Expected: compilation fails because the four-argument overload and `IsSearchableColumn` do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the visible-column rule and overload**
|
||||
|
||||
Keep the old overload as a compatibility wrapper and select columns using case-insensitive real field names:
|
||||
|
||||
```csharp
|
||||
public static bool IsSearchableColumn(string columnName)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(columnName) &&
|
||||
!columnName.StartsWith("_", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static string BuildSearchSql(
|
||||
string sourceSql,
|
||||
DataColumnCollection columns,
|
||||
int maxRows)
|
||||
{
|
||||
return BuildSearchSql(sourceSql, columns, maxRows, string.Empty);
|
||||
}
|
||||
|
||||
public static string BuildSearchSql(
|
||||
string sourceSql,
|
||||
DataColumnCollection columns,
|
||||
int maxRows,
|
||||
string searchField)
|
||||
{
|
||||
if (columns == null || columns.Count == 0)
|
||||
throw new InvalidOperationException("搜索 SQL 没有返回任何可查询列。");
|
||||
if (maxRows <= 0)
|
||||
throw new ArgumentOutOfRangeException("maxRows", "最大返回行数必须大于零。");
|
||||
|
||||
List<DataColumn> selectedColumns = new List<DataColumn>();
|
||||
foreach (DataColumn column in columns)
|
||||
{
|
||||
if (!IsSearchableColumn(column.ColumnName)) continue;
|
||||
if (string.IsNullOrWhiteSpace(searchField) ||
|
||||
string.Equals(column.ColumnName, searchField, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
selectedColumns.Add(column);
|
||||
}
|
||||
}
|
||||
if (selectedColumns.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(string.IsNullOrWhiteSpace(searchField)
|
||||
? "搜索 SQL 没有返回可见业务列。"
|
||||
: string.Format("搜索字段“{0}”不存在或属于隐藏技术列。", searchField));
|
||||
}
|
||||
|
||||
string alias = QuoteIdentifier(SearchAlias);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendFormat("select top ({0}) * from ({1}) as {2} where ",
|
||||
maxRows, NormalizeSourceSql(sourceSql), alias);
|
||||
for (int i = 0; i < selectedColumns.Count; i++)
|
||||
{
|
||||
if (i > 0) builder.Append(" or ");
|
||||
builder.Append("convert(nvarchar(4000), ");
|
||||
builder.Append(alias);
|
||||
builder.Append('.');
|
||||
builder.Append(QuoteIdentifier(selectedColumns[i].ColumnName));
|
||||
builder.Append(") like ");
|
||||
builder.Append(SearchParameterName);
|
||||
builder.Append(" escape N'\\'");
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Compile and run the contract harness**
|
||||
|
||||
Run the same compiler command, then:
|
||||
|
||||
```bat
|
||||
E:\lserp_project\codex_extended_return_support_tests.exe
|
||||
```
|
||||
|
||||
Expected: exit code `0` and `ExtendedReturnSupport contract tests passed.`
|
||||
|
||||
- [ ] **Step 5: Remove temporary harness artifacts and commit Task 1**
|
||||
|
||||
```bat
|
||||
del "E:\lserp_project\codex_extended_return_support_tests.cs"
|
||||
del "E:\lserp_project\codex_extended_return_support_tests.exe"
|
||||
git add -- "插件库/Lskj.Control/Model/ExtendedReturnSupport.cs"
|
||||
git diff --cached --check
|
||||
git commit -m "feat(control): support selected extended search columns"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Add selector persistence and local filter UI to the shared popup
|
||||
|
||||
**Files:**
|
||||
- Modify: `插件库/Lskj.Control/AutoGridLookUp/ExtendedReturnSearchPopup.cs:14-232`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: translated column options as `IEnumerable<KeyValuePair<string, string>>`, where key is the real field and value is the displayed caption.
|
||||
- Produces: `SelectedSearchField`, `SetSearchColumns(...)`, and `ClearResultAndFilter()` for both hosts.
|
||||
|
||||
- [ ] **Step 1: Record failing structural assertions**
|
||||
|
||||
Run this PowerShell check before editing:
|
||||
|
||||
```powershell
|
||||
$path = '插件库\Lskj.Control\AutoGridLookUp\ExtendedReturnSearchPopup.cs'
|
||||
$text = Get-Content -LiteralPath $path -Raw
|
||||
$required = @(
|
||||
'ComboBoxEdit searchFieldEdit',
|
||||
'searchPanel.ColumnCount = 4',
|
||||
'ShowAutoFilterRow = true',
|
||||
'SelectedSearchField',
|
||||
'SetSearchColumns',
|
||||
'ClearResultAndFilter'
|
||||
)
|
||||
foreach ($item in $required) {
|
||||
if (-not $text.Contains($item)) { throw "Missing popup contract: $item" }
|
||||
}
|
||||
```
|
||||
|
||||
Expected: failure on the first missing popup contract.
|
||||
|
||||
- [ ] **Step 2: Add the selector option model and public popup contract**
|
||||
|
||||
Add `using Lskj.Control.Model;` and `using System.Collections.Generic;`. Inside `ExtendedReturnSearchPopup`, add a private option object and the host-facing API:
|
||||
|
||||
```csharp
|
||||
private sealed class SearchColumnOption
|
||||
{
|
||||
public SearchColumnOption(string fieldName, string caption)
|
||||
{
|
||||
FieldName = fieldName ?? string.Empty;
|
||||
Caption = caption ?? string.Empty;
|
||||
}
|
||||
|
||||
public string FieldName { get; private set; }
|
||||
public string Caption { get; private set; }
|
||||
public override string ToString() { return Caption; }
|
||||
}
|
||||
|
||||
public string SelectedSearchField
|
||||
{
|
||||
get
|
||||
{
|
||||
SearchColumnOption option = searchFieldEdit.SelectedItem as SearchColumnOption;
|
||||
return option == null ? string.Empty : option.FieldName;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSearchColumns(IEnumerable<KeyValuePair<string, string>> columns)
|
||||
{
|
||||
string preferredField = selectedSearchField;
|
||||
searchFieldEdit.Properties.BeginUpdate();
|
||||
try
|
||||
{
|
||||
searchFieldEdit.Properties.Items.Clear();
|
||||
SearchColumnOption all = new SearchColumnOption(string.Empty, "所有列");
|
||||
searchFieldEdit.Properties.Items.Add(all);
|
||||
SearchColumnOption selected = null;
|
||||
if (columns != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> column in columns)
|
||||
{
|
||||
if (!ExtendedReturnSupport.IsSearchableColumn(column.Key)) continue;
|
||||
SearchColumnOption option = new SearchColumnOption(column.Key, column.Value);
|
||||
searchFieldEdit.Properties.Items.Add(option);
|
||||
if (string.Equals(option.FieldName, preferredField, StringComparison.OrdinalIgnoreCase))
|
||||
selected = option;
|
||||
}
|
||||
}
|
||||
searchFieldEdit.SelectedItem = selected ?? all;
|
||||
selectedSearchField = selected == null ? string.Empty : selected.FieldName;
|
||||
}
|
||||
finally
|
||||
{
|
||||
searchFieldEdit.Properties.EndUpdate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add `selectedSearchField` and update it from `SelectedIndexChanged`; `PrepareForOpen()` must not reset it.
|
||||
|
||||
- [ ] **Step 3: Change the top layout and keep input focus behavior**
|
||||
|
||||
Instantiate a `ComboBoxEdit`, set `TextEditStyle = TextEditStyles.DisableTextEditor`, and change the layout to four columns:
|
||||
|
||||
```csharp
|
||||
searchPanel.ColumnCount = 4;
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120F));
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 72F));
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 72F));
|
||||
searchPanel.Controls.Add(searchFieldEdit, 0, 0);
|
||||
searchPanel.Controls.Add(searchEdit, 1, 0);
|
||||
searchPanel.Controls.Add(queryButton, 2, 0);
|
||||
searchPanel.Controls.Add(clearButton, 3, 0);
|
||||
```
|
||||
|
||||
Set selector/input/query/clear tab indices to `0/1/2/3`. Keep `FocusSearchEditor()` focused on `searchEdit` so opening the popup still allows immediate typing.
|
||||
|
||||
- [ ] **Step 4: Enable and reset DevExpress local filtering**
|
||||
|
||||
Enable the built-in filter row and centralize clearing:
|
||||
|
||||
```csharp
|
||||
resultView.OptionsView.ShowAutoFilterRow = true;
|
||||
|
||||
public void ClearResultAndFilter()
|
||||
{
|
||||
resultView.ClearColumnsFilter();
|
||||
resultGrid.DataSource = null;
|
||||
}
|
||||
|
||||
public void PrepareForOpen()
|
||||
{
|
||||
searchEdit.Text = string.Empty;
|
||||
ClearResultAndFilter();
|
||||
}
|
||||
```
|
||||
|
||||
Do not attach the selector or auto-filter row to `SearchRequested`; only Enter in `searchEdit` and the query button raise database search events.
|
||||
|
||||
- [ ] **Step 5: Run structural assertions and compile `Lskj.Control`**
|
||||
|
||||
Run the Step 1 PowerShell check and the repository’s isolated C# compile helper. Expected: structural check exit `0` and `Lskj.Control` compile exit `0`.
|
||||
|
||||
- [ ] **Step 6: Commit Task 2**
|
||||
|
||||
```bat
|
||||
git add -- "插件库/Lskj.Control/AutoGridLookUp/ExtendedReturnSearchPopup.cs"
|
||||
git diff --cached --check
|
||||
git commit -m "feat(control): add extended lookup filter UI"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Integrate schema-only loading and selected fields in MyControl
|
||||
|
||||
**Files:**
|
||||
- Modify: `插件库/Lskj.Control/AutoGridLookUp/LabelExtendedReturnSearchEdit.cs:37-60,294-360,616-760`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `popup.SelectedSearchField`, `popup.SetSearchColumns(...)`, the existing `GetSchema(...)`, and the four-argument SQL builder.
|
||||
- Produces: schema-only asynchronous requests and result delivery that configures selector options before optionally binding rows.
|
||||
|
||||
- [ ] **Step 1: Record failing MyControl integration assertions**
|
||||
|
||||
```powershell
|
||||
$path = '插件库\Lskj.Control\AutoGridLookUp\LabelExtendedReturnSearchEdit.cs'
|
||||
$text = Get-Content -LiteralPath $path -Raw
|
||||
$required = @(
|
||||
'public string SearchField;',
|
||||
'public bool SchemaOnly;',
|
||||
'public DataTable Schema;',
|
||||
'popup.SelectedSearchField',
|
||||
'popup.SetSearchColumns',
|
||||
'MaxRows, request.SearchField'
|
||||
)
|
||||
foreach ($item in $required) {
|
||||
if (-not $text.Contains($item)) { throw "Missing MyControl integration: $item" }
|
||||
}
|
||||
```
|
||||
|
||||
Expected: failure before implementation.
|
||||
|
||||
- [ ] **Step 2: Extend request/result snapshots**
|
||||
|
||||
Add exact state to the existing nested classes:
|
||||
|
||||
```csharp
|
||||
private sealed class SearchRequest
|
||||
{
|
||||
public int Version;
|
||||
public string SourceSql;
|
||||
public string Keyword;
|
||||
public string SearchField;
|
||||
public string ConnectionString;
|
||||
public bool SchemaOnly;
|
||||
}
|
||||
|
||||
private sealed class QueryResult
|
||||
{
|
||||
public DataTable Schema;
|
||||
public DataTable Table;
|
||||
public Exception Error;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Queue schema-only work on popup open**
|
||||
|
||||
Resolve `sourceSql` once in `PopupEdit_QueryPopUp`. After `PrepareForOpen()`:
|
||||
|
||||
```csharp
|
||||
DataTable cachedSchema = TryGetSchema(sourceSql);
|
||||
if (cachedSchema != null) ConfigureSearchColumns(cachedSchema);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(EditValue))
|
||||
QueueSearch(sourceSql, EditValue, string.Empty, false);
|
||||
else if (cachedSchema == null)
|
||||
QueueSearch(sourceSql, string.Empty, string.Empty, true);
|
||||
else
|
||||
CancelSearch();
|
||||
```
|
||||
|
||||
`TryGetSchema` must take `schemaSyncRoot` and return the cached table only when `schemaSql` equals the resolved SQL. `QueueSearch` replaces the old `StartSearch` request construction and snapshots `SearchField` and `SchemaOnly`. The popup-open lookup deliberately uses an empty search field so an existing actual ID is found independently of the preserved selector; only explicit Enter/query actions use `popup.SelectedSearchField`.
|
||||
|
||||
- [ ] **Step 4: Build translated options from schema**
|
||||
|
||||
Use existing caption translation and shared visibility rules:
|
||||
|
||||
```csharp
|
||||
private void ConfigureSearchColumns(DataTable sourceSchema)
|
||||
{
|
||||
List<KeyValuePair<string, string>> options = new List<KeyValuePair<string, string>>();
|
||||
IList<ExtendedReturnFieldMapping> mappings =
|
||||
ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
foreach (DataColumn column in sourceSchema.Columns)
|
||||
{
|
||||
if (!ExtendedReturnSupport.IsSearchableColumn(column.ColumnName)) continue;
|
||||
options.Add(new KeyValuePair<string, string>(
|
||||
column.ColumnName,
|
||||
GetColumnCaption(column.ColumnName, mappings)));
|
||||
}
|
||||
popup.SetSearchColumns(options);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Make query and delivery schema-aware**
|
||||
|
||||
`Query(SearchRequest request)` must always load and validate schema, set `result.Schema`, and execute row SQL only when `!request.SchemaOnly`:
|
||||
|
||||
```csharp
|
||||
DataTable sourceSchema = GetSchema(request.SourceSql, request.ConnectionString);
|
||||
result.Schema = sourceSchema;
|
||||
IList<ExtendedReturnFieldMapping> mappings =
|
||||
ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
ValidateSourceColumns(sourceSchema.Columns, mappings);
|
||||
if (!request.SchemaOnly)
|
||||
{
|
||||
string searchSql = ExtendedReturnSupport.BuildSearchSql(
|
||||
request.SourceSql, sourceSchema.Columns, MaxRows, request.SearchField);
|
||||
result.Table = ExecuteQuery(
|
||||
searchSql,
|
||||
ExtendedReturnSupport.BuildLikeParameterValue(request.Keyword),
|
||||
request.ConnectionString);
|
||||
}
|
||||
```
|
||||
|
||||
Change delivery to receive the request, reject stale versions, call `ConfigureSearchColumns(result.Schema)`, and bind/configure result rows only when `result.Table != null`.
|
||||
|
||||
- [ ] **Step 6: Clear local filters on explicit database search**
|
||||
|
||||
In `Popup_SearchRequested`, call `popup.ClearResultAndFilter()` before queuing any non-empty keyword. Capture `popup.SelectedSearchField` in that same UI event. Empty keywords cancel work and leave a cleared result.
|
||||
|
||||
- [ ] **Step 7: Run assertions and compile through `Lskj.Control`**
|
||||
|
||||
Run the Step 1 check. Compile `Lskj.Model`, `Lskj.Util`, `Lskj.Business`, then `Lskj.Control` into an isolated dependency directory. Expected: all exits `0`.
|
||||
|
||||
- [ ] **Step 8: Commit Task 3**
|
||||
|
||||
```bat
|
||||
git add -- "插件库/Lskj.Control/AutoGridLookUp/LabelExtendedReturnSearchEdit.cs"
|
||||
git diff --cached --check
|
||||
git commit -m "feat(control): add form extended lookup column search"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Integrate schema-only loading and selected fields in GridControlEx
|
||||
|
||||
**Files:**
|
||||
- Modify: `插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs:32-85,323-449,621-905,1189-1220`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: the shared popup contract and selected-column SQL builder from Tasks 1-2.
|
||||
- Produces: per-grid-column source-aware schema caching, schema-only requests, and selected-field request snapshots within the existing serialized request state machine.
|
||||
|
||||
- [ ] **Step 1: Record failing grid integration assertions**
|
||||
|
||||
```powershell
|
||||
$path = '插件库\Lskj.Control\GridControlEx.ExtendedReturn.cs'
|
||||
$text = Get-Content -LiteralPath $path -Raw
|
||||
$required = @(
|
||||
'mExtendedReturnSchemaSqls',
|
||||
'public string SearchField;',
|
||||
'public bool SchemaOnly;',
|
||||
'public DataTable Schema;',
|
||||
'popup.SelectedSearchField',
|
||||
'popup.SetSearchColumns',
|
||||
'request.SearchField'
|
||||
)
|
||||
foreach ($item in $required) {
|
||||
if (-not $text.Contains($item)) { throw "Missing grid integration: $item" }
|
||||
}
|
||||
```
|
||||
|
||||
Expected: failure before implementation.
|
||||
|
||||
- [ ] **Step 2: Make cached schemas source-aware**
|
||||
|
||||
Add:
|
||||
|
||||
```csharp
|
||||
private readonly Dictionary<string, string> mExtendedReturnSchemaSqls =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
```
|
||||
|
||||
`GetExtendedReturnSchema` and a new `TryGetExtendedReturnSchema` must return a cached table only when both field name and resolved `sourceSql` match. Store the SQL beside each cached table and clear both dictionaries in `ResetExtendedReturnState()`.
|
||||
|
||||
- [ ] **Step 3: Extend grid request/result snapshots**
|
||||
|
||||
Add `SearchField` and `SchemaOnly` to `ExtendedLookupRequest`, plus `Schema` to `ExtendedLookupQueryResult`. Preserve the existing version, pending-request, and single-worker-per-field rules.
|
||||
|
||||
- [ ] **Step 4: Configure or asynchronously load schema on popup open**
|
||||
|
||||
In `ExtendedReturnSearch_QueryPopUp`, resolve source SQL after the business row is known. Configure selector options immediately from a matching cache. If the actual business value is empty and no schema is cached, enqueue a schema-only request; if it is non-empty, enqueue the normal initial search with an empty search field so it searches all visible columns without changing the preserved selector.
|
||||
|
||||
Use the existing caption method when building options:
|
||||
|
||||
```csharp
|
||||
private void ConfigureExtendedReturnSearchColumns(
|
||||
ExtendedReturnPopupContext context,
|
||||
DataTable sourceSchema)
|
||||
{
|
||||
List<KeyValuePair<string, string>> options = new List<KeyValuePair<string, string>>();
|
||||
IList<ExtendedReturnFieldMapping> mappings =
|
||||
ExtendedReturnSupport.ParseResultFields(context.Model.ResultFields);
|
||||
foreach (DataColumn column in sourceSchema.Columns)
|
||||
{
|
||||
if (!ExtendedReturnSupport.IsSearchableColumn(column.ColumnName)) continue;
|
||||
options.Add(new KeyValuePair<string, string>(
|
||||
column.ColumnName,
|
||||
GetExtendedReturnColumnCaption(column.ColumnName, mappings)));
|
||||
}
|
||||
context.Popup.SetSearchColumns(options);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Snapshot selector state for explicit searches**
|
||||
|
||||
`ExtendedReturnSearch_SearchRequested` must pass `popup.SelectedSearchField`. `StartExtendedReturnSearch` must clear the popup’s filters/results, reject an empty keyword, validate business configuration, resolve SQL, and enqueue a non-schema-only request containing the selected field.
|
||||
|
||||
- [ ] **Step 6: Make the worker result schema-aware without weakening stale-result guards**
|
||||
|
||||
`QueryExtendedReturn` must always populate `result.Schema`; it calls the four-argument SQL builder only for non-schema-only requests. In `BeginInvokeExtendedReturnResult`:
|
||||
|
||||
1. Keep every existing editor, focused column, business row, and request-version guard.
|
||||
2. Configure selector options from `result.Schema`.
|
||||
3. For schema-only requests, stop without setting `DeliveredVersion` or binding a result table.
|
||||
4. For row requests, set `DeliveredVersion`, bind the result table, configure result columns, and retain existing popup visibility behavior.
|
||||
|
||||
- [ ] **Step 7: Run assertions and compile through `Lskj.Control`**
|
||||
|
||||
Run the Step 1 check and the isolated four-project compile chain. Expected: all exits `0`.
|
||||
|
||||
- [ ] **Step 8: Commit Task 4**
|
||||
|
||||
```bat
|
||||
git add -- "插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs"
|
||||
git diff --cached --check
|
||||
git commit -m "feat(control): add grid extended lookup column search"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Final compatibility verification and delivery
|
||||
|
||||
**Files:**
|
||||
- Verify: `插件库/Lskj.Control/Model/ExtendedReturnSupport.cs`
|
||||
- Verify: `插件库/Lskj.Control/AutoGridLookUp/ExtendedReturnSearchPopup.cs`
|
||||
- Verify: `插件库/Lskj.Control/AutoGridLookUp/LabelExtendedReturnSearchEdit.cs`
|
||||
- Verify: `插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs`
|
||||
- Verify: `docs/superpowers/specs/2026-07-31-extended-return-search-column-filter-design.md`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: all contracts created in Tasks 1-4.
|
||||
- Produces: a clean, compiled local `main` ready for user runtime verification and later push authorization.
|
||||
|
||||
- [ ] **Step 1: Re-run the SQL contract harness from a clean temporary file**
|
||||
|
||||
Recreate the exact Task 1 harness, compile it with the final support source, run it, and confirm exit `0`. Delete the temporary `.cs` and `.exe` afterward.
|
||||
|
||||
- [ ] **Step 2: Run final structural behavior checks**
|
||||
|
||||
Assert all of the following exact invariants with PowerShell `Get-Content -Raw` checks:
|
||||
|
||||
- Popup layout has four columns and `ShowAutoFilterRow = true`.
|
||||
- `PrepareForOpen()` does not reset `selectedSearchField`.
|
||||
- Both explicit search handlers read `SelectedSearchField` and call `ClearResultAndFilter()`.
|
||||
- Both query paths call the four-argument SQL builder.
|
||||
- Both schema delivery paths call `SetSearchColumns`.
|
||||
- Grid reset clears both schema dictionaries.
|
||||
- Existing `ClearRequested`, `ResultSelected`, row indicator, arrow-key, and Enter-key event hookups remain present.
|
||||
|
||||
- [ ] **Step 3: Compile the final dependency chain in isolation**
|
||||
|
||||
Use `E:\lserp_project\codex_compile_project.ps1` with a fresh `DebugCodexExtendedFilter` directory. Compile in this exact order:
|
||||
|
||||
1. `插件库\Lskj.Model\Lskj.Model.csproj`
|
||||
2. `插件库\Lskj.Util\Lskj.Util.csproj`
|
||||
3. `插件库\Lskj.Business\Lskj.Business.csproj`
|
||||
4. `插件库\Lskj.Control\Lskj.Control.csproj`
|
||||
|
||||
Expected: each compiler exit is `0`; the known `CS2023 /noconfig` warning is acceptable.
|
||||
|
||||
- [ ] **Step 4: Inspect repository scope**
|
||||
|
||||
```bat
|
||||
git diff --check origin/main...main
|
||||
git status --short
|
||||
git log --oneline --decorate -8
|
||||
```
|
||||
|
||||
Expected: no whitespace errors, no temporary artifacts, and only the approved design/plan documents plus four production source files differ from the pre-feature base.
|
||||
|
||||
- [ ] **Step 5: Hand off runtime verification scenarios**
|
||||
|
||||
Report the ten scenarios in the design spec, emphasizing:
|
||||
|
||||
- selector captions and hidden-column exclusion;
|
||||
- all-column versus single-column database behavior;
|
||||
- selector persistence and fallback;
|
||||
- auto-filter row local-only behavior and reset on new search;
|
||||
- mouse/keyboard selection, clear, display translation, and ResultFields mapping for MyControl/grid with both 173 and 174.
|
||||
|
||||
Do not push until the user explicitly requests it.
|
||||
Reference in New Issue
Block a user