App-Link #1044 Lager -- als Artikel mit Stückliste verwalten

Einen Artikel kann man
- herstellen
- einkaufen
- verkaufen
- vermieten
- warten
- ...

( Was das ERP eben hergibt )




z.B.

--> /frepjs2000/Fr_Artikel_Pflege?submitaction=%2F%3D%2F&datatable_primarykey=129


/public/Lager_als_Artikel_Stueckliste_verwalten.png

Pro Artikelgruppe können individuelle Felder ohne Datenbank/Programm-Änderung definiert werden

--> s.a.  https://jobst-software.net/ainfojs2000/InfoPrint_Default?only_REPORT_KNZ=TxtDoc&search_text=ADialog
- 1 -    (TxtDoc-artikel2026-06-24.html)


App-Link #1018 Zeit -- per Desktop Warten und Auswerten -- {1}

Office-Funktionen
- Pivot (Kreuztabellen)
- Charts (Diagramme)
- Seriendokumente (Etiketten, Mail, ...)
- Import / Export
- Abfragen / Reports

Als Datenquelle stehen die Tabellen, Abfragen (Views) und Funktionen der SQL zur Verfügung.
Die Berechtigung ergibt sich aus der Anmeldung und der damit verbundenen Rolle.

z.B.

/public/js_es6_minierp_beta.png


--> /ahr_cal/?submitaction=%2F%3D%2F&datatable_primarykey=12

ev. weitere Funktionen

- Anfrage
- Angebot
- Lieferschein
- Rechnung
- Mahnung


Eingabe
-------
- ein Mastersatz für alle o.g. Funktionen
- Positionen (Artikel, Menge, Preis, Rabatt)


Bericht: Z.B. als Text- oder Tabellendokument
Status: Wenn ein Angebots-, Lieferschein-, ... Datum eingegeben wurde, dann ist das der Status (also z.B. geliefert, wenn Lieferdatum gepflegt)
Dateien: Die Datensätze Anfrage/Auftrag, Artikel, Adresse haben jeweils ein autom. angelegtes Datensatz-Verzeichnis
Archiv: Dokumente sind als .eml, .pdf, ... unter o.g. Datensatz-Verzeichnis zu speichern
- 2 -    (TxtDoc-artikel2026-06-24.html)


ADialog_Adr #1069 I

<dialog id="dialog_adr_details">
<form> <input name="json_input_ele_no" readonly type="hidden" />
<label>Quelle</label> <input name="Quelle" />
<label>Betreuer</label> <input name="Betreuer" list="Betreuer_LIST" />
<hr />
<label>Produktgruppe</label> <input name="Produktgruppe" list="Produktgruppe_LIST" />
<hr />
<label>Wiedervorlage</label> <input name="Wiedervorlage" type="date" />
<hr />
<label>Bemerkung</label> <input name="Bemerkung" />
</form>

<button name="btnOK" >OK</button>
<button name="btnCancel" >abbrechen</button>
</dialog>


<datalist id="Betreuer_LIST">
$DATA_OPTIONS.ADRESSNR_LIST.ADR_KNZ.ADRESSNR.ADRESS_TEXT.ADRESSNR
</datalist>

<datalist id="Produktgruppe_LIST">
$DATA_OPTIONS.ARTIKELGRUPPE_ADIALOG_LIST.ARTIKELGRUPPE_OG.ARTIKELGRUPPE.ARTIKELGRUPPE_TEXT
</datalist>



<dialog id="dialog_adr_ap_details">
<form> <input name="json_input_ele_no" readonly type="hidden" />
<label>Prio</label> <input name="Prio" list="ADR_PRIO_LIST" />
</form>

<button name="btnOK" >OK</button>
<button name="btnCancel" >abbrechen</button>
</dialog>

<datalist id="ADR_PRIO_LIST">
$DATA_OPTIONS.OK_NOK_ANSWER_LIST.OK_NOK_ANSWER_OG.OK_NOK_ANSWER.OK_NOK_ANSWER_BEZEICHNUNG
</datalist>

- 3 -    (TxtDoc-artikel2026-06-24.html)


AReport #1066 Stueckliste

{
"SELECT_FDNAMES": "artikelnr, p_artikels, t_artikelnr, t_menge, t_mehs, t1_x_menge, t1_x_menge_dets",
"FROM_SCHEMA": "frepjs2000",
"FROM_QRY": "Artikel_AParts_t1_x_t_artikelnr_sum_qry"
}

- 4 -    (TxtDoc-artikel2026-06-24.html)


ADialog_Artikel #1050 Anl.

<dialog id="dialog_artikel_details">
<form> <input name="json_input_ele_no" readonly type="hidden" />
<label>Hersteller</label> <input name="Hersteller" />
<label>Lieferant</label> <input name="Lieferant" /> <hr />

<label>Prospekt</label> <input name="Prospekt" />
<label>Dokumentation</label> <input name="Dokumentation" /> <hr />


<label>Kapazitaet</label> <input name="Kapazitaet" /> <hr />


<label>Serien_Nr</label> <input name="Serien_Nr" />
<label>Anl_Nr</label> <input name="Anl_Nr" /> <hr />

<label>Zugang</label> <input name="Zugang" type="date" />
<label>Abgang</label> <input name="Abgang" type="date" /> <hr />


<label>Kostenstelle</label> <input name="Kostenstelle" list="Kostenstelle_LIST" title="lt. Kunden / Lieferanten ... - Stamm" /> <hr />

<label>Verbrauchsmaterial</label> <input name="Verbrauchsmaterial" />
<label>Reinigung</label> <input name="Reinigung" />
<label>Wartung</label> <input name="Wartung" />
<label>Entsorgung</label> <input name="Entsorgung" /> <hr />


<label>Betreuer</label> <input name="Betreuer" /> <hr />


</form>

<button name="btnOK" >OK</button>
<button name="btnCancel" >abbrechen</button>
</dialog>

<datalist id="Kostenstelle_LIST">
$DATA_OPTIONS.ADRESSNR_LIST.ADR_KNZ.ADRESSNR.ADRESS_TEXT.ADRESSNR
</datalist>

/public/Artikel_Gruppe_Anl.png


--> /frepjs2000/Fr_Artikel_Pflege?submitaction=%2F%3D%2F&datatable_primarykey=350

Wenn ein Feld fehlt,

hier anlegen.
- 5 -    (TxtDoc-artikel2026-06-24.html)


ADialog_Artikel #1048 Elektr.

<dialog id="dialog_artikel_details">
<form> <input name="json_input_ele_no" readonly type="hidden" />
<label>Hersteller</label> <input name="Hersteller" />
<hr />
<label>Prospekt</label> <input name="Prospekt" />
<hr />
<label>Dokumentation</label> <input name="Dokumentation" />
<hr />
<label>Erfahrung</label> <input name="Erfahrung" />
</form>

<button name="btnOK" >OK</button>
<button name="btnCancel" >abbrechen</button>
</dialog>

- 6 -    (TxtDoc-artikel2026-06-24.html)


ADialog_Artikel #1047 O-Bestand

<dialog id="dialog_artikel_details">
<form> <input name="json_input_ele_no" readonly type="hidden" />
<label>Standort</label> <input name="Standort" list="Standort_LIST" title="lt. Kunden / Lieferanten ... - Stamm" />
<label>Kategorie</label> <input name="Kategorie" list="Kategorie_LIST" />
<label>Bezeichnung</label> <input name="Bezeichnung" />
<hr />
<label>m2</label> <input name="m2" />
<label>m3</label> <input name="m3" />
<hr />
<label>Zustand</label> <input name="Zustand" />
<hr />
<label>EK</label> <input name="EK" type="number" />
<label>mögl. VK</label> <input name="VK" type="number" />
<hr />
<label>Stand</label> <input name="Stand" type="date" />
</form>

<button name="btnOK" >OK</button>
<button name="btnCancel" >abbrechen</button>
</dialog>

<datalist id="Standort_LIST">
$DATA_OPTIONS.ADRESSNR_LIST.ADR_KNZ.ADRESSNR.ADRESS_TEXT.ADRESSNR
</datalist>

<datalist id="Kategorie_LIST">
<option>Grundstück<option>
<option>Gebäude<option>
<option>Raum<option>
<option>Einrichtung<option>
</datalist>

--> /frepjs2000/Fr_Artikel_Pflege?submitaction=%2F%3D%2F&datatable_primarykey=29


/public/Lager_als_Artikel_Stueckliste_verwalten.png


- 7 -    (TxtDoc-artikel2026-06-24.html)


ADialog_Artikel #1046 O-Einn.

<dialog id="dialog_artikel_details">
<form> <input name="json_input_ele_no" readonly type="hidden" />
<label>Vertrag</label> <input name="Vertrag" />
<hr />
<label>mögl. VK</label> <input name="VK" type="number" />
<hr />
<label>Stand</label> <input name="Stand" type="date" />
</form>

<button name="btnOK" >OK</button>
<button name="btnCancel" >abbrechen</button>
</dialog>

--> /frepjs2000/Fr_Artikel_Pflege?submitaction=%2F%3D%2F&datatable_primarykey=22


- 8 -    (TxtDoc-artikel2026-06-24.html)


App-Link #1040 Wenn die GUI nicht mehr weiterhilft, dann ev. automatisieren

Man hat viele Daten.

Trotzdem fehlt fast immer Irgendwas.

Entweder,
- man wird ungenauer
- oder holt sich die fehlenden Teile per KI & Co



In der klassischen IT braucht man Ordnungsbegriffe,
wie IBAN, EAN, Kunden-, Artikel-, Kostenstellennummer ...

Diese Genauigkeit ist nicht immer zur Hand,
sind aber meist für eine sinnvolle Automatisierung notwendig.


Klassische Datenbanken tun sich schwer mit diesen Ungenauigkeiten,
darum kann es Sinn machen, mit LLMs Lücken zu schliessen.

Diese werden dynamischer und u.a. durch die Unterstützung von
Dokumentdatentypen der Weg einfacher.
( z.B. die Lesbarkeit von JSON schlägt sich für Mensch und Maschine(Datenbank/Browser) nicht schlecht )

/public/json_beispiel.png

SQL war/ist eine Erleichterung um Daten abzufragen.

Eine Möglichkeit umgangssprachlich formulierte Abfragen abzusetzen,
würde Benutzern noch mehr helfen.

Z.B.:
-----
Selektiere postleitzahlen von im ERP gespeicherten Orten
mit dem Ortsnamen wien

Oder:
-----
Selektiere postleitzahlen von im open-...-data.net angebotenen Orten
mit dem Ortsnamen wien
( z.B. per Linked-Server - Linked/Foreign-Tables auf Webservices )
- 9 -    (TxtDoc-artikel2026-06-24.html)


App-Link #1029 XML-Validierung vs. Webservice

Wenn die XML-Validierung "valid" ergibt
und der Server ein Dokument trotzdem nicht annimmt.

Wie kann das denn sein ?
z.B.
- die Artikelnummer, Kundennummer, ... kennt der Server nicht (mehr)


Für was auf der Client-Seite validieren
und nicht gleich den Server fragen,
ob er ein Dokument annehmen würde ?

Daher ist "well-formed" heute meist "gut genug".

/public/gjs_htmlxml.png


- 10 -    (TxtDoc-artikel2026-06-24.html)


App-Detail #1022 KORE / Logistik -- z.B. eine Palette und die Details

Details lassen sich per Kopfrechnung weniger gut überschlagen.

Dafür hat man ev. eine SQL samt Tools.




z.B.

/public/Artikel_AParts.png


--> /frepjs2000/Fr_Artikel_Pflege?submitaction=%2F%3D%2F&datatable_primarykey=127

Eine SQL kann auch helfen mit rekursiven Strukturen umzugehen.

z.B.:


WITH RECURSIVE included_parts( t_nr, apart_artikelnr, artikelnr, par_menge, t1_x_menge) AS (
SELECT 1 as t_nr, apart_artikelnr, artikelnr, 1::numeric as par_menge, menge::numeric as t1_x_menge
FROM frepjs2000.Artikel_AParts WHERE artikelnr like '%Pal%'
UNION ALL
SELECT (t_nr + 1) as t_nr, p.apart_artikelnr, p.artikelnr, (par_menge * 1)::numeric as par_menge, (p.menge * pr.t1_x_menge)::numeric as t1_x_menge
FROM included_parts pr
join frepjs2000.Artikel_AParts p on pr.apart_artikelnr = p.artikelnr
)
SELECT
string_agg( inc_pr.t_nr::varchar, ' ') as t_nrs,
--
inc_pr.artikelnr,
round( SUM( inc_pr.par_menge), 4) as menge,
string_agg( distinct art.mengen_einheit, ' ') as mengen_einheit,
--
inc_pr.apart_artikelnr as t_artikelnr,
round( SUM( inc_pr.t1_x_menge), 4) as t1_x_menge,
string_agg( distinct ap_art.mengen_einheit, ' ') as t_meh,
string_agg( concat( f_n(inc_pr.t1_x_menge), f_meh( ' ', ap_art.mengen_einheit) ), ' + ') as t1_x_menge_dets,
string_agg( concat( inc_pr.apart_artikelnr, ' ', f_n(inc_pr.t1_x_menge), f_meh( ' ', ap_art.mengen_einheit) ), ' + ') as t1_x_menge_dets_ex
FROM included_parts inc_pr
join frepjs2000.artikel art on inc_pr.artikelnr = art.artikelnr
join frepjs2000.artikel ap_art on inc_pr.apart_artikelnr = ap_art.artikelnr
GROUP BY
inc_pr.artikelnr, inc_pr.apart_artikelnr
;
- 11 -    (TxtDoc-artikel2026-06-24.html)


App-Detail #1016 JSON-Inhalte per SQL-Views nutzen

z.B. als Journal: 

create or replace view al_storage.l_storage_lagerjournal_raw_raw_LF as
select all
5 as STORAGE_ID, Posid as STORAGE_POS_ID, Posid as BUCH_NR, 'LF' as INP_KNZ, 'Abg' AS ABG_ZUG_KNZ,
'central'::character varying(50) AS LAGERORT, apos.apos_details->>'Lager'::character varying(50) AS GEGENBUCH_LAGERORT,
a.LieferDatum as BEWEGUNGS_DATUM, ARTIKELNR, ART_BEZEICHNUNG, (MENGE * -1) AS BEWEGUNGS_MENGE,
concat('ANr: ', a.ANr, ', ', 'Liefer-Adr: ', a.LieferAdressNr) as BUCHUNGSTEXT,
aPOS_details, erfasst_am::timestamp without time zone as created_at
FROM frepjs2000.AngebotePos apos join frepjs2000.Anfragen a on apos.ANr = a.ANr
where apos.apos_details->>'Lager' is not null
--
union all
--
select all
5 as STORAGE_ID, Posid as STORAGE_POS_ID, Posid as BUCH_NR, 'LF' as INP_KNZ, 'Zug' AS ABG_ZUG_KNZ,
apos.apos_details->>'Lager'::character varying(50) AS LAGERORT, 'central'::character varying(50) AS GEGENBUCH_LAGERORT,
a.LieferDatum as BEWEGUNGS_DATUM, ARTIKELNR, ART_BEZEICHNUNG, MENGE AS BEWEGUNGS_MENGE,
concat('ANr: ', a.ANr, ', ', 'Liefer-Adr: ', a.LieferAdressNr) as BUCHUNGSTEXT,
aPOS_details, erfasst_am::timestamp without time zone as created_at
FROM frepjs2000.AngebotePos apos join frepjs2000.Anfragen a on apos.ANr = a.ANr
where apos.apos_details->>'Lager' is not null
--
order by 1, 2
;

- 12 -    (TxtDoc-artikel2026-06-24.html)


App-Detail #1014 zeitliche Optimierung von Views

Das geht mit Indizes, löschen alter Daten, ...

oder Materialized Views.



z.B. in der Variante Materialized View - Table wird refreshed, wenn die Grunddaten sich ändern:
( Verwendung: db.refresh_materialized_viewsAsync(); dbTab = new SqlTab( db, ..., "al_storage.l_storage_lagerjournal_qry", ...) )



CREATE SEQUENCE al_storage.L_Storage_audit_dmlno_seq;

grant usage on al_storage.L_Storage_audit_dmlno_seq TO al_storageextuser_role, ahp_w3_extuser_role;

CREATE TABLE al_storage.L_Storage_audit
(
dml_id numeric( 14, 0) NOT NULL default 0,
--
dmlno numeric( 14, 0) NOT NULL,
dmloperation varchar(10) NOT NULL,
dmltime timestamp without time zone NOT NULL,
dmluser varchar(50) ,
dmlclient_addr varchar(50) ,
--
dml_details json,
--
CONSTRAINT L_Storage_audit_pk PRIMARY KEY ( dmlno ),
CONSTRAINT L_Storage_audit_chk_dml_id CHECK ( dml_id = 0 )
);


grant select, update on al_storage.L_Storage_audit to al_storageonlineuser_role, al_storagerole, al_storageextuser_role;


INSERT INTO al_storage.l_storage_audit( dmlno, dmloperation, dmltime ) VALUES ( 0, 'init', now() );


CREATE or replace FUNCTION al_storage.L_Storage_audit_tf() RETURNS trigger LANGUAGE 'plpgsql' AS $BODY$
DECLARE
L_Storage_audit_dmlno int;
L_Storage_audit_dml_details json;
BEGIN
IF (TG_OP = 'DELETE') THEN
SELECT to_json( OLD.*) into L_Storage_audit_dml_details;
ELSE
SELECT to_json( NEW.*) into L_Storage_audit_dml_details;
END IF;

SELECT nextval('al_storage.L_Storage_audit_dmlno_seq') into L_Storage_audit_dmlno;

update al_storage.L_Storage_audit
SET DMLNO = L_Storage_audit_dmlno, dmloperation = TG_OP, dmltime = now(),
dmluser = user, dmlclient_addr = inet_client_addr()::varchar(50), dml_details = L_Storage_audit_dml_details;

IF (TG_OP = 'DELETE') THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$BODY$;


CREATE TRIGGER L_Storage_audit_aiud AFTER INSERT OR DELETE OR UPDATE ON al_storage.L_Storage FOR EACH ROW EXECUTE FUNCTION al_storage.L_Storage_audit_tf();
CREATE TRIGGER L_StoragePos_audit_aiud AFTER INSERT OR DELETE OR UPDATE ON al_storage.L_StoragePos FOR EACH ROW EXECUTE FUNCTION al_storage.L_Storage_audit_tf();

-- --- --- ---

CREATE or replace FUNCTION al_storage.refresh_materialized_views() RETURNS integer SECURITY DEFINER AS $$
DECLARE
audit_tab_dmlno int;
lrr_mv_audit_dmlno int;
BEGIN
SELECT max(dmlno) from al_storage.L_Storage_audit into audit_tab_dmlno;

SELECT min(audit_dmlno) from al_storage.l_storage_lagerjournal_raw_raw into lrr_mv_audit_dmlno;
if audit_tab_dmlno is null or lrr_mv_audit_dmlno is null or audit_tab_dmlno <> lrr_mv_audit_dmlno then

refresh materialized view CONCURRENTLY al_storage.l_storage_lagerjournal_raw_raw;
return 1;
end if;

RETURN 0;
END;
$$ LANGUAGE plpgsql;


ALTER FUNCTION al_storage.refresh_materialized_views() OWNER TO al_storagerole;

FUNCTION al_storage.l_storage_artikelkonto_qry_refresh() TO al_storagerole WITH GRANT OPTION;

-- --- --- ---

create materialized view al_storage.l_storage_lagerjournal_raw_raw as
select all
s_audit.dmlno as audit_dmlno,
STORAGE_ID, STORAGE_POS_ID, STORAGE_POS_ID as BUCH_NR, 'L' as INP_KNZ, 'Abg' AS ABG_ZUG_KNZ,
ABG_LAGERORT AS LAGERORT, ZUG_LAGERORT AS GEGENBUCH_LAGERORT, BEWEGUNGS_DATUM, ARTIKELNR, ART_BEZEICHNUNG, (BEWEGUNGS_MENGE * -1) AS BEWEGUNGS_MENGE, BUCHUNGSTEXT,
STORAGE_POS_details, created_at
FROM
al_storage.L_Storage_audit s_audit,
al_storage.L_StoragePos stor_pos
--
union all
--
select all
s_audit.dmlno as audit_dmlno,
STORAGE_ID, STORAGE_POS_ID, STORAGE_POS_ID as BUCH_NR, 'L' as INP_KNZ, 'Zug' AS ABG_ZUG_KNZ,
ZUG_LAGERORT AS LAGERORT, ABG_LAGERORT AS GEGENBUCH_LAGERORT, BEWEGUNGS_DATUM, ARTIKELNR, ART_BEZEICHNUNG, BEWEGUNGS_MENGE, BUCHUNGSTEXT,
STORAGE_POS_details, created_at
FROM
al_storage.L_Storage_audit s_audit,
al_storage.L_StoragePos stor_pos
--
order by 1, 2
;

CREATE UNIQUE INDEX l_storage_lagerjournal_raw_raw_uidx on al_storage.l_storage_lagerjournal_raw_raw ( STORAGE_ID, STORAGE_POS_ID, ABG_ZUG_KNZ );

ALTER materialized view al_storage.l_storage_lagerjournal_raw_raw OWNER TO al_storagerole;

- 13 -    (TxtDoc-artikel2026-06-24.html)


AReport #993 Bestandsliste

{
"SELECT_FDNAMES": "vorgang_knz, adressnr, beldatum, artikelnr, lagerort, bewegungs_menge",
"FROM_SCHEMA": "al_storage",
"FROM_QRY": "l_storage_bestandsliste_qry"
}

--> https://jobst-software.net/frepjs2000/AReport?nur_AREPORT=Bestandsliste&report_search_text=%25&format=csv


- 14 -    (TxtDoc-artikel2026-06-24.html)

INTRANET