26ff8d9b4f
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
1374 lines
50 KiB
Markdown
1374 lines
50 KiB
Markdown
# Database Schema Specification
|
|
|
|
## Purpose
|
|
|
|
The database schema defines the SQL Server cache database used by the JDE Scoping Tool application. This database serves as an intermediate cache layer between the enterprise source systems (JDE Oracle and CMS Sybase) and the application's search functionality. The schema is designed for compatibility with .NET 10 and supports both Dapper micro-ORM and EF Core data access patterns. The schema includes:
|
|
|
|
1. **Application tables** - Search request management and data update tracking
|
|
2. **Cache tables** - Local copies of JDE/CMS data split into current and historical partitions
|
|
3. **Reference tables** - Lookup data for items, users, work centers, etc.
|
|
4. **Views** - Union views combining current/historical data and computed aggregations
|
|
5. **Stored procedures** - Search lifecycle management (atomic operations)
|
|
6. **User-defined types** - Table-valued parameters for bulk filtering
|
|
7. **Functions** - MIS data matching logic
|
|
|
|
## Source Reference
|
|
|
|
| Legacy Files | Purpose |
|
|
|--------------|---------|
|
|
| OLD/Database/Tables/Search.sql | User search request tracking |
|
|
| OLD/Database/Tables/DataUpdate.sql | Cache refresh tracking |
|
|
| OLD/Database/Tables/WorkOrder_Curr.sql | Current work orders |
|
|
| OLD/Database/Tables/WorkOrder_Hist.sql | Historical work orders |
|
|
| OLD/Database/Tables/WorkOrderStep_Curr.sql | Current work order steps |
|
|
| OLD/Database/Tables/WorkOrderStep_Hist.sql | Historical work order steps |
|
|
| OLD/Database/Tables/WorkOrderTime_Curr.sql | Current work order time transactions |
|
|
| OLD/Database/Tables/WorkOrderTime_Hist.sql | Historical work order time transactions |
|
|
| OLD/Database/Tables/WorkOrderComponent_Curr.sql | Current work order components |
|
|
| OLD/Database/Tables/WorkOrderComponent_Hist.sql | Historical work order components |
|
|
| OLD/Database/Tables/WorkOrderRouting_Curr.sql | Work order routing transactions |
|
|
| OLD/Database/Tables/LotUsage_Curr.sql | Current lot usage (cardex) |
|
|
| OLD/Database/Tables/LotUsage_Hist.sql | Historical lot usage |
|
|
| OLD/Database/Tables/Lot.sql | Lot master data |
|
|
| OLD/Database/Tables/LotLocation.sql | Lot location tracking |
|
|
| OLD/Database/Tables/Item.sql | Item master data |
|
|
| OLD/Database/Tables/WorkCenter.sql | Work center reference |
|
|
| OLD/Database/Tables/ProfitCenter.sql | Profit center reference |
|
|
| OLD/Database/Tables/Branch.sql | Branch reference |
|
|
| OLD/Database/Tables/JdeUser.sql | JDE user (operator) reference |
|
|
| OLD/Database/Tables/StatusCode.sql | Work order status codes |
|
|
| OLD/Database/Tables/FunctionCode.sql | Function code reference |
|
|
| OLD/Database/Tables/OrgHierarchy.sql | Organization hierarchy mapping |
|
|
| OLD/Database/Tables/RouteMaster.sql | Item routing master |
|
|
| OLD/Database/Tables/MisData.sql | CMS MIS data cache |
|
|
| OLD/Database/Views/WorkOrder.sql | Union of current/historical work orders |
|
|
| OLD/Database/Views/WorkOrderStep.sql | Union of current/historical steps with function descriptions |
|
|
| OLD/Database/Views/WorkOrderTime.sql | Union of current/historical time transactions |
|
|
| OLD/Database/Views/WorkOrderComponent.sql | Union of current/historical components |
|
|
| OLD/Database/Views/LotUsage.sql | Union of current/historical lot usage |
|
|
| OLD/Database/Views/LastDataUpdates.sql | Most recent successful data updates by table |
|
|
| OLD/Database/Views/WorkOrderTotalScrap.sql | Aggregated scrap quantities by work order |
|
|
| OLD/Database/StoredProcedures/SubmitSearch.sql | Insert new search request |
|
|
| OLD/Database/StoredProcedures/StartSearch.sql | Mark search as started |
|
|
| OLD/Database/StoredProcedures/CompleteSearch.sql | Mark search as completed |
|
|
| OLD/Database/StoredProcedures/ResetPartialSearches.sql | Reset incomplete searches on startup |
|
|
| OLD/Database/Functions/MatchMis.sql | MIS data matching function |
|
|
| OLD/Database/Types/*.sql | Table-valued parameter types for filtering |
|
|
| OLD/Database/Setup.sql | Complete database setup script |
|
|
|
|
---
|
|
## Requirements
|
|
### Requirement: Search table
|
|
|
|
The system SHALL store user search requests, criteria, status, and results.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| ID | INT IDENTITY(1,1) | NOT NULL | Primary key, auto-increment |
|
|
| UserName | VARCHAR(128) | NOT NULL | Username of search creator |
|
|
| Name | VARCHAR(128) | NULL | User-friendly search name |
|
|
| Status | SMALLINT | NOT NULL | Status code (0=New, 1=Submitted, 2=Started, 3=Ended, 4=Error) |
|
|
| SubmitDT | DATETIME | NULL | Timestamp when search was submitted |
|
|
| StartDT | DATETIME | NULL | Timestamp when processing started |
|
|
| EndDT | DATETIME | NULL | Timestamp when processing completed |
|
|
| Criteria | VARCHAR(MAX) | NULL | JSON-serialized search criteria |
|
|
| Results | VARBINARY(MAX) | NULL | Excel file binary output |
|
|
|
|
#### Primary Key
|
|
|
|
- `ID` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_Search_UserName` on `[UserName]`
|
|
|
|
#### Business Rules
|
|
|
|
- Status values: 0=New, 1=Submitted, 2=Started, 3=Ended, 4=Error
|
|
- Results is populated only when Status = 3 (Ended)
|
|
- Criteria stores JSON-serialized SearchCriteria object
|
|
|
|
#### Scenario: Insert new search request
|
|
- **WHEN** a user "jdoe" submits a search named "Q4 Report" and the SubmitSearch stored procedure is called
|
|
- **THEN** a new row is inserted with Status = 1 (Submitted), SubmitDT is set to current timestamp, and ID is auto-generated
|
|
|
|
#### Scenario: Search completes successfully
|
|
- **WHEN** a search with ID = 123 and Status = 2 (Started) exists and CompleteSearch is called with WasSuccessful = 1
|
|
- **THEN** Status is updated to 3 (Ended), Results contains the Excel binary data, and EndDT is set to current timestamp
|
|
|
|
#### Scenario: Query searches by user
|
|
- **WHEN** multiple searches exist for different users and querying with UserName = "jdoe"
|
|
- **THEN** IX_Search_UserName index is used and only searches for that user are returned
|
|
|
|
---
|
|
|
|
### Requirement: DataUpdate table
|
|
|
|
The system SHALL track cache data refresh operations for monitoring and incremental updates.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| ID | INT IDENTITY(1,1) | NOT NULL | Primary key, auto-increment |
|
|
| SourceSystem | VARCHAR(50) | NOT NULL | Source system name (JDE, CMS) |
|
|
| SourceData | VARCHAR(50) | NOT NULL | Source data type identifier |
|
|
| TableName | VARCHAR(50) | NOT NULL | Target cache table name |
|
|
| StartDT | DATETIME | NOT NULL | Update start timestamp |
|
|
| EndDT | DATETIME | NOT NULL | Update end timestamp |
|
|
| UpdateType | SMALLINT | NOT NULL | Update type (1=Hourly, 2=Daily, 3=Mass) |
|
|
| WasSuccessful | BIT | NOT NULL | Success indicator |
|
|
| NumberRecords | BIGINT | NOT NULL | Number of records processed |
|
|
|
|
#### Primary Key
|
|
|
|
- `ID` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_DataUpdate_LastUpdate` on `[TableName], [WasSuccessful], [StartDT DESC]`
|
|
|
|
#### Business Rules
|
|
|
|
- UpdateType enum: 1=Hourly, 2=Daily, 3=Mass
|
|
- Used to determine incremental update windows
|
|
|
|
#### Scenario: Record successful data refresh
|
|
- **WHEN** a daily refresh of WorkOrder table completes and 5000 records are processed successfully
|
|
- **THEN** a row is inserted with UpdateType = 2, WasSuccessful = 1, NumberRecords = 5000
|
|
|
|
#### Scenario: Query last successful update
|
|
- **WHEN** multiple updates exist for WorkOrder table and querying for incremental sync window
|
|
- **THEN** IX_DataUpdate_LastUpdate returns most recent successful update by StartDT DESC
|
|
|
|
---
|
|
|
|
### Requirement: WorkOrder_Curr / WorkOrder_Hist tables
|
|
|
|
The system SHALL store JDE work order data, partitioned into current (active) and historical tables.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| WorkOrderNumber | BIGINT | NOT NULL | Primary key, JDE work order number |
|
|
| BranchCode | VARCHAR(12) | NULL | Branch/plant code |
|
|
| LotNumber | VARCHAR(30) | NULL | Assigned lot number |
|
|
| ItemNumber | VARCHAR(25) | NULL | Product item number |
|
|
| ShortItemNumber | BIGINT | NOT NULL | Numeric item identifier |
|
|
| ParentWorkOrderNumber | VARCHAR(8) | NULL | Parent work order (sub-assembly) |
|
|
| OrderQuantity | DECIMAL(15,2) | NOT NULL | Quantity ordered |
|
|
| HeldQuantity | DECIMAL(15,2) | NOT NULL | Quantity on hold |
|
|
| ShippedQuantity | DECIMAL(15,2) | NOT NULL | Quantity shipped |
|
|
| StatusCode | VARCHAR(10) | NULL | Work order status code |
|
|
| StatusCodeUpdateDT | DATETIME | NULL | Last status update timestamp |
|
|
| IssueDate | DATETIME | NOT NULL | Date work order was issued |
|
|
| StartDate | DATETIME | NOT NULL | Date work order was started |
|
|
| RoutingType | VARCHAR(3) | NULL | Routing type code |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `WorkOrderNumber` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_WorkOrder_Curr_ParentWorkOrderNumber` on `[ParentWorkOrderNumber]`
|
|
- `IX_WorkOrder_Curr_ItemNumber` on `[ItemNumber]`
|
|
- (Same indexes on _Hist table)
|
|
|
|
#### Business Rules
|
|
|
|
- Data partitioned between _Curr and _Hist tables based on work order status
|
|
- Accessed via WorkOrder view which unions both tables
|
|
|
|
#### Scenario: Insert work order to current table
|
|
- **WHEN** a new active work order 12345 from JDE is received and the sync process inserts the record
|
|
- **THEN** it is stored in WorkOrder_Curr table and can be queried via WorkOrder view
|
|
|
|
#### Scenario: Query work orders by item
|
|
- **WHEN** work orders exist for item "ABC123" and querying by ItemNumber
|
|
- **THEN** IX_WorkOrder_Curr_ItemNumber index is used for efficient lookup
|
|
|
|
#### Scenario: Query sub-assembly work orders
|
|
- **WHEN** a parent work order has child work orders and querying by ParentWorkOrderNumber
|
|
- **THEN** IX_WorkOrder_Curr_ParentWorkOrderNumber enables parent-child traversal
|
|
|
|
---
|
|
|
|
### Requirement: WorkOrderStep_Curr / WorkOrderStep_Hist tables
|
|
|
|
The system SHALL store JDE work order operation steps.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| WorkOrderNumber | BIGINT | NOT NULL | Work order identifier |
|
|
| WorkCenterCode | VARCHAR(12) | NOT NULL | Work center code |
|
|
| StepNumber | DECIMAL(7,2) | NOT NULL | Operation sequence number |
|
|
| StepTypeCode | VARCHAR(2) | NOT NULL | Operation type code |
|
|
| BranchCode | VARCHAR(12) | NOT NULL | Branch code |
|
|
| StepDescription | VARCHAR(30) | NULL | Step description |
|
|
| StartDT | DATETIME | NULL | Step start timestamp |
|
|
| EndDT | DATETIME | NULL | Step end timestamp |
|
|
| FunctionCode | VARCHAR(15) | NULL | Function code |
|
|
| ScrappedQuantity | DECIMAL(18,2) | NOT NULL | Quantity scrapped |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `WorkOrderNumber, WorkCenterCode, StepNumber, StepTypeCode` (clustered composite)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_WorkOrderStep_Curr_WorkCenterCode` on `[WorkCenterCode]` INCLUDE `[WorkOrderNumber]`
|
|
|
|
#### Business Rules
|
|
|
|
- StepNumber is DECIMAL(7,2) to support sub-steps (e.g., 10.01, 10.02)
|
|
- ScrappedQuantity is NOT NULL with default 0
|
|
|
|
---
|
|
|
|
### Requirement: WorkOrderTime_Curr / WorkOrderTime_Hist tables
|
|
|
|
The system SHALL store F31122 work order time transaction records (operator labor).
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| UniqueID | BIGINT | NOT NULL | Primary key |
|
|
| WorkOrderNumber | BIGINT | NOT NULL | Work order identifier |
|
|
| StepNumber | DECIMAL(7,2) | NOT NULL | Operation sequence |
|
|
| WorkCenterCode | VARCHAR(12) | NOT NULL | Work center code |
|
|
| BranchCode | VARCHAR(12) | NOT NULL | Branch code |
|
|
| AddressNumber | BIGINT | NOT NULL | Operator address number |
|
|
| GlDate | DATETIME | NULL | G/L processing date |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `UniqueID` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_WorkOrderTime_Curr_Lookup` on `[WorkOrderNumber, WorkCenterCode, StepNumber]`
|
|
- `IX_WorkOrderTime_Curr_AddressNumber` on `[AddressNumber]` INCLUDE `[WorkOrderNumber, WorkCenterCode, StepNumber, LastUpdateDT]`
|
|
|
|
#### Business Rules
|
|
|
|
- Links operators to work order operations via AddressNumber -> JdeUser
|
|
|
|
---
|
|
|
|
### Requirement: WorkOrderComponent_Curr / WorkOrderComponent_Hist tables
|
|
|
|
The system SHALL store work order component usage (materials consumed).
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| UniqueID | BIGINT | NOT NULL | Primary key |
|
|
| WorkOrderNumber | BIGINT | NOT NULL | Work order identifier |
|
|
| LotNumber | VARCHAR(30) | NOT NULL | Component lot number |
|
|
| BranchCode | VARCHAR(12) | NULL | Branch code |
|
|
| ShortItemNumber | BIGINT | NOT NULL | Component item number |
|
|
| Quantity | DECIMAL(15,2) | NOT NULL | Transaction quantity |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `UniqueID` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_WorkOrderComponent_Curr_WorkOrderNumber` on `[WorkOrderNumber]`
|
|
- `IX_WorkOrderComponent_Curr_LotLookup` on `[LotNumber, ShortItemNumber, BranchCode]`
|
|
|
|
---
|
|
|
|
### Requirement: LotUsage_Curr / LotUsage_Hist tables
|
|
|
|
The system SHALL store cardex entries recording component lot consumption in work orders.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| UniqueID | BIGINT | NOT NULL | Primary key |
|
|
| WorkOrderNumber | BIGINT | NOT NULL | Associated work order |
|
|
| LotNumber | VARCHAR(30) | NOT NULL | Component lot number |
|
|
| BranchCode | VARCHAR(12) | NULL | Branch code |
|
|
| ShortItemNumber | BIGINT | NOT NULL | Component item number |
|
|
| Quantity | DECIMAL(15,2) | NOT NULL | Transaction quantity |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `UniqueID` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_LotUsage_Curr_WorkOrderNumber` on `[WorkOrderNumber]`
|
|
- `IX_LotUsage_Curr_LotLookup` on `[LotNumber, ShortItemNumber, BranchCode]`
|
|
|
|
#### Business Rules
|
|
|
|
- Structure identical to WorkOrderComponent tables
|
|
- Represents different JDE transaction type (cardex vs component list)
|
|
|
|
---
|
|
|
|
### Requirement: WorkOrderRouting table
|
|
|
|
The system SHALL store work order routing transaction records (F3112Z1).
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| UserID | VARCHAR(40) | NOT NULL | Transaction user ID |
|
|
| BatchNumber | VARCHAR(60) | NOT NULL | Transaction batch number |
|
|
| TransactionNumber | VARCHAR(88) | NOT NULL | Transaction number |
|
|
| LineNumber | INT | NOT NULL | Transaction line number |
|
|
| StepNumber | DECIMAL(7,2) | NOT NULL | Operation sequence |
|
|
| WorkCenterCode | VARCHAR(12) | NOT NULL | Work center code |
|
|
| WorkOrderNumber | BIGINT | NOT NULL | Work order identifier |
|
|
| RoutingType | VARCHAR(12) | NULL | Routing type |
|
|
| BranchCode | VARCHAR(12) | NULL | Branch code |
|
|
| StepDescription | VARCHAR(120) | NULL | Step description |
|
|
| FunctionCode | VARCHAR(60) | NULL | Function code |
|
|
| TransactionDate | DATETIME | NOT NULL | Transaction original date |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `UserID, BatchNumber, TransactionNumber, LineNumber, StepNumber, WorkCenterCode` (clustered composite)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_WorkOrderRouting_Lookup` on `[WorkOrderNumber, WorkCenterCode, FunctionCode]` INCLUDE `[StepNumber]`
|
|
|
|
#### Business Rules
|
|
|
|
- No current/historical split (single table)
|
|
- Used for MIS matching to determine original operation sequence
|
|
|
|
---
|
|
|
|
### Requirement: Lot table
|
|
|
|
The system SHALL store JDE lot master data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| LotNumber | VARCHAR(30) | NOT NULL | Lot identifier |
|
|
| BranchCode | VARCHAR(12) | NOT NULL | Business unit code |
|
|
| ShortItemNumber | BIGINT | NOT NULL | Numeric item identifier |
|
|
| ItemNumber | VARCHAR(25) | NULL | Item number |
|
|
| SupplierCode | BIGINT | NOT NULL | Supplier address number |
|
|
| StatusCode | CHAR(1) | NULL | Single-character lot status |
|
|
| Memo1 | VARCHAR(30) | NULL | Memo line 1 |
|
|
| Memo2 | VARCHAR(30) | NULL | Memo line 2 |
|
|
| Memo3 | VARCHAR(30) | NULL | Memo line 3 |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `LotNumber, ShortItemNumber, BranchCode` (clustered composite)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_Lot_SupplierCode` on `[SupplierCode]` INCLUDE `[LotNumber]`
|
|
|
|
#### Business Rules
|
|
|
|
- Composite key because same lot number can exist for different items/branches
|
|
- StatusCode is single character (CHAR(1))
|
|
|
|
---
|
|
|
|
### Requirement: LotLocation table
|
|
|
|
The system SHALL store JDE lot location tracking data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| LotNumber | VARCHAR(120) | NOT NULL | Lot identifier |
|
|
| ShortItemNumber | BIGINT | NOT NULL | Item identifier |
|
|
| BranchCode | VARCHAR(12) | NOT NULL | Business unit code |
|
|
| Location | VARCHAR(80) | NOT NULL | Physical location code |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `LotNumber, ShortItemNumber, BranchCode, Location` (clustered composite)
|
|
|
|
#### Business Rules
|
|
|
|
- Location defaults to empty string
|
|
- LotNumber size (120) is larger than Lot table (30) to accommodate JDE location-specific lot identifiers
|
|
|
|
---
|
|
|
|
### Requirement: Item table
|
|
|
|
The system SHALL store JDE item (part type) master data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| ShortItemNumber | BIGINT | NOT NULL | Primary key, numeric identifier |
|
|
| ItemNumber | VARCHAR(25) | NOT NULL | Alphanumeric item number |
|
|
| Description | VARCHAR(30) | NULL | Item description |
|
|
| PlanningFamily | VARCHAR(3) | NULL | Master planning family |
|
|
| StockingType | CHAR(1) | NULL | Stocking type code |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `ShortItemNumber` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_Item_ItemNumber` on `[ItemNumber]`
|
|
|
|
#### Business Rules
|
|
|
|
- ShortItemNumber is the JDE internal key
|
|
- ItemNumber is the human-readable identifier
|
|
|
|
---
|
|
|
|
### Requirement: WorkCenter table
|
|
|
|
The system SHALL store JDE work center reference data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| Code | VARCHAR(12) | NOT NULL | Primary key, work center code |
|
|
| Description | VARCHAR(40) | NULL | Work center description |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `Code` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_WorkCenter_Lookup` on `[Description]`
|
|
|
|
---
|
|
|
|
### Requirement: ProfitCenter table
|
|
|
|
The system SHALL store JDE profit center reference data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| Code | VARCHAR(12) | NOT NULL | Primary key, profit center code |
|
|
| Description | VARCHAR(40) | NULL | Profit center description |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `Code` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_ProfitCenter_Lookup` on `[Description]`
|
|
|
|
---
|
|
|
|
### Requirement: Branch table
|
|
|
|
The system SHALL store JDE branch reference data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| Code | VARCHAR(12) | NOT NULL | Primary key, branch code |
|
|
| Description | VARCHAR(40) | NULL | Branch description |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `Code` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_Branch_Lookup` on `[Description]`
|
|
|
|
---
|
|
|
|
### Requirement: JdeUser table
|
|
|
|
The system SHALL store JDE user (operator) reference data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| AddressNumber | BIGINT | NOT NULL | Primary key, JDE address number |
|
|
| UserID | VARCHAR(10) | NULL | Login identifier |
|
|
| FullName | VARCHAR(40) | NOT NULL | Full name |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `AddressNumber` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_JdeUser_UserID` on `[UserID]`
|
|
- `IX_JdeUser_FullName` on `[FullName]`
|
|
|
|
---
|
|
|
|
### Requirement: StatusCode table
|
|
|
|
The system SHALL store JDE work order status code lookup data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| Code | VARCHAR(12) | NOT NULL | Primary key, status code |
|
|
| Description | VARCHAR(40) | NULL | Status description |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `Code` (clustered)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_StatusCode_Lookup` on `[Description]`
|
|
|
|
---
|
|
|
|
### Requirement: FunctionCode table
|
|
|
|
The system SHALL store JDE function code lookup data.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| Code | VARCHAR(10) | NOT NULL | Primary key, function code |
|
|
| Description | VARCHAR(MAX) | NULL | Function description |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `Code` (clustered)
|
|
|
|
#### Business Rules
|
|
|
|
- Description is VARCHAR(MAX) (unbounded length) - consider constraining to VARCHAR(2000) in future migration
|
|
|
|
---
|
|
|
|
### Requirement: OrgHierarchy table
|
|
|
|
The system SHALL store organization hierarchy mapping (work center to profit center).
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| WorkCenterCode | VARCHAR(12) | NOT NULL | Work center code |
|
|
| BranchCode | VARCHAR(12) | NOT NULL | Branch code |
|
|
| ProfitCenterCode | VARCHAR(12) | NOT NULL | Profit center code |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `WorkCenterCode, BranchCode` (clustered composite)
|
|
|
|
#### Indexes
|
|
|
|
- `IX_OrgHierarchy_ProfitCenterCode` on `[ProfitCenterCode]` INCLUDE `[WorkCenterCode]`
|
|
|
|
#### Business Rules
|
|
|
|
- Maps work centers to profit centers for filtering
|
|
- Same work center can belong to different profit centers by branch
|
|
|
|
---
|
|
|
|
### Requirement: RouteMaster table
|
|
|
|
The system SHALL store JDE item routing master data (F3003).
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| BranchCode | VARCHAR(12) | NOT NULL | Branch code |
|
|
| ItemNumber | VARCHAR(25) | NOT NULL | Item number |
|
|
| RoutingType | VARCHAR(3) | NOT NULL | Router type |
|
|
| SequenceNumber | DECIMAL(7,2) | NOT NULL | Job step number |
|
|
| FunctionCode | VARCHAR(15) | NULL | Function code |
|
|
| WorkCenterCode | VARCHAR(12) | NULL | Work center code |
|
|
| StartDate | DATETIME | NOT NULL | Effectivity start date |
|
|
| EndDate | DATETIME | NULL | Effectivity end date |
|
|
| LastUpdateDT | DATETIME | NOT NULL | Last update timestamp |
|
|
|
|
#### Primary Key
|
|
|
|
- `BranchCode, ItemNumber, RoutingType, SequenceNumber, StartDate` (clustered composite)
|
|
|
|
#### Business Rules
|
|
|
|
- Used for MIS matching when routing transaction data not available
|
|
- EndDate is nullable (open-ended effectivity)
|
|
|
|
---
|
|
|
|
### Requirement: MisData table
|
|
|
|
The system SHALL store CMS MIS (Manufacturing Information System) data from Sybase.
|
|
|
|
#### Columns
|
|
|
|
| Column | Type | Nullable | Description |
|
|
|--------|------|----------|-------------|
|
|
| ItemNumber | VARCHAR(32) | NOT NULL | Item number |
|
|
| BranchCode | VARCHAR(32) | NOT NULL | Branch code |
|
|
| SequenceNumber | VARCHAR(32) | NOT NULL | Operation step number |
|
|
| MisNumber | VARCHAR(32) | NOT NULL | MIS unique number |
|
|
| RevID | VARCHAR(32) | NOT NULL | MIS revision ID |
|
|
| CharNumber | VARCHAR(32) | NOT NULL | Characteristic number |
|
|
| TestDescription | VARCHAR(2000) | NULL | Test description |
|
|
| SamplingType | VARCHAR(32) | NULL | Type of sampling |
|
|
| SamplingValue | VARCHAR(32) | NULL | Sampling selection value |
|
|
| ToolsGauges | VARCHAR(2000) | NULL | Tools and gauges |
|
|
| WorkInstructions | VARCHAR(2000) | NULL | Work instructions |
|
|
| Status | VARCHAR(32) | NOT NULL | Release status (Current, BackLevel) |
|
|
| ReleaseDate | DATETIME | NULL | Release date |
|
|
| ObsoleteDate | DATETIME | NULL | Obsolete date |
|
|
|
|
#### Primary Key
|
|
|
|
- `ItemNumber, BranchCode, SequenceNumber, MisNumber, RevID, Status, CharNumber` (clustered composite - 7 columns)
|
|
|
|
#### Business Rules
|
|
|
|
- Sourced from CMS (Sybase), not JDE
|
|
- Status values: 'Current', 'BackLevel'
|
|
- No LastUpdateDT column (full refresh on each sync)
|
|
|
|
---
|
|
|
|
### Requirement: DbUp migration scripts
|
|
|
|
The system SHALL use DbUp migration scripts to create and maintain the database schema.
|
|
|
|
#### Migration Script Organization
|
|
|
|
- Scripts SHALL be numbered sequentially (NNN_Description.sql)
|
|
- Scripts SHALL be embedded as resources in JdeScoping.Database assembly
|
|
- Scripts SHALL execute in dependency order (tables → views → types → procedures)
|
|
|
|
#### Script Numbering Ranges
|
|
|
|
| Range | Category |
|
|
|-------|----------|
|
|
| 001-025 | Tables |
|
|
| 026-032 | Views |
|
|
| 033-039 | Table-valued parameter types |
|
|
| 040-043 | Stored procedures |
|
|
| 044+ | Functions |
|
|
|
|
#### Scenario: Fresh database deployment
|
|
|
|
- **WHEN** the application starts against an empty database
|
|
- **THEN** DbUp creates all 44 database objects in dependency order
|
|
- **AND** the SchemaVersions table records each applied migration
|
|
|
|
#### Scenario: Incremental migration
|
|
|
|
- **WHEN** the application starts against a database with some migrations applied
|
|
- **THEN** DbUp applies only new migrations not in SchemaVersions
|
|
- **AND** existing data is preserved
|
|
|
|
### Requirement: Migration idempotency
|
|
|
|
The system SHALL ensure migration scripts are idempotent for safe re-execution.
|
|
|
|
#### Idempotency Patterns
|
|
|
|
- Tables: Use `IF NOT EXISTS` checks
|
|
- Views: Use `CREATE OR ALTER VIEW`
|
|
- Types: Check sys.types before creation
|
|
- Procedures: Use `CREATE OR ALTER PROCEDURE`
|
|
- Functions: Use `CREATE OR ALTER FUNCTION`
|
|
|
|
#### Scenario: Re-run migration on existing database
|
|
|
|
- **WHEN** a migration script runs against a database where the object already exists
|
|
- **THEN** the script completes without error
|
|
- **AND** the object definition matches the script
|
|
|
|
## Views
|
|
|
|
### Requirement: WorkOrder view
|
|
|
|
The system SHALL provide a union of WorkOrder_Curr and WorkOrder_Hist tables.
|
|
|
|
```sql
|
|
CREATE VIEW dbo.WorkOrder AS
|
|
(
|
|
SELECT hist.* FROM dbo.WorkOrder_Hist hist
|
|
UNION ALL
|
|
SELECT curr.* FROM dbo.WorkOrder_Curr curr
|
|
)
|
|
```
|
|
|
|
#### Scenario: Query all work orders transparently
|
|
- **WHEN** work orders exist in both _Curr and _Hist tables and querying the WorkOrder view
|
|
- **THEN** results include records from both tables seamlessly
|
|
|
|
### Requirement: WorkOrderStep view
|
|
|
|
The system SHALL provide a union of WorkOrderStep_Curr and WorkOrderStep_Hist with FunctionCode description join.
|
|
|
|
```sql
|
|
CREATE VIEW [dbo].[WorkOrderStep] AS
|
|
SELECT wos.WorkOrderNumber, wos.WorkCenterCode, wos.StepNumber, wos.StepTypeCode,
|
|
wos.BranchCode, wos.StepDescription, wos.StartDT, wos.EndDT, wos.FunctionCode,
|
|
fc.Description AS FunctionOperationDescription,
|
|
wos.ScrappedQuantity, wos.LastUpdateDT
|
|
FROM (
|
|
SELECT ... FROM dbo.WorkOrderStep_Hist hist
|
|
UNION ALL
|
|
SELECT ... FROM dbo.WorkOrderStep_Curr curr
|
|
) wos LEFT OUTER JOIN dbo.FunctionCode fc ON (wos.FunctionCode = fc.Code)
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Adds FunctionOperationDescription via LEFT OUTER JOIN to FunctionCode
|
|
|
|
#### Scenario: Enrich steps with function descriptions
|
|
- **WHEN** a work order step with FunctionCode = "ASSY" exists and querying WorkOrderStep view
|
|
- **THEN** FunctionOperationDescription is populated from FunctionCode.Description
|
|
|
|
#### Scenario: Handle missing function code
|
|
- **WHEN** a work order step with NULL FunctionCode exists and querying WorkOrderStep view
|
|
- **THEN** FunctionOperationDescription is NULL (LEFT OUTER JOIN)
|
|
|
|
### Requirement: WorkOrderTime view
|
|
|
|
The system SHALL provide a union of WorkOrderTime_Curr and WorkOrderTime_Hist tables.
|
|
|
|
### Requirement: WorkOrderComponent view
|
|
|
|
The system SHALL provide a union of WorkOrderComponent_Curr and WorkOrderComponent_Hist tables.
|
|
|
|
### Requirement: LotUsage view
|
|
|
|
The system SHALL provide a union of LotUsage_Curr and LotUsage_Hist tables.
|
|
|
|
### Requirement: LastDataUpdates view
|
|
|
|
The system SHALL provide a pivot table showing most recent successful update timestamps by table and update type.
|
|
|
|
```sql
|
|
CREATE VIEW [dbo].[LastDataUpdates] AS
|
|
WITH UPDATE_CTE AS (
|
|
SELECT du.TableName, du.UpdateType, du.StartDT,
|
|
ROW_NUMBER() OVER (PARTITION BY du.TableName, du.UpdateType ORDER BY du.StartDT DESC) AS RN
|
|
FROM dbo.DataUpdate du
|
|
WHERE du.WasSuccessful = 1
|
|
)
|
|
SELECT TableName,
|
|
COALESCE([3], '1970-01-01') AS MassUpdateDT,
|
|
COALESCE(COALESCE([2], [3]), '1970-01-01') AS DailyUpdateDT,
|
|
COALESCE(COALESCE(COALESCE([1], [2]), [3]), '1970-01-01') AS HourlyUpdateDT
|
|
FROM (SELECT ... FROM UPDATE_CTE WHERE RN = 1) AS Source
|
|
PIVOT (MAX(StartDT) FOR UpdateType IN ([1], [2], [3])) AS PivotTable
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Returns most recent successful update per table for each update type
|
|
- Cascades defaults: Hourly falls back to Daily, Daily falls back to Mass
|
|
- Default date is '1970-01-01' when no updates exist
|
|
|
|
#### Scenario: Get incremental sync window
|
|
- **WHEN** WorkOrder table has Mass update at 2024-01-01 and Daily at 2024-06-15 and querying LastDataUpdates for WorkOrder
|
|
- **THEN** MassUpdateDT = 2024-01-01, DailyUpdateDT = 2024-06-15
|
|
|
|
#### Scenario: Cascade to Mass when Daily missing
|
|
- **WHEN** WorkOrder table has only Mass update at 2024-01-01 and querying LastDataUpdates for WorkOrder
|
|
- **THEN** DailyUpdateDT = 2024-01-01 (cascaded from Mass)
|
|
|
|
#### Scenario: Default when no updates exist
|
|
- **WHEN** a table has no successful updates and querying LastDataUpdates
|
|
- **THEN** all timestamps return '1970-01-01'
|
|
|
|
### Requirement: WorkOrderTotalScrap view
|
|
|
|
The system SHALL aggregate scrap quantities by work order.
|
|
|
|
```sql
|
|
CREATE VIEW [dbo].[WorkOrderTotalScrap] AS
|
|
SELECT wos.WorkOrderNumber,
|
|
COALESCE(SUM(wos.ScrappedQuantity), 0) AS TotalScrappedQuantity
|
|
FROM dbo.WorkOrderStep wos
|
|
WHERE wos.StepNumber = ROUND(wos.StepNumber, 0)
|
|
GROUP BY wos.WorkOrderNumber;
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Only counts whole-number steps (excludes sub-steps like 10.01)
|
|
- Returns 0 if no scrap exists
|
|
|
|
#### Scenario: Sum scrap for whole-number steps only
|
|
- **WHEN** work order 12345 has steps 10, 10.01, 20 with scrap 5, 3, 2 and querying WorkOrderTotalScrap
|
|
- **THEN** TotalScrappedQuantity = 7 (5 + 2, excludes sub-step 10.01)
|
|
|
|
#### Scenario: Return zero when no scrap
|
|
- **WHEN** work order 67890 has no scrapped quantities and querying WorkOrderTotalScrap
|
|
- **THEN** TotalScrappedQuantity = 0
|
|
|
|
---
|
|
|
|
## Stored Procedures
|
|
|
|
### Requirement: SubmitSearch stored procedure
|
|
|
|
The system SHALL insert a new search request with Status = Submitted (1).
|
|
|
|
```sql
|
|
CREATE PROCEDURE [dbo].[SubmitSearch] (
|
|
@p_UserName VARCHAR(128),
|
|
@p_Name VARCHAR(128),
|
|
@p_Criteria VARCHAR(MAX),
|
|
@o_SearchID INT OUTPUT
|
|
)
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Sets Status = 1 (Submitted)
|
|
- Sets SubmitDT = GETDATE()
|
|
- Returns new ID via OUTPUT parameter
|
|
|
|
#### Scenario: Submit new search request
|
|
- **WHEN** user "jdoe" submits criteria for Q4 analysis and SubmitSearch is called with UserName, Name, Criteria
|
|
- **THEN** a new Search row is created with Status = 1 and @o_SearchID OUTPUT contains the new ID
|
|
|
|
#### .NET 10 Implementation Notes
|
|
|
|
**Decision**: Keep as stored procedure. This atomic insert-and-return-ID operation benefits from stored procedure encapsulation. The OUTPUT parameter pattern maps directly to Dapper's `Query<int>` with `@o_SearchID OUTPUT` or EF Core's `FromSqlRaw`.
|
|
|
|
### Requirement: StartSearch stored procedure
|
|
|
|
The system SHALL mark a search as started.
|
|
|
|
```sql
|
|
CREATE PROCEDURE [dbo].[StartSearch] (@p_SearchID INT)
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Sets Status = 2 (Started)
|
|
- Sets StartDT = GETDATE()
|
|
|
|
#### Scenario: Worker picks up queued search
|
|
- **WHEN** search ID 123 with Status = 1 (Submitted) exists and StartSearch(123) is called
|
|
- **THEN** Status = 2 (Started) and StartDT = current time
|
|
|
|
#### .NET 10 Implementation Notes
|
|
|
|
**Decision**: Candidate for C# service layer. Simple single-row UPDATE could be implemented as repository method for better testability. However, keeping as stored procedure is acceptable for consistency with other search lifecycle procedures.
|
|
|
|
### Requirement: CompleteSearch stored procedure
|
|
|
|
The system SHALL mark a search as completed with results.
|
|
|
|
```sql
|
|
CREATE PROCEDURE [dbo].[CompleteSearch] (
|
|
@p_SearchID INT,
|
|
@p_WasSuccessful BIT,
|
|
@p_Results VARBINARY(MAX)
|
|
)
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Sets Status = 3 (Ended) if successful, 4 (Error) if not
|
|
- Sets Results to provided binary data
|
|
- Sets EndDT = GETDATE()
|
|
|
|
#### Scenario: Search completes successfully
|
|
- **WHEN** search ID 123 with Status = 2 (Started) exists and CompleteSearch(123, 1, <excel bytes>) is called
|
|
- **THEN** Status = 3 (Ended), Results = <excel bytes>, EndDT = current time
|
|
|
|
#### Scenario: Search fails with error
|
|
- **WHEN** search ID 456 with Status = 2 (Started) exists and CompleteSearch(456, 0, NULL) is called
|
|
- **THEN** Status = 4 (Error), Results = NULL, EndDT = current time
|
|
|
|
#### .NET 10 Implementation Notes
|
|
|
|
**Decision**: Keep as stored procedure. The conditional status logic (success vs error) and VARBINARY handling make this a good fit for stored procedure. When migrating to blob storage, this procedure would be modified to store a blob URL instead.
|
|
|
|
### Requirement: ResetPartialSearches stored procedure
|
|
|
|
The system SHALL reset incomplete searches on application startup.
|
|
|
|
```sql
|
|
CREATE PROCEDURE [dbo].[ResetPartialSearches]
|
|
```
|
|
|
|
#### Business Rules
|
|
|
|
- Resets Status to 1 (Submitted) for any search with StartDT but no EndDT
|
|
- Clears StartDT to NULL
|
|
- Used on service startup to recover from crashes
|
|
|
|
#### Scenario: Recover from service crash
|
|
- **WHEN** search ID 789 has Status = 2, StartDT = yesterday, EndDT = NULL and ResetPartialSearches is called on service startup
|
|
- **THEN** Status = 1 (Submitted), StartDT = NULL, and the search will be re-processed
|
|
|
|
#### Scenario: Ignore completed searches
|
|
- **WHEN** search ID 111 has Status = 3, StartDT and EndDT both set and ResetPartialSearches is called
|
|
- **THEN** search ID 111 is unchanged (EndDT is not NULL)
|
|
|
|
#### .NET 10 Implementation Notes
|
|
|
|
**Decision**: Keep as stored procedure. Bulk UPDATE with conditional logic is efficient as stored procedure. Called once at startup, so performance is not critical, but atomic execution ensures consistency.
|
|
|
|
---
|
|
|
|
## User-Defined Types
|
|
|
|
### Requirement: WorkOrderFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for work order number filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[WorkOrderFilterParameter] AS TABLE (
|
|
WorkOrderNumber BIGINT
|
|
);
|
|
```
|
|
|
|
### Requirement: ItemNumberFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for item number filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[ItemNumberFilterParameter] AS TABLE (
|
|
ItemNumber VARCHAR(25)
|
|
);
|
|
```
|
|
|
|
### Requirement: ProfitCenterFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for profit center filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[ProfitCenterFilterParameter] AS TABLE (
|
|
Code VARCHAR(12)
|
|
);
|
|
```
|
|
|
|
### Requirement: WorkCenterFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for work center filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[WorkCenterFilterParameter] AS TABLE (
|
|
Code VARCHAR(12)
|
|
);
|
|
```
|
|
|
|
### Requirement: OperatorFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for operator filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[OperatorFilterParameter] AS TABLE (
|
|
UserName VARCHAR(10)
|
|
);
|
|
```
|
|
|
|
### Requirement: ComponentLotFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for component lot filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[ComponentLotFilterParameter] AS TABLE (
|
|
ComponentLotNumber VARCHAR(30),
|
|
ItemNumber VARCHAR(128)
|
|
);
|
|
```
|
|
|
|
### Requirement: ItemOperationMisFilterParameter type
|
|
|
|
The system SHALL provide a table-valued parameter for item/operation/MIS filtering.
|
|
|
|
```sql
|
|
CREATE TYPE [dbo].[ItemOperationMisFilterParameter] AS TABLE (
|
|
ItemNumber VARCHAR(32),
|
|
OperationNumber VARCHAR(32),
|
|
MisNumber VARCHAR(32),
|
|
MisRevision VARCHAR(32)
|
|
);
|
|
```
|
|
|
|
### .NET 10 Compatibility Notes
|
|
|
|
Table-valued parameters (TVPs) are fully supported in .NET 10 with both Dapper and EF Core:
|
|
|
|
**Dapper approach**:
|
|
```csharp
|
|
// Using DataTable
|
|
var table = new DataTable();
|
|
table.Columns.Add("WorkOrderNumber", typeof(long));
|
|
foreach (var wo in workOrders)
|
|
table.Rows.Add(wo);
|
|
|
|
var param = new { filter = table.AsTableValuedParameter("WorkOrderFilterParameter") };
|
|
```
|
|
|
|
**Modern alternative with `IEnumerable<SqlDataRecord>`**:
|
|
```csharp
|
|
// More memory-efficient for large datasets
|
|
public static IEnumerable<SqlDataRecord> ToWorkOrderRecords(IEnumerable<long> workOrders)
|
|
{
|
|
var metadata = new SqlMetaData("WorkOrderNumber", SqlDbType.BigInt);
|
|
var record = new SqlDataRecord(metadata);
|
|
foreach (var wo in workOrders)
|
|
{
|
|
record.SetInt64(0, wo);
|
|
yield return record;
|
|
}
|
|
}
|
|
```
|
|
|
|
**EF Core approach** (EF Core 8+):
|
|
```csharp
|
|
// EF Core supports TVPs via FromSqlRaw with structured parameters
|
|
var results = await context.WorkOrders
|
|
.FromSqlRaw("SELECT * FROM dbo.WorkOrder WHERE WorkOrderNumber IN (SELECT WorkOrderNumber FROM @filter)",
|
|
new SqlParameter("@filter", SqlDbType.Structured) { TypeName = "WorkOrderFilterParameter", Value = table })
|
|
.ToListAsync();
|
|
```
|
|
|
|
---
|
|
|
|
## Functions
|
|
|
|
### Requirement: MatchMIS function
|
|
|
|
The system SHALL provide a table-valued function to match work order steps to MIS data.
|
|
|
|
#### Parameters
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| @workOrderNumber | BIGINT | Work order to match |
|
|
| @itemNumber | VARCHAR(25) | Item number |
|
|
| @branchCode | VARCHAR(12) | Branch code |
|
|
| @routingType | VARCHAR(3) | Routing type |
|
|
| @issueDate | DATETIME | Work order issue date |
|
|
| @workCenterCode | VARCHAR(12) | Work center code |
|
|
| @sequenceNumber | DECIMAL(7,2) | Step sequence number |
|
|
| @steptimestamp | DATETIME | Step timestamp |
|
|
| @functionCode | VARCHAR(15) | Function code |
|
|
| @functionOperationDescription | VARCHAR(80) | Function description |
|
|
|
|
#### Return Table
|
|
|
|
| Column | Type | Description |
|
|
|--------|------|-------------|
|
|
| WorkOrderNumber | BIGINT | Input work order |
|
|
| ItemNumber | VARCHAR(25) | Input item |
|
|
| ItemDescription | VARCHAR(30) | Item description lookup |
|
|
| BranchCode | VARCHAR(12) | Input branch |
|
|
| WorkCenterCode | VARCHAR(12) | Input work center |
|
|
| StepTimestamp | DATETIME | Input timestamp |
|
|
| SequenceNumber | DECIMAL(7,2) | Input sequence |
|
|
| FunctionCode | VARCHAR(15) | Input function code |
|
|
| FunctionOperationDescription | VARCHAR(80) | Input function description |
|
|
| MatchedSequenceNumber | DECIMAL(7,2) | Matched sequence from routing |
|
|
| RoutingMatch | BIT | Found via F3112Z1 routing |
|
|
| MasterMatch | BIT | Found via F3003 master routing |
|
|
| MisNumber | VARCHAR(32) | MIS number |
|
|
| RevID | VARCHAR(32) | MIS revision |
|
|
| CharNumber | VARCHAR(32) | Characteristic number |
|
|
| MisSequenceNumber | VARCHAR(32) | MIS sequence number |
|
|
| TestDescription | VARCHAR(2000) | Test description |
|
|
| SamplingType | VARCHAR(32) | Sampling type |
|
|
| SamplingValue | VARCHAR(32) | Sampling value |
|
|
| ToolsGauges | VARCHAR(2000) | Tools and gauges |
|
|
| WorkInstructions | VARCHAR(2000) | Work instructions |
|
|
| Status | VARCHAR(32) | MIS status |
|
|
| ReleaseDate | DATETIME | MIS release date |
|
|
|
|
#### Business Rules
|
|
|
|
1. Looks up item description from Item table
|
|
2. Finds parent work order (if exists)
|
|
3. Searches F3112Z1 (WorkOrderRouting) for sequence aliases matching work center + function code
|
|
4. Falls back to F3003 (RouteMaster) if no routing matches found
|
|
5. Attempts to match MIS data with Status = 'Current' first
|
|
6. Falls back to Status = 'BackLevel' if no current match
|
|
7. Returns routing/master matches without MIS if no MIS data found
|
|
8. Returns input parameters with NULL MIS fields if no matches at all
|
|
|
|
#### Scenario: Match via routing with current MIS
|
|
- **WHEN** work order 12345, step 10 with function code "ASSY" is processed and MatchMIS finds a routing entry in F3112Z1 and MIS data exists with Status = 'Current'
|
|
- **THEN** RoutingMatch = 1, MasterMatch = 0, MIS fields populated
|
|
|
|
#### Scenario: Fallback to master routing
|
|
- **WHEN** work order 12345, step 10 with no routing entry is processed and MatchMIS finds match in F3003 RouteMaster
|
|
- **THEN** RoutingMatch = 0, MasterMatch = 1, MatchedSequenceNumber from master
|
|
|
|
#### Scenario: Fallback to BackLevel MIS
|
|
- **WHEN** routing match found but no 'Current' MIS exists and 'BackLevel' MIS exists for the item/operation
|
|
- **THEN** Status = 'BackLevel' MIS data is returned
|
|
|
|
#### Scenario: No MIS data available
|
|
- **WHEN** routing match found but no MIS data exists and MatchMIS completes
|
|
- **THEN** RoutingMatch/MasterMatch set, MIS fields = NULL
|
|
|
|
---
|
|
|
|
## Domain Model Cross-Reference
|
|
|
|
| Database Table | Domain Model Entity | Notes |
|
|
|----------------|---------------------|-------|
|
|
| Search | Search | Direct mapping; Criteria column maps to CriteriaJSON property |
|
|
| DataUpdate | DataUpdate | Direct mapping |
|
|
| WorkOrder (view) | WorkOrder | Access via view; _Curr/_Hist not exposed |
|
|
| WorkOrderStep (view) | WorkOrderStep | View adds FunctionOperationDescription |
|
|
| WorkOrderTime (view) | WorkOrderTime | Direct mapping via view |
|
|
| WorkOrderComponent (view) | WorkOrderComponent | Direct mapping via view |
|
|
| WorkOrderRouting | WorkOrderRouting | Single table, no view |
|
|
| LotUsage (view) | LotUsage | Direct mapping via view |
|
|
| Lot | Lot | Direct mapping |
|
|
| LotLocation | LotLocation | Direct mapping |
|
|
| Item | Item | Direct mapping |
|
|
| WorkCenter | WorkCenter | Direct mapping |
|
|
| ProfitCenter | ProfitCenter | Direct mapping |
|
|
| Branch | Branch | Direct mapping |
|
|
| JdeUser | JdeUser | Direct mapping |
|
|
| StatusCode | StatusCode | Direct mapping |
|
|
| FunctionCode | FunctionCode | Direct mapping |
|
|
| OrgHierarchy | OrgHierarchy | Direct mapping |
|
|
| RouteMaster | RouteMaster | Direct mapping |
|
|
| MisData | MisData | ObsoleteDate column included in domain model |
|
|
|
|
---
|
|
|
|
## EF Core Compatibility Notes
|
|
|
|
### Schema Patterns
|
|
|
|
The schema is compatible with EF Core 8+ with the following considerations:
|
|
|
|
1. **Composite Primary Keys**: EF Core supports composite keys but requires explicit configuration via `HasKey()` in `OnModelCreating`. The 7-column composite key on MisData is supported but may be cumbersome; consider using `[Keyless]` entity with manual queries if write operations are not needed.
|
|
|
|
2. **Views as Entities**: Union views (WorkOrder, WorkOrderStep, etc.) can be mapped as keyless entities using `HasNoKey()` and `ToView()`. This provides read-only access which matches the intended usage.
|
|
|
|
3. **Table-Valued Functions**: MatchMIS can be called via `FromSqlRaw` or configured as a TVF mapping in EF Core 8+.
|
|
|
|
4. **No Foreign Keys**: The schema intentionally omits foreign key constraints for bulk load performance. EF Core navigation properties can still be configured using `.HasForeignKey()` without database constraints (shadow foreign keys).
|
|
|
|
5. **DECIMAL Precision**: Ensure `HasPrecision(15, 2)` or `HasPrecision(18, 2)` is configured for decimal columns to match database precision.
|
|
|
|
### Recommended EF Core Configuration
|
|
|
|
```csharp
|
|
// For views - read-only access
|
|
modelBuilder.Entity<WorkOrder>()
|
|
.HasNoKey()
|
|
.ToView("WorkOrder");
|
|
|
|
// For composite keys
|
|
modelBuilder.Entity<WorkOrderStep>()
|
|
.HasKey(e => new { e.WorkOrderNumber, e.WorkCenterCode, e.StepNumber, e.StepTypeCode });
|
|
|
|
// For navigation without FK constraints
|
|
modelBuilder.Entity<WorkOrderTime>()
|
|
.HasOne<JdeUser>()
|
|
.WithMany()
|
|
.HasForeignKey(e => e.AddressNumber)
|
|
.HasPrincipalKey(u => u.AddressNumber);
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Notes
|
|
|
|
### Data Type Recommendations
|
|
|
|
| Current Type | Recommended Type | Rationale | Migration Priority |
|
|
|--------------|------------------|-----------|-------------------|
|
|
| DATETIME | DATETIME2(7) | Better precision (100ns vs 3.33ms), wider range (0001-9999 vs 1753-9999), no daylight saving ambiguity | Future - new columns only |
|
|
| GETDATE() | SYSUTCDATETIME() | UTC timestamps for distributed systems consistency | Future - new procedures |
|
|
| VARCHAR(MAX) for Criteria | NVARCHAR(MAX) | Unicode support for international characters | Low - backward compatible |
|
|
| SMALLINT for Status | INT | Consistent with .NET enum defaults (int) | Low - no functional impact |
|
|
| VARBINARY(MAX) for Results | Azure Blob Storage URL | Reduces database size, improves scalability | Medium - see blob storage notes |
|
|
| VARCHAR(MAX) for Description | VARCHAR(2000) | Bounded length improves query planning | Low - FunctionCode only |
|
|
|
|
### Excel Results Storage Migration Path
|
|
|
|
Current implementation stores Excel results as `VARBINARY(MAX)` in the Search.Results column. For improved scalability:
|
|
|
|
**Phase 1 (Current)**: Keep VARBINARY(MAX) for initial .NET 10 migration
|
|
- Maintains backward compatibility
|
|
- No infrastructure changes required
|
|
- Suitable for single-database deployment
|
|
|
|
**Phase 2 (Future)**: Migrate to Azure Blob Storage
|
|
1. Add `ResultsBlobUrl VARCHAR(500)` column to Search table
|
|
2. Modify CompleteSearch to store blob URL instead of binary
|
|
3. Create migration script to move existing Results to blob storage
|
|
4. Eventually remove Results VARBINARY(MAX) column
|
|
|
|
**Blob Storage Benefits**:
|
|
- Database size reduction (Excel files typically 100KB-10MB each)
|
|
- Better backup/restore performance
|
|
- CDN integration for download acceleration
|
|
- Lifecycle management (auto-delete old results)
|
|
|
|
### Stored Procedure Evaluation Summary
|
|
|
|
| Procedure | Decision | Rationale |
|
|
|-----------|----------|-----------|
|
|
| SubmitSearch | Keep | Atomic INSERT with OUTPUT parameter |
|
|
| StartSearch | Keep (or migrate) | Simple UPDATE, but consistency with lifecycle |
|
|
| CompleteSearch | Keep | Conditional logic, VARBINARY handling |
|
|
| ResetPartialSearches | Keep | Bulk conditional UPDATE at startup |
|
|
|
|
All procedures can be called from .NET 10 via Dapper or EF Core. Migration to C# service layer is optional and would primarily benefit unit testing (mockable repository interfaces).
|
|
|
|
### Current/Historical Table Pattern
|
|
|
|
The `_Curr` / `_Hist` suffix pattern partitions data based on work order status:
|
|
- `_Curr`: Active work orders (typically status < 90)
|
|
- `_Hist`: Completed/closed work orders
|
|
|
|
**Future Consideration**: SQL Server table partitioning could replace the dual-table pattern:
|
|
- Single table with partition function on StatusCode or date
|
|
- Simplifies application code (no UNION ALL views)
|
|
- Enables partition switching for archive operations
|
|
- Requires SQL Server Enterprise Edition or Azure SQL
|
|
|
|
### Index Coverage for Historical Tables
|
|
|
|
The following indexes should be verified on historical tables (matching current table patterns):
|
|
- `IX_WorkOrder_Hist_ParentWorkOrderNumber`
|
|
- `IX_WorkOrder_Hist_ItemNumber`
|
|
- `IX_WorkOrderStep_Hist_WorkCenterCode`
|
|
- `IX_WorkOrderTime_Hist_Lookup`
|
|
- `IX_WorkOrderTime_Hist_AddressNumber`
|
|
- `IX_WorkOrderComponent_Hist_WorkOrderNumber`
|
|
- `IX_WorkOrderComponent_Hist_LotLookup`
|
|
- `IX_LotUsage_Hist_WorkOrderNumber`
|
|
- `IX_LotUsage_Hist_LotLookup`
|
|
|
|
---
|
|
|
|
## Resolved Questions
|
|
|
|
### Question 1: Setup.sql vs sqlproj scripts
|
|
|
|
**Resolution**: The sqlproj scripts in Tables/ folder are authoritative. Setup.sql appears to be an older bootstrapping script that predates the current _Curr/_Hist partitioning pattern. The migration should use the sqlproj-defined schema.
|
|
|
|
### Question 2: WorkOrderScrap table conflict
|
|
|
|
**Resolution**: Use WorkOrderStep.ScrappedQuantity pattern (from sqlproj). The WorkOrderScrap table in Setup.sql is deprecated. The WorkOrderTotalScrap view should aggregate from WorkOrderStep, not a separate WorkOrderScrap table.
|
|
|
|
### Question 3: LotLocation.LotNumber size (120 vs 30)
|
|
|
|
**Resolution**: Intentional design. LotLocation tracks physical locations which may use extended lot identifiers that concatenate base lot number with location-specific suffixes. The 120-character limit accommodates these extended identifiers.
|
|
|
|
### Question 4: MisData.ObsoleteDate in domain model
|
|
|
|
**Resolution**: Include ObsoleteDate in domain model. This column tracks MIS revision obsolescence and is useful for audit/reporting purposes.
|
|
|
|
### Question 5: FunctionCode.Description size
|
|
|
|
**Resolution**: Keep VARCHAR(MAX) for backward compatibility. Document as a future migration candidate to VARCHAR(2000) if query performance issues arise.
|
|
|
|
### Question 6: No foreign keys
|
|
|
|
**Resolution**: Intentional for bulk load performance. The cache database receives large bulk inserts from JDE/CMS sync processes. Foreign key constraints would significantly slow these operations. Referential integrity is maintained at the application layer.
|
|
|
|
### Question 7: Composite primary keys
|
|
|
|
**Resolution**: Keep existing composite keys. While surrogate keys would simplify EF Core mapping, the existing composite keys accurately represent the natural business keys and have worked reliably in production. The 7-column MisData key can be handled with `[Keyless]` mapping if needed.
|
|
|
|
### Question 8: Data partitioning strategy
|
|
|
|
**Resolution**: Work orders are partitioned based on the WDSTEP (status) field from JDE. Status codes >= 90 indicate completed/closed work orders and are stored in _Hist tables. The sync process handles this partitioning during data refresh.
|
|
|
|
### Question 9: Index coverage
|
|
|
|
**Resolution**: Historical tables should mirror current table indexes. See "Index Coverage for Historical Tables" section above.
|
|
|
|
### Question 10: MisData cache refresh
|
|
|
|
**Resolution**: MisData uses full table refresh (TRUNCATE + bulk INSERT) rather than incremental sync. The CMS source system does not provide reliable change tracking, so full refresh is the only option. This is acceptable given the relatively small MisData row count.
|
|
|
|
### Question 11: Results storage
|
|
|
|
**Resolution**: Keep VARBINARY(MAX) for initial .NET 10 migration. Plan for Azure Blob Storage migration as Phase 2 enhancement. See "Excel Results Storage Migration Path" section.
|
|
|
|
### Question 12: Search.Criteria encryption
|
|
|
|
**Resolution**: Not required for initial migration. Search criteria contains filter values (dates, work order numbers) but no PII or sensitive data. Encryption at rest can be achieved via SQL Server Transparent Data Encryption (TDE) if needed at the database level.
|
|
|
|
### Question 13: Audit columns
|
|
|
|
**Resolution**: Defer to future enhancement. Existing LastUpdateDT columns provide basic audit capability. Full audit (CreatedDT, CreatedBy, ModifiedDT, ModifiedBy) can be added as a Phase 2 enhancement if compliance requirements dictate.
|
|
|
|
### Question 14: ScrappedQuantity default
|
|
|
|
**Resolution**: Add DEFAULT constraint in migration script. The application logic assumes default of 0, so the database should enforce this:
|
|
```sql
|
|
ALTER TABLE dbo.WorkOrderStep_Curr ADD CONSTRAINT DF_WorkOrderStep_Curr_ScrappedQuantity DEFAULT 0 FOR ScrappedQuantity;
|
|
ALTER TABLE dbo.WorkOrderStep_Hist ADD CONSTRAINT DF_WorkOrderStep_Hist_ScrappedQuantity DEFAULT 0 FOR ScrappedQuantity;
|
|
```
|
|
|
|
---
|
|
|
|
## Codex Review Findings
|
|
|
|
*This section documents findings from automated and manual code review.*
|
|
|
|
### Finding 1: Inconsistent timestamp columns
|
|
|
|
Some tables use `DATETIME` while the migration notes recommend `DATETIME2(7)`. For backward compatibility, existing `DATETIME` columns will be preserved. New columns added during migration should use `DATETIME2(7)`.
|
|
|
|
### Finding 2: Missing indexes on historical tables
|
|
|
|
Historical tables (_Hist) may be missing indexes that exist on current tables (_Curr). Verify index parity during migration. See "Index Coverage for Historical Tables" section.
|
|
|
|
### Finding 3: VARCHAR(MAX) usage
|
|
|
|
FunctionCode.Description uses VARCHAR(MAX). While functional, unbounded columns can impact query optimizer decisions. Monitor for performance issues; consider migration to VARCHAR(2000) if problems arise.
|