Home / Demo
Retail order system
Six Forms modules: a main menu, ORDERS (master-detail with a list of values and alerts) and four single-table forms (categories, customers, employees, products).
Target: .NET 10 (ASP.NET Core minimal API) and PostgreSQL, a single-page web client. Original Forms source:
github.com/v7med7elmy-ai/oracle-retail-management-system
at commit 30b5409, licence: MIT.
The live demos share one database per app: visitors see each other's changes, and the data resets to the scenario fixture every night.
Parity: 17 of 17 scenarios
Each scenario was recorded on the original and replayed against the migrated app. Three runs from a fresh database gave byte-identical diffs for every scenario; none has a defect. Open a scenario for its steps, screenshots of both apps, the golden record, the diff and its waivers.
- Match: the same value. Tolerated: different only within a written rule of the tolerance policy, here empty Forms detail rows the web page does not draw, money shown to two decimals, text differing only in surrounding spaces, and a date that defaults to today.
- Waived 0 everywhere: 10 waivers are proposed, but the migrated app implements every one of those source behaviours, so no value needs a waiver. Each stays available if the customer prefers the modern behaviour (see waivers).
- Defect: a difference to repair. Unmapped: recorded on the original but not compared, because the migrated screen has no counterpart (mostly the Forms record indicator, window list and alert titles, and hidden items); each scenario page lists them.
| Scenario | What it does | match | tolerated | waived | defect | unmapped | Three runs |
|---|---|---|---|---|---|---|---|
| categories-add-delete | Add a record, save, delete the unreferenced fixture record. | 64 | 0 | 0 | 0 | 12 | identical |
| categories-browse | Open categories from the main menu, browse first/last/next/previous, exit to the menu. | 57 | 0 | 0 | 0 | 14 | identical |
| customers-add-delete | Add a record, save, delete the unreferenced fixture record. | 68 | 4 | 0 | 0 | 12 | identical |
| customers-browse | Open customers from the main menu, browse first/last/next/previous, exit to the menu. | 62 | 0 | 0 | 0 | 14 | identical |
| employees-add-delete | Add a record, save, delete the unreferenced fixture record. | 78 | 4 | 0 | 0 | 12 | identical |
| employees-browse | Open employees from the main menu, browse first/last/next/previous, exit to the menu. | 66 | 6 | 0 | 0 | 15 | identical |
| main-menu-exit | Press the main menu's exit button. | 22 | 0 | 0 | 0 | 4 | identical |
| main-menu-orders | Open orders from the main menu, exit back to the menu. | 34 | 27 | 0 | 0 | 11 | identical |
| order-two-lines | Create an order with two lines, change a quantity, save. | 259 | 290 | 0 | 0 | 99 | identical |
| orders-add-new | Query the orders, press ADD NEW, fill a header and save; ORDER_ID is mandatory and empty, so the save stops. | 115 | 216 | 0 | 0 | 62 | identical |
| orders-browse | Execute the query, decline to save the PRE-FORM record, browse the orders and their lines. | 118 | 170 | 0 | 0 | 51 | identical |
| orders-delete-empty | Query the orders, go to order 902 (no lines), press DELETE; the order is deleted. | 97 | 123 | 0 | 0 | 37 | identical |
| orders-delete-refused | Query the orders, press DELETE on order 901, which has lines; nothing is deleted. | 91 | 120 | 0 | 0 | 39 | identical |
| orders-navigate-unsaved | Query, change a line quantity, press Next; Cancel, then Next again and No. | 140 | 189 | 0 | 0 | 62 | identical |
| orders-save-fails | Enter an order with a line whose ITEM_ID already exists, press SAVE; the commit fails. | 135 | 194 | 0 | 0 | 60 | identical |
| products-add-delete | Add a record, save, delete the unreferenced fixture record. | 102 | 8 | 0 | 0 | 17 | identical |
| products-browse | Open products from the main menu, browse first/last/next/previous, exit to the menu. | 62 | 10 | 0 | 0 | 14 | identical |
| total | 1570 | 1361 | 0 | 0 | 535 |
The same app with an Angular front end: 17 of 17
The check does not depend on what the new interface is built with. This app has a second front end written in Angular on the same back end and API, and the same scenarios, mapping, tolerance policy and waivers check it: 17 of 17 scenarios at parity, 0 defects, three runs from a fresh database byte-identical, and every diff byte-identical to the default front end's.
| Scenario | match | tolerated | waived | defect | unmapped | Three runs | Diff as the default front end |
|---|---|---|---|---|---|---|---|
| categories-add-delete | 64 | 0 | 0 | 0 | 12 | identical | identical |
| categories-browse | 57 | 0 | 0 | 0 | 14 | identical | identical |
| customers-add-delete | 68 | 4 | 0 | 0 | 12 | identical | identical |
| customers-browse | 62 | 0 | 0 | 0 | 14 | identical | identical |
| employees-add-delete | 78 | 4 | 0 | 0 | 12 | identical | identical |
| employees-browse | 66 | 6 | 0 | 0 | 15 | identical | identical |
| main-menu-exit | 22 | 0 | 0 | 0 | 4 | identical | identical |
| main-menu-orders | 34 | 27 | 0 | 0 | 11 | identical | identical |
| order-two-lines | 259 | 290 | 0 | 0 | 99 | identical | identical |
| orders-add-new | 115 | 216 | 0 | 0 | 62 | identical | identical |
| orders-browse | 118 | 170 | 0 | 0 | 51 | identical | identical |
| orders-delete-empty | 97 | 123 | 0 | 0 | 37 | identical | identical |
| orders-delete-refused | 91 | 120 | 0 | 0 | 39 | identical | identical |
| orders-navigate-unsaved | 140 | 189 | 0 | 0 | 62 | identical | identical |
| orders-save-fails | 135 | 194 | 0 | 0 | 60 | identical | identical |
| products-add-delete | 102 | 8 | 0 | 0 | 17 | identical | identical |
| products-browse | 62 | 10 | 0 | 0 | 14 | identical | identical |
The app is also rebuilt from native APEX components on its original Oracle schema, checked with the same scenarios: the APEX version.
Faithful to the original
The migrated app copies the original's behaviour, including what looks like a bug. These are the places a visitor will notice.
The migrated app has a new interface, but its behaviour is the original's, recorded scenario by scenario. Where the original does something odd, the migrated app does it too, so the diff stays at zero defects. Each waiver (W1-W10) names a behaviour the customer can choose to drop; until they do, it stays. In the app, a grey note under the message line or the alert explains these when they appear.
| What you see | Why | Waiver |
|---|---|---|
| "FRM-41003: This function cannot be performed here." when the app starts | The original's main menu runs a built-in in its startup trigger that fails there. | W8 |
| Orders opens on a blank new order with a number (16), and the first Query / reload asks "Do you want to save the changes you have made?" | The original creates a record and draws an order number when the form opens. Yes saves a blank order; No discards it. | W2 |
| An order saved from that record gets the next number (17), not the one shown | The original draws a second number when it inserts. | W2 |
| New orders are saved without an order date | The original inserts the empty date item as NULL. | W1 |
| Two alerts after every order save: "Saved." and then تم حفظ الأوردر بنجاح يا بطل! ("The order was saved successfully, hero!") | The commit confirmation and the form's own success message, each a modal alert in the original. A failed save shows حصل مشكلة في الحفظ، اتأكد من البيانات ("A problem occurred while saving, check the data"). | W4 |
| "Add new" on Orders can never be saved (FRM-40202, then FRM-40222) | The added record has no number, and the original's order id is required but disabled. Enter new orders in the record the form opens on. | W10 |
| Order lines need a typed Item id, and ids are unique across all orders | The original has the user type the line key and draws none from its sequence. | W3 |
| "Customer Id1" on the message line after a query in Orders | A debug message left in a trigger: the first order's customer. | W6 |
| "Category Id Required902" (or Customer, Employee, Product) after Next, Previous or Last | A debug message left in a trigger: the record's key appended to fixed text. | W5 |
| "Press Tab to move between choices, Return to select." while the save question is open | The Forms Java client's keyboard hint for its dialogs. | W9 |
| The alert "gooooooooooooooooooooood" when leaving Employees | A developer's placeholder alert on the exit button. | W7 |
| Errors read "FRM-40508: ORACLE error: unable to INSERT record." (or UPDATE, DELETE), also against PostgreSQL | The original's wording, kept because the scenarios compare it. The migrated app adds one line under it with the reason (for example "Price must be a number"). | - |
| Typed primary keys in Categories, Customers, Employees and Products | The original has the user type each key. | - |
| Delete commits at once, without asking | The original's delete button deletes and commits. | - |
| Exit from Orders drops unsaved order lines without asking | The original leaves the form without validating (EXIT_FORM NO_VALIDATE). | - |
| Total amount and Final amount stay empty after lines are added | The original never computes them; only each line's total is computed. | - |
| The order type list offers "sala" | A list value of the original. | - |
| A message stays on the message line while you type | The Forms message line keeps its last message until the next one. | - |
What is new rather than copied: the layout, the menu, the dialogs, the names shown next to customer, employee, product and category ids, the reason line under database errors, Return moving to the next field instead of saving, and the page shown after Exit. A form opened from the menu shows the trail "Main menu > Customers" and the suspended menu under it, as the original's CALL_FORM leaves the menu waiting; its Exit button reads "Back to menu", and the browser's Back runs the same Exit, save question included.
Coverage of the original
Every trigger and program unit of the Forms modules, measured with Forms Runtime Diagnostics while the
scenarios ran; database PL/SQL measured with DBMS_PLSQL_CODE_COVERAGE.
| Module | Triggers | Program units | Covered | Unreachable | Blocked |
|---|---|---|---|---|---|
| MAIN_MENU | 7 | 0 | 7 | 0 | 0 |
| ORDERS | 14 | 3 | 17 | 0 | 0 |
| CATEGORIES_FORM | 9 | 0 | 9 | 0 | 0 |
| CUSTOMERS_FORM | 9 | 0 | 9 | 0 | 0 |
| EMPLOYEES_FORM | 9 | 0 | 9 | 0 | 0 |
| PRODUCT_FORM | 9 | 0 | 9 | 0 | 0 |
| total | 57 | 3 | 60 | 0 | 0 |
Decision branches: 18, of which 14 covered, 2 unreachable (retirement candidates) and 2 blocked. Database PL/SQL: 1 unit(s) in the coverage report, 1 of 1 blocks covered. Exit check: PASS.
Every branch with its class
| Trigger or unit | Branch | Class | Scenarios or reason |
|---|---|---|---|
ORDERS/ORDERS.PUSH_BUTTON10/WHEN-BUTTON-PRESSED | commit succeeded (FORM_SUCCESS TRUE) | covered | order-two-lines |
ORDERS/ORDERS.PUSH_BUTTON10/WHEN-BUTTON-PRESSED | commit failed (FORM_SUCCESS FALSE) | covered | orders-add-new, orders-save-fails |
ORDERS/ORDERS/ON-CHECK-DELETE-MASTER | detail rows exist, delete refused | covered | orders-delete-refused |
ORDERS/ORDERS/ON-CHECK-DELETE-MASTER | no detail rows, delete goes ahead | covered | orders-delete-empty |
ORDERS/ORDERS/ON-POPULATE-DETAILS | master record NEW or INSERT (or ORDER_ID null) - no detail query | covered | orders-add-new |
ORDERS/ORDERS/ON-POPULATE-DETAILS | detail queried | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/ORDERS/ON-POPULATE-DETAILS | cursor moved by the query, returned to the start item | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/ORDERS/ON-POPULATE-DETAILS | cursor still on the start item after the query | unreachable | The relation is not deferred, so QUERY_MASTER_DETAILS always does GO_BLOCK('ORDER_ITEMS') and the cursor always leaves the master item it started on. |
ORDERS/unit:QUERY_MASTER_DETAILS | relation not deferred - GO_BLOCK and EXECUTE_QUERY | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/unit:QUERY_MASTER_DETAILS | relation deferred - SET_BLOCK_PROPERTY non-coordinated | unreachable | The only relation (ORDERS_ORDER_ITEMS) is not deferred and nothing changes that at run time. |
ORDERS/unit:QUERY_MASTER_DETAILS | GO_BLOCK to the detail failed - exception handler | blocked | Needs GO_BLOCK('ORDER_ITEMS') to fail during master coordination. The block has no PRE-BLOCK, WHEN-NEW-BLOCK-INSTANCE or record-level validation that can refuse entry, and the master record was validated before coordination began; no input found that makes it fail. FRD would show it as CHECK_PACKAGE_FAILURE with FORM_SUCCESS FALSE. |
ORDERS/unit:CHECK_PACKAGE_FAILURE | previous built-in succeeded | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/unit:CHECK_PACKAGE_FAILURE | previous built-in failed - raise FORM_TRIGGER_FAILURE | blocked | Called after GO_BLOCK and GO_ITEM in the relation code. Needs one of those navigations to fail, the same condition as the QUERY_MASTER_DETAILS exception branch; no input found. |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | detail block NEW - nothing to clear | covered | orders-add-new, orders-browse, orders-delete-empty, orders-delete-refused, orders-navigate-unsaved |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | detail block queried - CLEAR_BLOCK(NO_VALIDATE), cursor returned | covered | orders-add-new, orders-browse, orders-delete-empty |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | changed block below the master found (First_Changed_Block_Below) | covered | orders-navigate-unsaved |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | changes - CLEAR_BLOCK(ASK_COMMIT), user discards or saves, detail cleared | covered | orders-navigate-unsaved |
ORDERS/unit:CLEAR_ALL_MASTER_DETAILS | changes - CLEAR_BLOCK(ASK_COMMIT), user cancels, FORM_TRIGGER_FAILURE, cursor returned | covered | orders-navigate-unsaved |
Database PL/SQL units
| Unit | Type | Blocks | Covered | Class |
|---|---|---|---|---|
| TRG_CALC_ITEM_TOTAL | TRIGGER | 1 | 1 | covered |
Waivers
Source behaviour a customer would probably not replicate, proposed for the customer's decision. Each is pinned to the exact values it covers; the counts are values waived in the final diffs. File: waivers.yaml.
Waived 0 everywhere: 10 waivers are proposed, but the migrated app implements every one of those source behaviours, so no value needs a waiver. Each stays available if the customer prefers the modern behaviour (see waivers).
| Id | Waiver | Values | Reason |
|---|---|---|---|
| W1 | ORDER_DATA saved as NULL | 0 | The source saves a new order without an order date: the empty ORDER_DATA item is inserted as NULL, so no order the form creates has a date. A target that stores the time of the order instead keeps data the source loses. Parity is implemented (the target sends every empty header item as NULL). |
| W2 | Order number consumed when the form opens | 0 | Opening ORDERS draws an order number from ORD_SEQ and shows it (16); saving draws another one and stores that (17). The number the user sees while entering the order is not the number of the saved order, and every opened form uses up a number. A target that assigns the number at save draws one value per order and shows it only after saving. Parity is implemented (the target draws a number on opening, shows it, and lets the insert draw the stored one). |
| W3 | Line ITEM_ID typed by the user | 0 | The user types the key of each order line (ITEM_ID 1, 2), and ITEM_SEQ is not used. ITEM_ID is the primary key of all order lines, not a line number within the order, so a second order whose lines are typed as 1 and 2 again fails on a duplicate key. A target that draws the key from ITEM_SEQ avoids the collision. Parity is implemented (new lines take the typed key; a key that belongs to another order is refused). |
| W4 | Two modal alerts after saving | 0 | After a save the source shows two modal alerts, each needing OK: the commit confirmation (FRM-40400, "3 records applied and saved") and then the form's own success message. A web target would normally confirm once, without a modal dialog, in the status line. Parity is implemented (the target shows the two confirmations as browser alerts, in the same order, with the same messages in the status line). |
| W5 | Key debug message on record navigation (single-table forms) | 0 | Next, Previous and Last in Categories, Customers, Employees and Products leave a message such as "Category Id Required902" on the message line: the record's key appended to a fixed text, left over from a validation trigger. It tells the user nothing (no field is required at that point) and First does not show it. A target would show no message. Parity is implemented (the same text on the same buttons). |
| W6 | Customer debug message in ORDERS | 0 | After a query in ORDERS the message line reads "Customer Id1": the first order's customer number appended to a fixed text, left over from a trigger, and it stays while the user navigates. A target would show no message. Parity is implemented. |
| W7 | Placeholder alert on leaving Employees | 0 | The Employees exit button shows a modal alert "gooooooooooooooooooooood" that must be acknowledged before the form closes: a developer's placeholder. A target would close the form directly. Parity is implemented. Without the alert the scenario's acknowledging step cannot be performed; that entry stays a defect to review with this waiver (it names no value to pin). |
| W8 | Error message when the application starts | 0 | The main menu opens with "FRM-41003: This function cannot be performed here." on the message line: a built-in started from the menu's startup trigger fails in that context. The user did nothing wrong; a target would start with an empty message line. Parity is implemented. |
| W9 | Keyboard hint of the Forms client while a question is open | 0 | While the save question (Yes/No/Cancel) is open, the Forms Java client writes its keyboard hint "Press Tab to move between choices, Return to select." to the message line. It belongs to the client's dialog handling, not to the application. A web target's dialog needs no such hint. Parity is implemented (the target writes the same hint while its question dialog is open). |
| W10 | An order added with Add cannot be saved | 0 | Add in ORDERS creates a record without an order number. ORDER_ID is required and disabled, so the user cannot enter one and saving fails every time: "FRM-40202: Field must be entered.", then "FRM-40222: Disabled item 'ORDERS.ORDER_ID' failed validation.", then the form's own failure message. Only the record the form opens on (which draws a number) can be saved. A target would number the added order and save it. Parity is implemented. A target that saves it also differs in the database rows and ORD_SEQ draws of that save; those entries are reviewed with this waiver, not covered by it. |