Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions samples/projects/FiveTech_ERP/Form1.prg
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ return nil
METHOD StartServer() CLASS TForm1

local cMsg := ""
local cArg

if ! ErpHttpStart( ::nPort, @cMsg )
::SetWinTitle( "HTTP failed: " + cMsg )
Expand All @@ -64,6 +65,19 @@ METHOD StartServer() CLASS TForm1
endif

::cUrl := "http://127.0.0.1:" + hb_ntos( ::nPort ) + "/"
// Rama PC parametrizable: ZWEB_FRONT=<montura> FiveTech_ERP.exe carga ese
// bundle dentro del WebView2 (mismo contrato que el navegador). Acepta
// tanto una carpeta con index.html (ZWEB_FRONT=web-vainilla, =portal)
// como un archivo suelto ya terminado en .html (ZWEB_FRONT=db-config.html).
// Sin la variable, comportamiento 100% idéntico al de hoy.
cArg := AllTrim( hb_GetEnv( "ZWEB_FRONT" ) )
if ! Empty( cArg )
if Lower( Right( cArg, 5 ) ) == ".html"
::cUrl += cArg
else
::cUrl += cArg + "/index.html"
endif
endif
::SetWinTitle( ::cUrl + " · meta: " + ErpMetaRoot() )
if ::oWeb != nil
::oWeb:Navigate( ::cUrl )
Expand Down
9 changes: 9 additions & 0 deletions samples/projects/FiveTech_ERP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,12 @@ sync_meta.bat
> Looking different in the window is usually the **HTML shell** (`www\index.html` is a slim client). FWH serves the full dashboard HTML from `login.prg`. Meta JSON can be identical while the UI chrome still differs.

> **Warning — generated files:** `sync_meta.bat` (run by every build) mirrors `./meta` from the FWH tree and **regenerates `www\login.html` / `www\dashboard.html`** from the TEXT blocks in `C:\fwteam\samples\DesktopWeb\login.prg` (`_extract_fwh_html.py`). Never edit `www\*.html` or `./meta` locally as the only copy — apply durable changes in the FWH source and re-sync, or they are lost on the next build.

## Data layer (`erp_db.prg`)

`meta/app.json -> "database" -> "driver"` selects where `data.*` datasets
actually live: `json` (default, `meta/data/*.json`), `dbfcdx` (local DBF/CDX
in `./data`), `openads` (remote OpenADS server), or `hdbc` (RDDHDBC over
MariaDB — opt-in, third-party, licensed separately; see
`docs/hdbc-driver.md`). `GET /api/db/status` reports the active driver and
its availability.
37 changes: 35 additions & 2 deletions samples/projects/FiveTech_ERP/build_win64.bat
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ if not exist "%HBLIB%\hbvmmt.lib" (
echo Harbour: %HBDIR%
echo Libs: %HBLIB%

REM --- Optional: HDBC (RDDHDBC over MariaDB) — third-party, licensed, opt-in.
REM Nothing here is provided by this repo. To enable, you supply BOTH:
REM 1. your own licensed hdbctools.lib in %HBLIB%
REM 2. HDBC_CONNECT_PRG = path to your own .prg implementing
REM ErpDbHdbcUserConnect() (contract documented in erp_db.prg /
REM docs/hdbc-driver.md) using your licensed HDBC package.
REM Missing either -> build is byte-identical to today (hdbc driver simply
REM unavailable; see ErpDbHdbcAvailable() / GET /api/db/status).
set "HDBC_DFLAG="
set "HDBC_OBJ="
set "HDBC_LIBS="
if exist "%HBLIB%\hdbctools.lib" if not "%HDBC_CONNECT_PRG%"=="" if exist "%HDBC_CONNECT_PRG%" (
set "HDBC_DFLAG=-dHB_WITH_HDBC"
echo HDBC: hdbctools.lib + %HDBC_CONNECT_PRG% found - hdbc driver will link
) else (
echo HDBC: not configured - hdbc driver will not link ^(see docs/hdbc-driver.md^)
)

REM --- VS x64 ---
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
if not exist "%VSWHERE%" set "VSWHERE=%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe"
Expand Down Expand Up @@ -94,11 +112,19 @@ if errorlevel 1 (
exit /b 1
)
copy /y "%PROJDIR%\erp_db.prg" "%BUILDDIR%\erp_db.prg" >nul
"%HBBIN%\harbour.exe" erp_db.prg -n -w -q -i"%HBINC%" -i"%BUILDDIR%" -i"%HBROOT%\include" -oerp_db.c
"%HBBIN%\harbour.exe" erp_db.prg -n -w -q %HDBC_DFLAG% -i"%HBINC%" -i"%BUILDDIR%" -i"%HBROOT%\include" -oerp_db.c
if errorlevel 1 (
echo HARBOUR FAILED on erp_db.prg
exit /b 1
)
if not "%HDBC_DFLAG%"=="" (
copy /y "%HDBC_CONNECT_PRG%" "%BUILDDIR%\erp_db_hdbc_connect.prg" >nul
"%HBBIN%\harbour.exe" erp_db_hdbc_connect.prg -n -w -q %HDBC_DFLAG% -i"%HBINC%" -i"%BUILDDIR%" -i"%HBROOT%\include" -oerp_db_hdbc_connect.c
if errorlevel 1 (
echo HARBOUR FAILED on HDBC_CONNECT_PRG
exit /b 1
)
)
copy /y "%PROJDIR%\erp_proc.prg" "%BUILDDIR%\erp_proc.prg" >nul
"%HBBIN%\harbour.exe" erp_proc.prg -n -w -q -i"%HBINC%" -i"%BUILDDIR%" -i"%HBROOT%\include" -oerp_proc.c
if errorlevel 1 (
Expand All @@ -121,6 +147,12 @@ cl.exe %CL_BASE% erp_http.c /Foerp_http.obj
if errorlevel 1 exit /b 1
cl.exe %CL_BASE% erp_db.c /Foerp_db.obj
if errorlevel 1 exit /b 1
if not "%HDBC_DFLAG%"=="" (
cl.exe %CL_BASE% erp_db_hdbc_connect.c /Foerp_db_hdbc_connect.obj
if errorlevel 1 exit /b 1
set "HDBC_OBJ=erp_db_hdbc_connect.obj"
set "HDBC_LIBS=hdbctools.lib %HDBC_EXTRA_LIBS%"
)
cl.exe %CL_BASE% erp_proc.c /Foerp_proc.obj
if errorlevel 1 exit /b 1
cl.exe %CL_BASE% classes.c /Foclasses.obj
Expand Down Expand Up @@ -159,6 +191,7 @@ if errorlevel 1 (
echo [4] Link FiveTech_ERP.exe (hbvmmt)
set "OBJS=main.obj erp_meta.obj erp_http.obj erp_db.obj erp_proc.obj classes.obj tcontrol.obj tform.obj tcontrols.obj hbbridge.obj hb_db_real.obj fwh_webview2.obj fte_errdlg.obj"
if exist stddlgs.obj set "OBJS=%OBJS% stddlgs.obj"
if not "%HDBC_OBJ%"=="" set "OBJS=%OBJS% %HDBC_OBJ%"

set "HBLIBS=hbvmmt.lib hbrtl.lib hbcommon.lib hblang.lib hbrdd.lib hbmacro.lib hbpp.lib hbcplr.lib hbct.lib hbhsx.lib hbsix.lib hbusrrdd.lib rddntx.lib rddnsx.lib rddcdx.lib rddfpt.lib hbcpage.lib hbpcre.lib hbzlib.lib hbdebug.lib hbsqlit3.lib sqlite3.lib gtgui.lib gtwin.lib gtwvt.lib hbmainwin.lib"
REM hbmainwin may be named differently
Expand All @@ -177,7 +210,7 @@ if exist "%HBLIB%\rddads.lib" (

set "SYSLIBS=user32.lib kernel32.lib gdi32.lib comctl32.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib advapi32.lib uuid.lib ws2_32.lib winmm.lib msimg32.lib gdiplus.lib winspool.lib dwmapi.lib iphlpapi.lib shlwapi.lib"

link.exe /NOLOGO /OUT:"%OUT%" /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT %ADSLINK% /LIBPATH:"%HBLIB%" %OBJS% %HBLIBS% %ADSLIBS% %SYSLIBS%
link.exe /NOLOGO /OUT:"%OUT%" /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT %ADSLINK% /LIBPATH:"%HBLIB%" %OBJS% %HBLIBS% %ADSLIBS% %HDBC_LIBS% %SYSLIBS%
if errorlevel 1 (
echo LINK FAILED
exit /b 1
Expand Down
149 changes: 149 additions & 0 deletions samples/projects/FiveTech_ERP/docs/hdbc-driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Optional `hdbc` data driver (RDDHDBC over MariaDB)

`erp_db.prg` already supports a pluggable data layer selected by
`meta/app.json -> "database" -> "driver"`: `json` (default), `dbfcdx`,
`openads`. This adds a fourth option, `hdbc`, backed by **RDDHDBC** — a
third-party Harbour RDD (by Manu Expósito) that maps standard xBase RDD
calls onto MariaDB.

## Heads up: this touches a generated file

The FWH shell's own built-in "Edit app → Database" panel
(`www/dashboard.html`) already has a driver dropdown + host/port/dataPath/
user/password fields (shared with `openads`, which needs the same shape) —
it just didn't list `hdbc` as an option. Added it here (`<option
value="hdbc">` + the same visibility toggle `openads` already uses). Per
this sample's own README warning, `www/dashboard.html` is normally
regenerated from FWH's `login.prg` by `sync_meta.bat` — this diff only
exists in the copy in this PR; **please mirror the same two small edits in
the FWH source** so they survive the next sync (search for
`app-db-driver` in both files).

## What this repo ships, and what it does not

This repo ships only:

- a fourth branch in `ErpDbConfig`/`ErpDbOpen`/`ErpDbStatus` in `erp_db.prg`,
compiled in only when the build defines `HB_WITH_HDBC`;
- the matching, equally opt-in wiring in `build_win64.bat`.

It does **not** ship, link, vendor, or reference any RDDHDBC/HDBC source,
binary, or API name. RDDHDBC is Manu Expósito's own commercial, licensed
product — obtaining and using it is between you and him, same as MariaDB
itself. A build of this sample that doesn't configure HDBC (the default) is
byte-for-byte what it is today; `hdbc` simply isn't a selectable driver.

## Why an RDD, not raw SQL

`erp_db.prg`'s existing drivers (`dbfcdx`, `openads`) already talk to their
backend exclusively through standard RDD verbs — `dbUseArea`, `dbGoTop`,
`dbSkip`, `FieldGet`, `dbAppend`, `RLock`, `FieldPut`, `dbDelete`,
`dbCommit`, `dbCreate` — never hand-built SQL. RDDHDBC operates at exactly
that level (`USE <table> VIA "RDDHDBC"`), so adding it is additive: no other
file changes, and `ErpDbReadRows`/`ErpDbApply`/`erp_http.prg`'s dispatch
(`if ErpDbDriver() != "json" ...`) work unmodified.

## How to enable it (you provide two things)

1. **`hdbctools.lib`** — your own licensed RDDHDBC build, placed in the same
`%HBLIB%` directory as the rest of your Harbour libs.
2. **A connector `.prg`** implementing one function:

```harbour
// your own file, NOT part of this repo
function ErpDbHdbcUserConnect( hCfg )
// hCfg = the "database" hash from app.json: driver/backend/host/port/
// dataPath (used as the DB name for this driver)/user/password.
// Build a connection with your licensed HDBC package and register it
// with the RDD via ITS OWN public entry point (documented by
// RDDHDBC, not reproduced here). Return .T. once ready, .F. to fail
// the open cleanly.
return .T.
```

Point `build_win64.bat` at it via `HDBC_CONNECT_PRG=C:\path\to\your_file.prg`.

With both present, `build_win64.bat` adds `-dHB_WITH_HDBC` to the `erp_db.prg`
compile, compiles+links your connector, and links `hdbctools.lib`. Missing
either one → the script prints an `HDBC: not configured` note and builds
exactly as before.

In practice `hdbctools.lib` alone is not enough to link a working exe — the
underlying package also ships driver-specific pieces (e.g. a MariaDB
Connector/C wrapper lib) and needs the actual MariaDB Connector/C runtime
DLL reachable at run time. List whatever `.lib` names and `/LIBPATH`s your
own package needs via `HDBC_EXTRA_LIBS`; this was confirmed end-to-end
against a real local MariaDB, see the "Verified" note below.

Then set `meta/app.json -> "database"` either directly, or from
`http://<host>:2222/db-config.html` (any admin session) — a small page that
reads the current config via `GET /api/meta?key=app`, lets you edit
driver/host/port/dataPath/user/password, and saves it back with
`POST /api/meta` (same admin-only write path the runtime form designer
already uses). Reachable from a browser, from the web branch's `/portal/`
(companion PR #24), or, on the "PC" branch, by starting the exe with
`ZWEB_FRONT=db-config.html` — `Form1.prg`'s `ZWEB_FRONT` (see PR #24)
already accepts a bare `.html` file, not just a bundle folder, so this
loads directly inside the embedded WebView2:

```json
"database": {
"driver": "hdbc",
"host": "127.0.0.1",
"port": 3306,
"dataPath": "your_db_name",
"user": "...",
"password": "..."
}
```

Check `GET /api/db/status` (also shown on `db-config.html`) — `hdbcAvailable`
reflects whether the running exe was actually built with `HB_WITH_HDBC`.

## Known limits (verify before relying on this in production)

- **Tables are not auto-created against a live connection** — by design;
`ErpDbEnsureTables()` treats `hdbc` like a SQL backend and does nothing
automatically. What this repo *does* provide is `GET /api/db/schema-sql`
(any authenticated session), which returns `CREATE TABLE IF NOT EXISTS`
DDL for every `data.*` dataset — inferred from `meta/data/*.json` with the
same logic `ErpDbInferSchema()` already uses for `dbfcdx`, plus the
`_h_rowid_`/`deleted_at` bookkeeping columns RDDHDBC tables need. It only
ever generates text (`ErpDbHdbcSchemaSql()` in `erp_db.prg`, no HDBC
dependency at all) — review it, then run it yourself, e.g.
`curl -b cookies.txt http://127.0.0.1:2222/api/db/schema-sql > schema.sql && mysql -u ... < schema.sql`.
RDDHDBC's own index-metadata table is **not** included — provision it per
RDDHDBC's docs. Calling this also writes each dataset's `<name>.map.json`
next to `./data` if it doesn't exist yet (same file `dbfcdx` relies on) —
`ErpDbReadRows`/`ErpDbApply` need it to know the column<->field mapping
once you actually switch `driver` to `"hdbc"`.
- **Record locking (`RLock`) is process-local**, not database-wide — it
correctly serializes the concurrent threads of a *single*
`FiveTech_ERP.exe`, the model this sample already uses, but does not
protect against a second, separate process writing the same rows.
- Large tables: this integration does not configure or assume any
windowed/paged fetch mode RDDHDBC may offer — validate with your own
data volumes.
- Update this file (not `erp_db.prg`'s comments) if any of the above
changes after you test against your own RDDHDBC version.

## Verified end-to-end (own local build, own local database)

This driver was built and exercised against a real MariaDB instance (own
licensed RDDHDBC package, not part of this repo) before opening this PR:
`GET /api/db/status` reporting `hdbcAvailable:true`; `GET /api/dataset`
returning real rows (UTF-8, accents intact) sourced from MariaDB, not the
JSON files; `POST /api/dataset` add/update/delete all round-tripped
correctly (verified independently with direct SQL). Two real bugs were
found and fixed in that process, both already reflected above:

1. The RDD's actual registered name is `"RDDHDBC"`, not `"HDBC"` — used in
the `dbUseArea()` call. There is also no standalone `REQUEST` target for
it: your `ErpDbHdbcUserConnect()` calling any of the RDD's own public
functions (as the contract requires) already force-links its
registration code, since both live in the same compiled module.
2. `ErpDbApply()`'s "delete not persisted" re-check (`Deleted()` right after
`dbDelete()`) is an OpenADS-specific workaround that produced false
negatives on hdbc — deletes were persisting correctly (`deleted_at` set)
even though `Deleted()` on the just-modified record read back `.F.`
before the next fetch. Now scoped to `driver == "openads"` only.
Loading
Loading