![]() |
![]() |
![]() |
![]() |
A lightweight sales & order management system built on top of the Oracle OE sample schema, with:
-
FastAPI backend (Python)
-
React frontend (Vite + Axios)
-
Oracle Database (OE.ORDERS + OE.ORDER_ITEMS + OE.PRODUCT_INFORMATION)
-
Dockerized for easy deployment on any machine
- Fetch product list from
OE.PRODUCT_INFORMATION - Optional localized product names via
OE.PRODUCT_DESCRIPTIONS- Language is controlled by
PRODUCT_LANGenv var or?lang=query parameter
- Language is controlled by
- Display product name and list price in the UI
- Create a new order in
OE.ORDERS - Automatically insert the corresponding line item into
OE.ORDER_ITEMS - Each order line contains:
PRODUCT_ID,UNIT_PRICE,QUANTITY,LINE_ITEM_ID
- Order summary is shown before submission on the frontend
- View the latest 50 orders (newest on top)
- Each order row shows:
- Order ID, order date, customer id, status, total amount
- A short items preview like:
Headphones x1; Cable x2 (+1 satΔ±r) - Items count (number of
ORDER_ITEMSlines)
- Click Detay / Gizle to expand an order and see a nested table with full line items:
- Product name, unit price, quantity, line total
- Auto-refresh toggle (on/off) for the orders list (polling every 5s)
- Oracle connection pooling with
oracledb:- Pool created at app startup
- Shared across all requests
- Efficient queries:
/ordersendpoint fetches last 50 orders- Then loads all their items in a single query using a dynamic
INclause
- Language-aware product names:
PRODUCT_LANGenv var for default language (e.g.TR,US)langquery parameter overrides per-request if needed
- Frontend & backend run in containers
- Configurable via
.env.docker - Only dependency: an accessible Oracle database with the OE schema
-
Python 3 + FastAPI
-
oracledb(thin mode) -
Uvicorn (ASGI server)
-
Connection pooling to Oracle
-
React (Vite)
-
Axios for API calls
-
Custom responsive CSS
- Oracle 19c+
- OE sample schema:
OE.PRODUCT_INFORMATIONOE.PRODUCT_DESCRIPTIONS(for translations, optional)OE.ORDERSOE.ORDER_ITEMS
-
Docker & Docker Compose
-
Environment-based configuration via
.env.docker
project-root/
β
ββ backend/ # FastAPI backend
β ββ main.py
β ββ requirements.txt
β ββ Dockerfile
β ββ .dockerignore
β
ββ client/ # React frontend
β ββ src/App.jsx
β ββ Dockerfile
β ββ vite.config.js
β ββ .dockerignore
β
ββ docker-compose.yml # Multi-container orchestration
ββ .env.docker # Oracle DB connection config
ββ README.md
The Sales Pulse app itself only writes to OE.ORDERS and OE.ORDER_ITEMS.
However, in many Oracle demo setups you may have a separate ETL/PL/SQL job or trigger that copies OE orders into SH.SALES for reporting (e.g., SH schema dashboards).
If you (or someone else) implement such a process, you must ensure:
-
SH.TIMES covers the dates you insert
- SH.SALES is partitioned by
TIME_ID. - Any
TIME_IDyou use in SH.SALES must exist in SH.TIMES. - Otherwise, inserts may fail or partitions may not match.
- SH.SALES is partitioned by
-
SH.SALES has partitions for those dates
- If you insert data for years not covered by existing partitions, you will hit:
ORA-14400: inserted partition key does not map to any partition
You can generate one row per day for 2025 with a PL/SQL block like this (adapt the year as needed):
BEGIN
FOR v_date IN 0 .. 364 LOOP
DECLARE
d DATE := TO_DATE('01-01-2025', 'DD-MM-YYYY') + v_date;
BEGIN
INSERT INTO SH.TIMES (
TIME_ID, DAY_NAME, DAY_NUMBER_IN_WEEK, DAY_NUMBER_IN_MONTH,
CALENDAR_WEEK_NUMBER, FISCAL_WEEK_NUMBER, WEEK_ENDING_DAY, WEEK_ENDING_DAY_ID,
CALENDAR_MONTH_NUMBER, FISCAL_MONTH_NUMBER, CALENDAR_MONTH_DESC, CALENDAR_MONTH_ID,
FISCAL_MONTH_DESC, FISCAL_MONTH_ID, DAYS_IN_CAL_MONTH, DAYS_IN_FIS_MONTH,
END_OF_CAL_MONTH, END_OF_FIS_MONTH, CALENDAR_MONTH_NAME, FISCAL_MONTH_NAME,
CALENDAR_QUARTER_DESC, CALENDAR_QUARTER_ID, FISCAL_QUARTER_DESC, FISCAL_QUARTER_ID,
DAYS_IN_CAL_QUARTER, DAYS_IN_FIS_QUARTER, END_OF_CAL_QUARTER, END_OF_FIS_QUARTER,
CALENDAR_QUARTER_NUMBER, FISCAL_QUARTER_NUMBER, CALENDAR_YEAR, CALENDAR_YEAR_ID,
FISCAL_YEAR, FISCAL_YEAR_ID, DAYS_IN_CAL_YEAR, DAYS_IN_FIS_YEAR,
END_OF_CAL_YEAR, END_OF_FIS_YEAR
)
VALUES (
d,
TO_CHAR(d, 'Day'),
TO_NUMBER(TO_CHAR(d, 'D')),
TO_NUMBER(TO_CHAR(d, 'DD')),
TO_NUMBER(TO_CHAR(d, 'WW')),
TO_NUMBER(TO_CHAR(d, 'WW')),
NEXT_DAY(d, 'SATURDAY'),
TO_NUMBER(TO_CHAR(NEXT_DAY(d, 'SATURDAY'), 'DDD')),
TO_NUMBER(TO_CHAR(d, 'MM')),
TO_NUMBER(TO_CHAR(d, 'MM')),
TO_CHAR(d, 'Mon'),
TO_NUMBER(TO_CHAR(d, 'MM')),
TO_CHAR(d, 'Mon'),
TO_NUMBER(TO_CHAR(d, 'MM')),
TO_NUMBER(TO_CHAR(LAST_DAY(d), 'DD')),
TO_NUMBER(TO_CHAR(LAST_DAY(d), 'DD')),
LAST_DAY(d),
LAST_DAY(d),
TO_CHAR(d, 'Month'),
TO_CHAR(d, 'Month'),
'Q' || TO_CHAR(d, 'Q'),
TO_NUMBER(TO_CHAR(d, 'Q')),
'Q' || TO_CHAR(d, 'Q'),
TO_NUMBER(TO_CHAR(d, 'Q')),
90, 90,
ADD_MONTHS(TRUNC(d, 'Q'), 3) - 1,
ADD_MONTHS(TRUNC(d, 'Q'), 3) - 1,
TO_NUMBER(TO_CHAR(d, 'Q')),
TO_NUMBER(TO_CHAR(d, 'Q')),
2025,
2025,
2025,
2025,
365, 365,
TO_DATE('31-12-2025', 'DD-MM-YYYY'),
TO_DATE('31-12-2025', 'DD-MM-YYYY')
);
END;
END LOOP;
COMMIT;
END;How does it work?
-
Inserts one row for each day of 2025 (01-01-2025 β 31-12-2025).
-
Sets logical values for all required columns:
-
Day, month, and quarter information are calculated automatically.
-
WEEK_ENDING_DAY = Saturday
-
DAYS_IN_CAL_YEAR = 365
-
Quarter and month names are calculated with TO_CHAR().
![]() |
Adding new partitions. (Adding a βcatch-allβ future partition to SH.SALES)
- If you do not want to manage yearly partitions manually, you can add a MAXVALUE partition so that all future dates are accepted:
- Opening partitions means updating the SH.SALES table to accept new dates. This is because the SH.SALES table is a partitioned table and only accepts data for specific dates via the TIME_ID column.
- If a maxvalue partition (infinite date) is added to the table, we don't have to bother with it again:
ALTER TABLE SH.SALES
ADD PARTITION SALES_FUTURE VALUES LESS THAN (MAXVALUE)
TABLESPACE USERS;This allows TIME_ID values for 2026, 2030, 2050, etc. without further partition changes.
![]() |
The backend reads the following environment variables:
ORACLE_DSN=192.168.1.182:1521/testdb
ORACLE_USER=superset
ORACLE_PASSWORD=superset123
# Optional: default language for PRODUCT_DESCRIPTIONS.LANGUAGE_ID
PRODUCT_LANG=TRPRODUCT_LANGcontrols which language row is used inOE.PRODUCT_DESCRIPTIONS.- You can also override it per request using
?lang=TRor?lang=USon the endpoints
(e.g./products?lang=TR,/orders?lang=US).
For the application to work end-to-end, follow these required setup steps in order:
- You need an Oracle DB instance running with the OE sample schema.
- If not available, install Oracle 19c+ and load the sample schemas (including OE).
- Ensure the following tables exist and are accessible:
OE.PRODUCT_INFORMATIONOE.PRODUCT_DESCRIPTIONS(optional but recommended for translated names)OE.ORDERSOE.ORDER_ITEMS
Oracle should be accessible via IP & port, e.g.:
DSN = 192.168.1.182:1521/testdbThe user you connect with (e.g. superset) must have:
SELECTon the tables listed aboveINSERTonOE.ORDERSandOE.ORDER_ITEMS
Create .env.docker in the project root and set your own values:
# .env.docker
ORACLE_DSN=YOUR_IP:1521/YOURDB
ORACLE_USER=YOUR_USER
ORACLE_PASSWORD=YOUR_PASSWORD
# Default language for OE.PRODUCT_DESCRIPTIONS (e.g. TR, US)
PRODUCT_LANG=TRTip: You can test the connection with a basic Oracle client like SQL Developer or sqlplus.
If youβre using a local Oracle VM or container, ensure itβs running before launching the app.
git clone https://github.com/yourusername/sales-pulse.git
cd sales-pulseORACLE_DSN=YOUR_IP:1521/YOURDB
ORACLE_USER=YOUR_USER
ORACLE_PASSWORD=YOUR_PASSWORD
PRODUCT_LANG=TRdocker-compose up --buildThis will:
- Start the FastAPI backend (port 8000 inside the container, or mapped port such as 8001)
- Start the React frontend (port 5173)
- Connect both to Oracle and serve the app
Typical local ports:
- Backend β
http://localhost:8000(orhttp://localhost:8001depending on your compose mapping) - Frontend β
http://localhost:5173(or mapped port)
After the first build:
# Rebuild only if you changed code
docker compose build client
docker compose build server
docker compose up -d
docker compose build client: rebuilds the React frontend
docker compose build server: rebuilds the FastAPI backend
Tip:docker compose up --build -dwill also rebuild whatβs needed automatically.
docker-compose downIf not using Docker:
cd backend
python -m venv venv
# Linux/macOS
source venv/bin/activate
# Windows
# venv\Scripts\activate
pip install -r requirements.txt
uvicorn main:app --reload --port 8000cd client
npm install
npm run dev- The frontend uses
VITE_API_URLto talk to the backend Example:
VITE_API_URL=http://localhost:8000-
Frontend (React)
- Calls backend APIs using
VITE_API_URL(configured in.env/ environment). - Provides two main tabs:
- Yeni SipariΕ β create a new order
- SipariΕler β view the latest 50 orders
- Shows:
- Product dropdown with translated names (if available) and prices
- Quantity input
- Live order summary (product name, unit price, total)
- Auto-refreshing orders table with βDetayβ / βGizleβ to see line items
- Calls backend APIs using
-
Backend (FastAPI)
- Connects to Oracle using a connection pool (
oracledb.create_pool). - Exposes endpoints:
GET /pingβ health checkGET /products?lang=...β products fromOE.PRODUCT_INFORMATIONjoined withOE.PRODUCT_DESCRIPTIONS(optional translation)GET /orders?lang=...β last 50 rows fromOE.ORDERSplusitems_count&items_previewfor each orderGET /order-items?order_id=...&lang=...β details fromOE.ORDER_ITEMSjoined with product namesPOST /add-orderβ inserts into:OE.ORDERS(order header)OE.ORDER_ITEMS(single line item with price * quantity)
- Uses
PRODUCT_LANGenv var as default language;langquery parameter overrides per request.
- Connects to Oracle using a connection pool (
-
Database (Oracle)
- Uses the OE sample schema as the main source of truth:
OE.PRODUCT_INFORMATIONβ base product data and list pricesOE.PRODUCT_DESCRIPTIONSβ translated product names (perLANGUAGE_ID)OE.ORDERSβ order headersOE.ORDER_ITEMSβ order line items
- No direct dependency on
SH.SALES/SH.TIMESin the core app. - Optionally, you can build additional ETL/PL/SQL jobs to push OE orders into
SH.SALESfor data warehouse/reporting scenarios.
- Uses the OE sample schema as the main source of truth:
In the superset, we select our Oracle database, OE schema, and ORDERS table and create a chart. Here, we examine the number of sales by year.
![]() |
![]() |
Next, we set the update interval for our chart. In the example, we chose 1 minute. By updating the chart every minute, we can monitor the latest sales numbers.
![]() |
![]() |
As can be seen, while the total number of sales in 2025 was 5.46 thousand, the table was updated to show us that new sales were made, and the number of sales in 2025 increased to 19.4 thousand.
![]() |
![]() |










