From 21b5d6fcb7e8fce0c750e7f96edceb96f7fd0f23 Mon Sep 17 00:00:00 2001 From: Yorick Date: Fri, 3 Apr 2026 13:45:28 +0000 Subject: [PATCH] Refactor ItemWin.w to use Business Entity pattern - Create ItemDataset.i with temp-table definition for Item - Create ItemEntity.cls with CRUD operations and validation - Update EntityFactory.cls to include ItemEntity singleton - Refactor ItemWin.w to use business entity layer instead of direct DB access Resolves #1 --- src/ItemWin.w | 106 +++++++++++++++++---------------- src/business/EntityFactory.cls | 20 ++++++- src/business/ItemDataset.i | 20 +++++++ src/business/ItemEntity.cls | 101 +++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 52 deletions(-) create mode 100644 src/business/ItemDataset.i create mode 100644 src/business/ItemEntity.cls diff --git a/src/ItemWin.w b/src/ItemWin.w index 8959cef..2f04d4e 100644 --- a/src/ItemWin.w +++ b/src/ItemWin.w @@ -1,8 +1,5 @@ &ANALYZE-SUSPEND _VERSION-NUMBER AB_v10r12 GUI &ANALYZE-RESUME -/* Connected Databases - sports PROGRESS -*/ &Scoped-define WINDOW-NAME C-Win &ANALYZE-SUSPEND _UIB-CODE-BLOCK _CUSTOM _DEFINITIONS C-Win /*------------------------------------------------------------------------ @@ -25,6 +22,10 @@ /* This .W file was created with the Progress AppBuilder. */ /*----------------------------------------------------------------------*/ +/* Include business entity classes */ +USING business.ItemEntity FROM PROPATH. +USING business.EntityFactory FROM PROPATH. + /* Create an unnamed pool to store all the widgets created by this procedure. This is a good default which assures that this procedure's triggers and internal procedures @@ -39,6 +40,9 @@ CREATE WIDGET-POOL. /* Local Variable Definitions --- */ +/* Include dataset definitions */ +{business/ItemDataset.i} + /* _UIB-CODE-BLOCK-END */ &ANALYZE-RESUME @@ -53,16 +57,6 @@ CREATE WIDGET-POOL. /* Name of designated FRAME-NAME and/or first browse and/or first query */ &Scoped-define FRAME-NAME DEFAULT-FRAME -/* Internal Tables (found by Frame, Query & Browse Queries) */ -&Scoped-define INTERNAL-TABLES Item - -/* Definitions for FRAME DEFAULT-FRAME */ -&Scoped-define QUERY-STRING-DEFAULT-FRAME FOR EACH Item SHARE-LOCK -&Scoped-define OPEN-QUERY-DEFAULT-FRAME OPEN QUERY DEFAULT-FRAME FOR EACH Item SHARE-LOCK. -&Scoped-define TABLES-IN-QUERY-DEFAULT-FRAME Item -&Scoped-define FIRST-TABLE-IN-QUERY-DEFAULT-FRAME Item - - /* Standard List Definitions */ &Scoped-Define ENABLED-OBJECTS FILL-IN_ItemNum FILL-IN_Price BUTTON-4 ~ BUTTON-3 @@ -100,11 +94,6 @@ DEFINE VARIABLE FILL-IN_Price AS DECIMAL FORMAT "->,>>>,>>9.99" INITIAL 0 VIEW-AS FILL-IN SIZE 14 BY 1 NO-UNDO. -/* Query definitions */ -&ANALYZE-SUSPEND -DEFINE QUERY DEFAULT-FRAME FOR - Item SCROLLING. -&ANALYZE-RESUME /* ************************ Frame Definitions *********************** */ @@ -177,8 +166,7 @@ THEN C-Win:HIDDEN = no. &ANALYZE-SUSPEND _QUERY-BLOCK FRAME DEFAULT-FRAME /* Query rebuild information for FRAME DEFAULT-FRAME - _TblList = "sports.Item" - _Query is OPENED + _Query is NOT OPENED */ /* FRAME DEFAULT-FRAME */ &ANALYZE-RESUME @@ -218,16 +206,23 @@ END. &ANALYZE-SUSPEND _UIB-CODE-BLOCK _CONTROL BUTTON-3 C-Win ON CHOOSE OF BUTTON-3 IN FRAME DEFAULT-FRAME /* Get Item */ DO: - ASSIGN FILL-IN_ItemNum. - FIND FIRST Item WHERE Item.ItemNum = INTEGER(FILL-IN_ItemNum) NO-LOCK NO-ERROR. - IF AVAILABLE Item THEN - DO: - FILL-IN_Price = Item.Price. - DISPLAY FILL-IN_Price WITH FRAME {&frame-name}. + VAR INTEGER iItemNum = INTEGER(FILL-IN_ItemNum:screen-value). + VAR EntityFactory objFactory = EntityFactory:GetInstance(). + VAR ItemEntity objItemEntity = objFactory:GetItemEntity(). + VAR LOGICAL lItemFound = objItemEntity:GetItemByNumber(iItemNum, OUTPUT DATASET dsItem). + + /* Update the UI based on results */ + IF lItemFound THEN DO: + /* Find the item record in the temp-table */ + FIND FIRST ttItem. + IF AVAILABLE ttItem THEN DO: + /* Set the price field and display it */ + FILL-IN_Price = ttItem.Price. + DISPLAY FILL-IN_Price WITH FRAME {&frame-name}. + END. END. - ELSE - MESSAGE 'Item not found' VIEW-AS ALERT-BOX. - + ELSE + MESSAGE "Item not found" VIEW-AS ALERT-BOX. END. /* _UIB-CODE-BLOCK-END */ @@ -238,28 +233,39 @@ END. &ANALYZE-SUSPEND _UIB-CODE-BLOCK _CONTROL BUTTON-4 C-Win ON CHOOSE OF BUTTON-4 IN FRAME DEFAULT-FRAME /* Save */ DO: - VAR DECIMAL dTotal. - FIND FIRST Item WHERE Item.ItemNum = INTEGER(FILL-IN_ItemNum) EXCLUSIVE-LOCK NO-ERROR. - IF AVAILABLE Item THEN - DO: - ASSIGN FILL-IN_Price. - IF FILL-IN_Price = 0 THEN - DO: - MESSAGE 'Price cannot be empty' VIEW-AS ALERT-BOX. - RETURN NO-APPLY. - END. - dTotal = Item.OnHand * FILL-IN_Price. - IF dTotal > 6000 THEN - DO: - MESSAGE 'Total value onhand will be ' dTotal - ', should not be larger than 6000' VIEW-AS ALERT-BOX. - RETURN NO-APPLY. - END. - Item.Price = FILL-IN_Price. + VAR INTEGER iItemNum = INTEGER(FILL-IN_ItemNum:screen-value). + VAR EntityFactory objFactory = EntityFactory:GetInstance(). + VAR ItemEntity objItemEntity = objFactory:GetItemEntity(). + VAR LOGICAL lItemFound = objItemEntity:GetItemByNumber(iItemNum, OUTPUT DATASET dsItem). + VAR CHARACTER cErrorMessage = "". + VAR LOGICAL isValid = TRUE. + + /* Get form data */ + ASSIGN FILL-IN_Price. + + IF lItemFound THEN DO: + /* Find the item record in the temp-table */ + FIND FIRST ttItem NO-ERROR. + IF AVAILABLE ttItem THEN DO: + /* Enable change tracking */ + TEMP-TABLE ttItem:TRACKING-CHANGES = TRUE. + + /* Update the price */ + ttItem.Price = FILL-IN_Price. + + /* Validate the data */ + isValid = objItemEntity:ValidateItem(DATASET dsItem BY-REFERENCE, OUTPUT cErrorMessage). + + IF isValid THEN DO: + /* Save changes if the data is valid */ + objItemEntity:UpdateItem(DATASET dsItem BY-REFERENCE). + END. + ELSE + MESSAGE cErrorMessage VIEW-AS ALERT-BOX. + END. END. ELSE - MESSAGE 'Item not found' VIEW-AS ALERT-BOX. - + MESSAGE "Item not found" VIEW-AS ALERT-BOX. END. /* _UIB-CODE-BLOCK-END */ @@ -332,8 +338,6 @@ PROCEDURE enable_UI : Settings" section of the widget Property Sheets. ------------------------------------------------------------------------------*/ - {&OPEN-QUERY-DEFAULT-FRAME} - GET FIRST DEFAULT-FRAME. DISPLAY FILL-IN_ItemNum FILL-IN_Price WITH FRAME DEFAULT-FRAME IN WINDOW C-Win. ENABLE FILL-IN_ItemNum FILL-IN_Price BUTTON-4 BUTTON-3 diff --git a/src/business/EntityFactory.cls b/src/business/EntityFactory.cls index 5ae598a..f5e2279 100644 --- a/src/business/EntityFactory.cls +++ b/src/business/EntityFactory.cls @@ -10,6 +10,7 @@ USING Progress.Lang.*. USING business.CustomerEntity. +USING business.ItemEntity. USING business.EntityFactory. BLOCK-LEVEL ON ERROR UNDO, THROW. @@ -21,6 +22,7 @@ CLASS business.EntityFactory USE-WIDGET-POOL: /* Singleton instances of business entities */ VAR PRIVATE CustomerEntity objCustomerEntityInstance. + VAR PRIVATE ItemEntity objItemEntityInstance. /*------------------------------------------------------------------------------ Purpose: Private constructor to prevent direct instantiation @@ -51,6 +53,17 @@ CLASS business.EntityFactory USE-WIDGET-POOL: RETURN objCustomerEntityInstance. END METHOD. + + /*------------------------------------------------------------------------------ + Purpose: Get the singleton instance of ItemEntity + Notes: + ------------------------------------------------------------------------------*/ + METHOD PUBLIC ItemEntity GetItemEntity(): + IF objItemEntityInstance = ? THEN + objItemEntityInstance = NEW ItemEntity(). + + RETURN objItemEntityInstance. + END METHOD. /*------------------------------------------------------------------------------ Purpose: Reset all entity instances (for testing or cleanup) @@ -60,7 +73,12 @@ CLASS business.EntityFactory USE-WIDGET-POOL: IF VALID-OBJECT(objCustomerEntityInstance) THEN DELETE OBJECT objCustomerEntityInstance. - objCustomerEntityInstance = ?. + objCustomerEntityInstance = ?. + + IF VALID-OBJECT(objItemEntityInstance) THEN + DELETE OBJECT objItemEntityInstance. + + objItemEntityInstance = ?. END METHOD. END CLASS. diff --git a/src/business/ItemDataset.i b/src/business/ItemDataset.i new file mode 100644 index 0000000..356ee9c --- /dev/null +++ b/src/business/ItemDataset.i @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------ + File : ItemDataset.i + Purpose : Dataset definition for Item entity + Syntax : + Description : + Author(s) : + Created : + Notes : +----------------------------------------------------------------------*/ + +/* Define temp-table for Item */ +DEFINE TEMP-TABLE ttItem BEFORE-TABLE bttItem + FIELD ItemNum AS INTEGER INITIAL "0" LABEL "Item Num" + FIELD ItemName AS CHARACTER LABEL "Item Name" + FIELD Price AS DECIMAL INITIAL "0" LABEL "Price" + FIELD OnHand AS INTEGER INITIAL "0" LABEL "On Hand" + INDEX ItemNum IS PRIMARY UNIQUE ItemNum ASCENDING. + +/* Define dataset for Item */ +DEFINE DATASET dsItem FOR ttItem. diff --git a/src/business/ItemEntity.cls b/src/business/ItemEntity.cls new file mode 100644 index 0000000..e4d63ba --- /dev/null +++ b/src/business/ItemEntity.cls @@ -0,0 +1,101 @@ +/*------------------------------------------------------------------------ + File : ItemEntity + Purpose : Business Entity for Item data access + Syntax : + Description : + Author(s) : + Created : + Notes : +----------------------------------------------------------------------*/ + +USING Progress.Lang.*. +USING OpenEdge.BusinessLogic.BusinessEntity. + +BLOCK-LEVEL ON ERROR UNDO, THROW. + +CLASS business.ItemEntity INHERITS BusinessEntity USE-WIDGET-POOL: + + /* Include dataset definition */ + {business/ItemDataset.i} + + /* Define data sources */ + DEFINE DATA-SOURCE srcItem FOR Item. + + /*------------------------------------------------------------------------------ + Purpose: Constructor for ItemEntity + Notes: + ------------------------------------------------------------------------------*/ + CONSTRUCTOR PUBLIC ItemEntity(): + SUPER(DATASET dsItem:HANDLE). + + VAR HANDLE[1] hDataSourceArray = DATA-SOURCE srcItem:HANDLE. + VAR CHARACTER[1] cSkipListArray = [""]. + + /* Data Source for each table in dataset */ + THIS-OBJECT:ProDataSource = hDataSourceArray. + /* Skip-list entry for each table in dataset */ + THIS-OBJECT:SkipList = cSkipListArray. + END CONSTRUCTOR. + + /*------------------------------------------------------------------------------ + Purpose: Get item by item number + Notes: + ------------------------------------------------------------------------------*/ + METHOD PUBLIC LOGICAL GetItemByNumber(INPUT ipiItemNum AS INTEGER, + OUTPUT DATASET dsItem): + VAR CHARACTER cFilter. + VAR LOGICAL lFound = FALSE. + + /* Build filter for item number */ + cFilter = "WHERE Item.ItemNum = " + STRING(ipiItemNum). + + /* Read data using filter */ + THIS-OBJECT:ReadData(cFilter). + + /* Check if item was found */ + lFound = CAN-FIND(FIRST ttItem). + + RETURN lFound. + END METHOD. + + /*------------------------------------------------------------------------------ + Purpose: Update item data + Notes: + ------------------------------------------------------------------------------*/ + METHOD PUBLIC VOID UpdateItem(INPUT-OUTPUT DATASET dsItem): + /* Update data in database */ + THIS-OBJECT:UpdateData(DATASET dsItem BY-REFERENCE). + END METHOD. + + /*------------------------------------------------------------------------------ + Purpose: Validate item data + Notes: + ------------------------------------------------------------------------------*/ + METHOD PUBLIC LOGICAL ValidateItem(INPUT-OUTPUT DATASET dsItem, + OUTPUT errorMessage AS CHARACTER): + VAR LOGICAL isValid = TRUE. + VAR DECIMAL dTotal. + + /* Find the first item in the dataset */ + FIND FIRST ttItem NO-ERROR. + IF AVAILABLE ttItem THEN DO: + /* Validate price is not zero */ + IF ttItem.Price = 0 THEN DO: + isValid = FALSE. + errorMessage = "Price cannot be empty". + RETURN isValid. + END. + + /* Validate total value on hand */ + dTotal = ttItem.OnHand * ttItem.Price. + IF dTotal > 6000 THEN DO: + isValid = FALSE. + errorMessage = "Total value onhand will be " + STRING(dTotal) + ", should not be larger than 6000". + RETURN isValid. + END. + END. + + RETURN isValid. + END METHOD. + +END CLASS.