Files
jdescopingtool/openspec/specs/database-schema/spec.md
T
Joseph Doherty 26ff8d9b4f Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
2026-01-02 07:43:29 -05:00

50 KiB

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.

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.

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.

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.

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).

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.

CREATE PROCEDURE [dbo].[StartSearch] (@p_SearchID INT)

Business Rules

  • Sets Status = 2 (Started)
  • Sets StartDT = GETDATE()
  • 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.

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, ) is called
  • THEN Status = 3 (Ended), Results = , 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.

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.

CREATE TYPE [dbo].[WorkOrderFilterParameter] AS TABLE (
    WorkOrderNumber BIGINT
);

Requirement: ItemNumberFilterParameter type

The system SHALL provide a table-valued parameter for item number filtering.

CREATE TYPE [dbo].[ItemNumberFilterParameter] AS TABLE (
    ItemNumber VARCHAR(25)
);

Requirement: ProfitCenterFilterParameter type

The system SHALL provide a table-valued parameter for profit center filtering.

CREATE TYPE [dbo].[ProfitCenterFilterParameter] AS TABLE (
    Code VARCHAR(12)
);

Requirement: WorkCenterFilterParameter type

The system SHALL provide a table-valued parameter for work center filtering.

CREATE TYPE [dbo].[WorkCenterFilterParameter] AS TABLE (
    Code VARCHAR(12)
);

Requirement: OperatorFilterParameter type

The system SHALL provide a table-valued parameter for operator filtering.

CREATE TYPE [dbo].[OperatorFilterParameter] AS TABLE (
    UserName VARCHAR(10)
);

Requirement: ComponentLotFilterParameter type

The system SHALL provide a table-valued parameter for component lot filtering.

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.

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:

// 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>:

// 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+):

// 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.

// 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:

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.