Vierasavaimet ja sovellukset näyttö

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Lisäsin puuttuvan koodin vierasavainten käsittelyyn. Sitä voi kokeilla lokkaantumalla lelusovellukseen (testi/testaus ks. etusivu) ja valitsemalla terttu sanan alta tilaukset sovelluksen ja painamalla change nappulaa. Voit kokeilla täyttelemällä asiakasnumero, asiakasnimi, tuotenumero, tuotteen nimi kenttiä. Asiakas ja tuotetietoja voit katsella asiakas ja tuote sovelluksissa.

Nyt löytyy myös sovellukset sovellus, jossa voi katsella sovellusten määrityksiä.

Lokkausnäytölle lisäsin terttu palvelimen versionumeron ja ohjelman sha256 tiivisteen.

Vielä puuttuu ainakin:

  • Lokin kirjoitus
  • Fetch lokista
  • Näytön tietojen tarkistus
  • Tietovirtojen hallinta (lelusovelluksessa tilaus, toimitus, laskutus, kirjanpito)
  • Kenttien järjestys ruudulla (nyt kentän nimen mukaan aakkosjärjestyksessä)
  • Summaus esimerkiksi myyntiraporteille (tai tapahtumien automaattinen lisäys), summaus varastosaldo/-tapahtumatyyliin, (myynnit miinusta, ostot plussaa)
  • ja varmaankin paljon muuta, ainakin muiden toimintojen tarpeita (ostotilaus, varastointi, jne.).

HTML asiakas

Sain valmiiksi uuden version käyttöliittymästä. Rutiini on aika pitkä, joten ajattelin käydä sen läpi vähän yksityiskohtaisemmin kuin edelliset koodinpätkät.

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Linkissä on muuten tämä uusi versio, toivotaan, ettei se kaatuile… Linkissä on edellisen postin sisään lokkaantuminen. Anna käyttäjätunnukseksi testi ja salasanaksi testaus.

Edit: Lisätty save nappula jatkon tekstiin ja koodiin. Lisätty loppuun otsakkeen ja rivien tulostusrutiinit.

Rutiini käy läpi seuraavat vaiheet

  • nappuloiden käsittely
  • HTML otsakkeen tulostus (HTTP/1.0, Location, Server, Date jne.)
  • terttu sanan valikon tulostus
  • ylärivin nappuloiden tulostus
  • vierasavainten käsittely (aiemmassa postissa)
  • Save nappulan rutiini
  • otsakeiden kenttien luku tertusta
  • rivien kenttien tietojen luku tertusta
  • Fetch nappulan käsittely ensimmäinen käsittely
  • Prev ja Next nappuloiden endimmäinen käsittely
  • Submit, Display, Change ja Save nappuloiden käsittely
  • Fetch, Prev ja Next nappuloiden toinen yhteinen osa
  • tulosta otsake
  • tulosta rivit
  • vapauta käytetyt muistialueet.

Koodissa on vähän enemmän kommentteja kun tavallisesti. Ohjelman viimeinen versio linkki toimii taas. Vierasavainten käsittelypostissa mainitut muutokset löytyy kappaleessa eivät ole vielä paikallaan.

void html_sovellus(char *sovellus)
{
  int comp;
  char temps[512];
  unsigned char *criteria;

  unsigned char *bonesheaderdata,*bonesheaderdatacriteria;
  unsigned char *boneslinesdata,*boneslinesdatacriteria;
  unsigned char *memberid[64];
  unsigned char timebuf[128];
  unsigned char *ucp;
  time_t now;

  struct set *setheaderfields, *sethf;
  struct set *setlinesfields, *setlf;
  struct set *setheaderdata, *sethd;
  struct set *setscreendata, *setsd;
  struct set *setlinesdata, *setld;
  struct set *setprev,*setnext;

  bonesheaderdatacriteria=NULL;
  boneslinesdatacriteria=NULL;
  setheaderfields=NULL, sethf=NULL;
  setlinesfields=NULL, setlf=NULL;
  setheaderdata=NULL, sethd=NULL;
  setscreendata=NULL, setsd=NULL,
  setlinesdata=NULL, setld=NULL;
  setprev=NULL,setnext=NULL;

  *func='\0';

  ; luetaan parametreistä func parametri
  set_get_element("func",&comp,func,htmlparams);

  if(*func=='\0') {
    strcpy(func,"");
  }

  if(!strcmp(func,"Terttu"))
    strcpy(func,"");
  ; Nappuloiden käsittely

  if(!exists_session_var("mode"))
    strcpy(mode,"Display");
  else {
    ucp=NULL;
    get_session_var("mode",&ucp);
    strcpy(mode,ucp);
  }

  ; Func kentässä on ylärivin nappulan toiminnon nimi
  ; (Fetch, Prev, Next, Display, Change, Save jne.)
  ; Mode kentän mukaan otsakkeen ja rivien tulostuksessa
  ; tulostetaan vain katselu(Display) tai muuttelu(Change)
  ; kenttiä. Lisäksi mode vaikuttaa siihen tulostetaanko
  ; rivit lohkoon ylimääräisiä rivejä rivien lisäystä
  ; varten.
  ;
  ; Suomeksi:
  ;
  ; Fetch, Prev ja Next nappulat antavat katselunäytön,
  ; muuttaaksesi tietoja voit painaa Change nappulaa.
  ;
  ; Submit, Reset ja Save eivät vaikuta näyttömuotoon,
  ; eli jos olit ennen nappulaa muuttamassa, näyttö
  ; säilyy muutosmoodissa, jos olit display moodissa,
  ; näyttö säilyy muuttelumoodissa.
  ;
  ; Display ja change nappulat vaikuttavat vain
  ; näyttötilaan (katselu, muutos).
  ;
  ; Fetch, Prev ja Next nappulat lukevat tietoja
  ; tertusta. Ja muuttavat näyttötilaksi katselutilan
  ;
  ; Save nappula kutsuu skk_save rutiinia, joka lisää
  ; nappulan lokin loppuun(nyt). Muut rutiinin
  ; tehtävät on vielä auki 
  ;
  if(!strcmp(func,"Fetch")) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Prev") ) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Next") ) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Submit")) {
    /* ei vaikuta modeen */
  } else if(!strcmp(func,"Reset")) {
  } else if(!strcmp(func,"Display") ) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Change")) {
    strcpy(mode,"Change");
  } else if(!strcmp(func,"Save")) {
    /* Save ei vaikuta näyttöön */     
  }

  if(mode==NULL) {
    strcpy(mode,"Display");
    set_session_var("mode",mode);
  } else 
    set_session_var("mode",mode);

  ; Tulostetaan html otsake. Html otsakkeessa
  ; ensimmäisessä osassa on html:n rivien lukumäärä.
  ; Tämän takia ensimmäinen ja toinen osa tulostetaan
  ; erikseen (html1 ja html2 muuttujat). html_printf
  ; löytyy aiemmasta postista.

  html=html1;
  html[0]='\0';

  html_printf("HTTP/1.0 200 OK\r\n");
  html_printf("Location: \r\n");
  html_printf("Server: %s\r\n",programname);
  now = time(NULL);
  strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

  html_printf("Date: %s\r\n",timebuf);

  html=html2;
  html[0]='\0';
  html_printf("\n<!DOCTYPE html>\r\n");
  html_printf("<html lang=\"fi\">");

  html_printf("<head>");
  html_printf("<meta charset=\"UTF-8\">");
  html_printf("<title>Terttu</title>",clientname);
  html_printf("<meta name=\"author\" content=\"Jari Kuivaniemi\">");
  html_printf("<link href=\"terttu.css\" rel=\"stylesheet\" type=\"text/css\">");
#ifdef OLD1
  html_printf("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=\"UTF-8\">");
#endif
  html_printf("</head>");
  html_printf("<body>");
  html_printf("<form action=\"%s\" method=\"post\">",sovellus);
  html_printf("<input type=\"hidden\" name=\"sessionid\" value=\"%s\">",sessionid);

  html_printf("<nav>");
  html_printf("<ul>");
  html_printf("<li>Terttu");
  html_printf("<ul>");

  ; Tulostaa terttu sanan valikon.

  html_sovellus_menu();

  html_printf("</ul></li>");

  ; Tulostaa ylärivin nappulat

  html_print_buttons();

  html_printf("</ul>");
  html_printf("</nav>");

  html_printf("<table border=\"0\">");
  html_printf("<td>");

  ; Selvittää foreign key yhteydet ja täyttää kentät.
  
  set_find_foreigns(sovellus,setscreen); /* JariK 20150920 */

  ; Save nappula suorittaa skk_save rutiinin. Lisäksi
  ; tarvitaan varmaankin tarkistuksia tiedoille (katso
  ; set_find_foreigns / tietovirrat.
  ;
  ; Aluksi Save nappi kirjoittaa tietueet lokiin, jossa
  ; sama tieto voi olla ajallisesti eri aikoina useampaan
  ; kertaan. skk_fetch:stä ilmeisesti tulee uusi versio,
  ; joka lukee lokia. Ilmeisesti viimeisin versio valitaan
  ; näytölle. Jatkossa poimintaehdosta (esim
  ; asiakasnumero=1000) voisi muodostaa tiiviste-
  ; merkkijonon, jonka nimiseen tiedostoon kaikki
  ; asiakasta tai muuta objektia koskevat tiedot
  ; laitetaan. Asiakasluettelo (kaikki tietueet,
  ; joissa on asiakasnumero kenttä) voisi olla
  ; saatavilla pelkästä 'asiakasnumero' tiivistettynä
  ; sanasta tehdystä tiedostosta.
  ;

  if(!strcmp(func,"Save")) {                                                                                                                    
    skk_save(setscreen);                                                                                                                        
  }
  ; Haetaan otsakkeen kenttien nimet ja muodostetaan
  ; otsakkeen hakuun käytettävä lause (bonesheaderdata)

  rowid=0;

  sprintf(temps,"'sovellus'=\"%s\", 'chapter'=\"header\", 'memberid', 'memberlength'",sovellus); /* Added memberlength. JariK 20140209 */

  skk_fetch2(&setheaderfields,temps,skk);

  bonesheaderdata=NULL;

  sethf=setheaderfields;
  while(sethf!=NULL) {
    set_get_element("memberid",&comp,memberid,sethf->data);
    set_add_element(&bonesheaderdata,memberid); /* Collect fieldnames to fieldsh */
    sethf=sethf->next;
  }

  ; Haetaan rivikenttien nimet ja muodostetaan rividatan
  ; hakuun tarvittava lause.
  sprintf(temps,"'sovellus'=\"%s\", 'chapter'=\"lines\", 'memberid', 'memberlength'",sovellus); /* JariK 20140127 */

  skk_fetch3(&setlinesfields,temps,skk); /* Changed to version 2. JariK 20140214 to version 3. JariK 20140303 */

  boneslinesdata=NULL;

  setlf=setlinesfields; /* sovellus, chapter="header", memberid */
  while(setlf!=NULL) {
    set_get_element("memberid",&comp,memberid,setlf->data);
    set_add_element(&boneslinesdata,memberid); /* Collect fieldnames to fieldsh */
    setlf=setlf->next;
  }

  ; Tämä on fetch nappulan ensimmäinen vaihe. Fetch
  ; nappula lukee näytön otsakkeen kenttien arvot.
  ;
  ; Arvoja käytetään fetch, prev ja next nappuloiden
  ; valikoimien otsaketietueiden valinnassa.
  ; Fetch tallettaa kenttien arvot istuntomuuttujan
  ; findmatch arvoksi.
  ;
  ; Lopuksi Fetchissä luetaan tuo otsakeketju ja
  ; palautetaan ketjun ensimmäinen otsikko sethd
  ; muuttujassa.
  if(!strcmp(func,"Fetch")) { /* JariK 20140220 */

    setscreendata=NULL;

    /* skkuser contains fields filled by user (see parameter reading)
     * fieldsh contains names of header fields.
     */
    skk_fetch_sets(&setscreendata,bonesheaderdata,setscreen);

    setsd=setscreendata;
    criteria=NULL;
    while(setsd!=NULL) {
      set_copy_filled(&criteria,setsd->data);
      setsd=setsd->next;
    }

    if(setscreendata!=NULL) /* Free also. JariK 20140221 */
      set_free(setscreendata);
    setscreendata=NULL;

    bonesheaderdatacriteria=NULL;
    set_copy_all(&bonesheaderdatacriteria,criteria);
    set_copy_all(&bonesheaderdatacriteria,bonesheaderdata);
     
    set_session_var("findmatch",bonesheaderdatacriteria);

    if(criteria!=NULL)
      free(criteria);
    criteria=NULL;

    setheaderdata=NULL;
    skk_fetch_sets(&setheaderdata,bonesheaderdatacriteria,skk->first);
    
    sethd=setheaderdata;
  }

  ; Prev ja next nappulat lukevat näytöltä tämän hetkisen
  ; otsake tietueen kentät. Seuraavaksi hartaan Fetchin
  ; saamat valintakriteerit findmatch ympäristö-
  ; muuttujasta. Sitten luetaan valintakriteerien
  ; mukaiset tiedot tertusta. Seuraavaksi 

  if((!strcmp(func,"Prev") || /* JariK 20140223 */
     !strcmp(func,"Next")) &&
     exists_session_var("findmatch")) { /* JariK 20140223 */

    skk_fetch_sets(&setscreendata,bonesheaderdata,setscreen); /* Ennen set_fetch. JariK 20150920 */

    setsd=setscreendata;
    criteria=NULL;
    while(setsd!=NULL) {
      set_copy_filled(&criteria,setsd->data);
      setsd=setsd->next;
    }

    if(setsd!=NULL)
      set_free(setsd);
    setsd=NULL;

    /* Luetaan fetchin tallettama kenttä, joka sisältää
     * otsakkeen kentät fetchin painamisen ajalta.
     * Esim selaa asiakkaan tilauksia jos asiakasnumero
     * on täytetty.
     */
    get_session_var("findmatch",&bonesheaderdatacriteria);

    setheaderdata=NULL;
    skk_fetch_sets(&setheaderdata,bonesheaderdatacriteria,skk->first);
    
    setnext=setheaderdata; /* has next row. JariK 20140226 */
    sethd=NULL; /* has new current row. JariK 20140226 */
    setprev=NULL; /* has previous row. JariK 20140226 */
    
    while(setnext!=NULL) {
      ; Kun arvot mätsäävät ruudulla prev tai next
      ; nappulan painamisen aikana olleisiin tietoihin,
      ; näytetään seuraavaksi joko tätä tietuetta edeltävä
      ; tai seuraava otsikko.
      if(!set_match2(setnext->data,criteria)) {
        if(!strcmp(func,"Prev")) {
          if(setprev!=NULL) {
            sethd=setprev;
          } else {
            sethd=setnext;
          }
          break;
        } else if(!strcmp(func,"Next")) {
          if(setnext->next!=NULL) {
            sethd=setnext->next;
          } else {
            sethd=setnext;
          }
          break;
        }
      }
      setprev=setnext;
      setnext=setnext->next;
    }
    /* Ei mätsäävää tietuetta, otetaan eka (esim fetch, reset, next) 20150929 JariK */
    if(sethd==NULL) 
      sethd=setheaderdata;
  }

  ; Submit, Change, Display ja Save nappuloilla luetaan
  ; vain ruudulla olevat tiedot uudestaan
  ; tulostettavaksi.
  if(!strcmp(func,"Submit") ||
     !strcmp(func,"Change") ||
     !strcmp(func,"Display") ||
     !strcmp(func,"Save") ) {

    setheaderdata=NULL;
    skk_fetch_sets(&setheaderdata,bonesheaderdata,setscreen);

    setlinesdata=NULL;
    skk_fetch_sets(&setlinesdata,boneslinesdata,setscreen);

    setld=setlinesdata;
  }

  if(!strcmp(func,"Fetch") ||
     !strcmp(func,"Prev") ||
     !strcmp(func,"Next")) {
    boneslinesdatacriteria=NULL;
    if(sethd!=NULL)
      set_copy_all(&boneslinesdatacriteria,sethd->data);
    set_copy_all(&boneslinesdatacriteria,boneslinesdata);

    setlinesdata=NULL;
    skk_fetch_sets(&setlinesdata,boneslinesdatacriteria,skk->first);

    setld=setlinesdata;
  }

  sethf=setheaderfields; /* sovellus, chapter="header", memberid */

  if(setheaderfields!=NULL)
    html_print_header(sethd,setheaderfields,mode); /* Ennen sethd->data */

  setlf=setlinesfields; /* sovellus, chapter="lines", memberid */
  setld=setlinesdata;

  html_print_lines(setld, setlinesfields,mode);

  html_printf("</td>");
  html_printf("</tr>");
  html_printf("</table>");

  set_debug_foreigns(sovellus,setscreen);

  html_printf("</body>");
  html_printf("</form>");
  html_printf("</html>");
  html_printf("\n\n");

  ; Vapautetaan käytössä olleet muistialueet

  if(bonesheaderdata!=NULL)
    free(bonesheaderdata);
  bonesheaderdata=NULL;

  if(bonesheaderdatacriteria!=NULL)
    free(bonesheaderdatacriteria);
  bonesheaderdatacriteria=NULL;

  if(setheaderfields!=NULL)
    set_free(setheaderfields);
  setheaderfields=NULL;

  if(setlinesfields!=NULL)
    set_free(setlinesfields);
  setlinesfields=NULL;

  if(setheaderdata!=NULL)
    set_free(setheaderdata);
  setheaderdata=NULL;

  if(setlinesdata!=NULL)
    set_free(setlinesdata);
  setlinesdata=NULL;
}

int html_print_header(struct set *set, struct set *setheaderfields,char *mode)
{
  struct set *sethf;

  html_printf("<div id=\"header\">");
  html_printf("<table border=\"0\">");
  sethf=setheaderfields;

  ; Tulostetaan kentän nimi arvopareja kaikille
  ; otsakkeen kentille.

  while(sethf!=NULL) {
    html_printf("<tr>");
    html_printf("<td>");
    html_print_fieldname(set,sethf->data,mode);
    html_printf("</td><td>");
    html_print_fieldvalue(set,sethf->data,mode);
    html_printf("</td>");
    sethf=sethf->next;
    html_printf("</tr>");
  }
  html_printf("</table>");
  html_printf("</div>");
}

int html_print_lines(struct set *setlinesdata, struct set *setlinesfields,char *mode)
{
  int comp,lines;
  char memberid[64],memberlength[10],value[128];
  struct set *setlf;
  struct set *setld;

  html_printf("<div id=\"lines\">");
  html_printf("<table border=\"0\">");
  html_printf("<tr>");

  ; Tulostetaan kentän nimet taulukon yläriville.

  setlf=setlinesfields;
  while(setlf!=NULL) {
    set_get_element("memberid",&comp,memberid,setlf->data);

    html_printf("<td>");
    html_printf("%s",memberid); /* Little bugfix JariK 31012014 */
    html_printf("</td>");
    setlf=setlf->next;
  }
  html_printf("</tr>");

  rowid++;

  lines=0;

  ; Selataan rividata läpi, ja kirjoitetaan yksi rivi
  ; joka tietueelle.

  setld=setlinesdata;
  while(setld!=NULL) {
    html_printf("<tr>");

    ; Tulostetaan riville kaikki kentät.

    setlf=setlinesfields;
    while(setlf!=NULL) {
      set_get_element("memberid",&comp,memberid,setlf->data); /* Get fieldname */
      set_get_element("memberlength",&comp,memberlength,setlf->data);
      set_get_element(memberid,&comp,value,setld->data); /* Get data */
      html_printf("<td>");
      html_printf("<input type=\"text\" value=\"%s\" name=\"%s%c%d\" size=\"%s\"  id=\"programbuttonlines\"",
                  value,memberid,rowidchar,rowid,memberlength);
      if(mode !=NULL && !strcmp(mode,"Display"))
        html_printf(" readonly");
      html_printf(">",
                  value,memberid,rowidchar,rowid,memberlength);
      html_printf("</td>");
      setlf=setlf->next;
    }
    html_printf("</tr>");

    rowid++;
    lines++;
    setld=setld->next;
  }

  ; Muuttelu tilassa tulostetaan tyhjiä rivejä, niin
  ; että kokonaisrivien määrä on 10 (esim 1 täytetty
  ; ja 9 tyhjää. Yli kymmenen rivin tapahtumissa oli
  ; ajatus että tulostetaan vain yksi ylimääräinen
  ; rivi. Kun se täytetään enter tuo uuden tyhjän rivin.
  ; sitä ei ole tässä.

  if(mode!=NULL && !strcmp(mode,"Change")) {
    while(lines<10) {
      html_printf("<tr>");
      setlf=setlinesfields;
      while(setlf!=NULL) {
        set_get_element("memberid",&comp,memberid,setlf->data); /* Get fieldname */
        set_get_element("memberlength",&comp,memberlength,setlf->data);
        html_printf("<td>");
        html_printf("<input type=\"text\" value=\"\" name=\"%s%c%d\" size=\"%s\"  id=\"programbuttonlines\">",
                    memberid,rowidchar,rowid,memberlength);
        html_printf("</td>");
        setlf=setlf->next;
      }
      html_printf("</tr>");

      rowid++;
      lines++;
    }
  }
}

int html_print_fieldname(struct set *set, char *sethf,char *mode)
{
  int comp;
  char memberid[64];

  ; Tulostetaan kentän nimi.

  set_get_element("memberid",&comp,memberid,sethf);
  html_printf("%s",memberid);
}

int html_print_fieldvalue(struct set *set, char *setf,char *mode)
{
  int comp;
  char memberid[64],memberlength[10],value[128];

  set_get_element("memberid",&comp,memberid,setf);
  set_get_element("memberlength",&comp,memberlength,setf);

  ; Tulostetaan kentän arvo. Tässä tehdään input lause
  ; syöttöluukkua varten. Lisäksi iffi näyttökentälle.

  *value='\0';
  if(set!=NULL && set->data!=NULL)
    set_get_element(memberid,&comp,value,set->data); /* Added data to header. JariK 20140216 */
  html_printf("<input type=\"text\" value=\"%s\" name=\"%s%c%d\" size=\"%s\" id=\"programbuttonheader\"",
      value,memberid,rowidchar,rowid,memberlength);
  if(mode !=NULL && !strcmp(mode,"Display"))
    html_printf(" readonly");
  html_printf("><br>");
}



Login

Haukkasin liian suuren palan edellisessä postissa, vaikka suurin osa koodista olikin jo olemassa. Aloitan uudestaan login palalla. Tästä puuttuu vielä kenttien pituuksien rajoituiksia, tämä on vasta ensimmäinen versio. Salasanat ovat vielä selväkielisiä.

Tätä samaa rutiinia käytetään käyttäjän tietojen tarkistuksessa, session id:n määrittelyssä ja login näytön tulostuksessa.

Lisäsin loppuun vielä html-parametrien käsittelyn. Se on pala, joka muuttaa html-näytön parametrit terttu muotoon. (katso userid ja password kentät logon näytöllä)

Kaikki oikeudet tietenkin pidätetään.

int get_session(char *sessionid)
{
 int c,ok,comp;
 char userid2[32],password2[32],password3[32],bones[64];
 struct set *sets,*setp,*setusers;
 time_t now;
 unsigned char timebuf[128];

 ok=0;
 *sessionid='\0';
 *userid2='\0';
 *password2='\0';
 sets=NULL;
 setusers=NULL;

 /* luetaan html datasta sessionid */
 set_get_element("sessionid",&comp,sessionid,htmlparams);

 /* Jos id löytyi, tarkistetaan istunnon olemassaolo */
 if(*sessionid!='\0') {
  if(exists_session_var("SESSION"))
    ok=1;
  else
    sessionid[0]='\0';
 }

 if(*sessionid=='\0') {
  /* luetaan näytöltä käyttäjätunnus ja käyttäjän syöttämä salasana */
  skk_fetch_sets(&sets,"'userid', 'password'",setscreen); /* ennen skk_fetch2() JariK 20150917 */

  /* Näitä pareja löytyy vain yksi (katso lopun näytön tulostus */
  if(skk_count_sets(sets)==1) {
   set_get_element("userid",&comp,userid2,sets->data);
   set_get_element("password",&comp,password2,sets->data);
  }

  if(*userid2!='\0' || password2!='\0') {
   /* Haetaan mätsäävä tunnus tertusta */
   sprintf(bones,"'userid'=\"%s\", 'password'",userid2);
   skk_fetch_sets(&setusers,bones,skk->first);

   if(skk_count_sets(setusers)==1) {
    /* Jos löytyi vaan yksi */
    set_get_element("password",&comp,password3,setusers->data);

    /* Verrataan salasanoja, jos samat,luodaan sessionid,
       talletetaan se ja userid */
    if(!strcmp(password2,password3)) {
     get_session_id(sessionid);
     set_session_var("session",sessionid);
     set_session_var("userid",userid);
     ok=1;
    }
   }
  }
 }

 /* Jos sessionidtä ei löytynyt/saatu aikaiseksi,
  * tulostetaan login näyttö käyttäjälle:
  */
 if(*sessionid=='\0') {
  html=html1;
  html[0]='\0';

  html_printf("HTTP/1.0 200 OK\r\n");
  html_printf("Location: \r\n");
  html_printf("Server: %s\r\n",programname);
  now = time(NULL);
  strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

  html_printf("Date: %s\r\n",timebuf);

  html=html2;
  html[0]='\0';
  html_printf("\n<!DOCTYPE html>\r\n");
  html_printf("<html lang=\"fi\">");

  html_printf("<head>");
  html_printf("<meta charset=\"UTF-8\">");
  html_printf("<title>Terttu</title>");
  html_printf("<meta name=\"author\" content=\"Jari Kuivaniemi\">");
  html_printf("<link href=\"terttu.css\" rel=\"stylesheet\" type=\"text/css\">");
  html_printf("</head>");
  html_printf("<body>");

  html_printf("<form action=\"logon\" method=\"post\">");
  html_printf("<input type=\"hidden\" name=\"sessionid\" value=\"%s\">",sessionid);
  html_printf("<table border=\"0\">");
  html_printf("<tr>");
  html_printf("Testiohjelmaan lokkaantuminen Userid=testi, password=testaus");
  html_printf("</tr>");
  html_printf("<tr>");
  html_printf("</tr>");
  html_printf("<td>");
  html_printf("Userid");
  html_printf("</td>");
  html_printf("<td>");

  html_printf("<input type=\"char\" name=\"userid-0\" value=\"%s\">",userid2);
  html_printf("</td>");
  html_printf("</tr>");

  html_printf("<tr>");
  html_printf("<td>");
  html_printf("Password");
  html_printf("</td>");
  html_printf("<td>");
  html_printf("<input type=\"password\" name=\"password-0\" value=\"%s\">",password2);
  html_printf("</td>");

  html_printf("<tr>");
  html_printf("<input type=\"submit\" value=\"Submit\">");
  html_printf("</tr>");

  html_printf("</table>");
  html_printf("</form>");

  html_printf("<br>");
  html_printf("Tämä on kesken olevan projektin \"lelusovellus\", eikä se ole tarkoitettu tuotteiden tilaamiseen.<br>");
  html_printf("<br>");
  html_printf("Saat listan toiminnoista leijumalla Terttu -sanan päällä.<br>");
  html_printf("<br>");
  html_printf("Raportti sovelluksen rakentamisesta löytyy osoitteesta moijari.com.<br>");
  html_printf("<br>");
  html_printf("<br>");
  html_printf("</body>");
 }
 return(ok);
}

html_get_screen_params(struct set **set)
{
 int id,first;
 char *p,*q;
 unsigned char name[64],value[64];
 struct set *setu;

 /* hae html parametrien sijaintiosoite */
 p=html_get_param2_addr();

 while(*p!='\0') {
  /* Hae parametrin nimi ja arvo */
  html_get_param2(&p,name,sizeof(name)-1,value,sizeof(value)-1);

  /* osoite parametrin nimen loppuun */
  q=name+strlen(name)-1;
  id=0;

  /* Haetaan parametrin nimen lopussa olevan numeron alku */
  if(isdigit(*q)) {
   while(isdigit(*(q-1))) {
   q--;
  }

  /* rivinumeron lisäksi parametrin nimen lopussa pitää olla '-'.
   * esimerkiksi 'userid-0'.
   */
  if(*(q-1)==rowidchar)
   *(q-1)='\0';
   /* rivin numero inttimuuttujaan */
   id=atoi(q);
  } else {
   id=-1;
  }

  if(id>=0) {
   /* Talletetaan kenttä oikealle riville. */
   set_add_element_id(set,id,name,value);
  }
  /* Ohitetaan parametrien välissä oleva '&' merkki. */
  if(*p=='&') {
   p++;
  }
 }
}

Html (asiakas) liittymä

Seuraavaksi pitäisi kirjoittaa käyttäjän html-liittymä uudestaan. Olen “haaveillut” tästä jo joulukuun puolesta välistä, koodin pitäisi syntyä, mutta katsotaan.

Ensimmäisessä istunnossa en vielä saanut aikaan kuin suurinpiirteisen vaiheluettelon. Jos et halua lukea puolivalmisteesta, voit odottaa perjantaihin. Toisaalta tämä koko projekti on luonteeltaan puolivalmiste, joten… Jatkossa vielä kesken oleva versio. jatkan joko tätä pohdintaa tai koodausta viimeistään perjantaina (olen allokoinut jatkon sinne).

Kaikki oikeudet tietenkin pidätetään.

Liittymä sisältää koko html toiminnon alusta loppuun, eikä se tallenna muuta kuin istuntomuuttujia (session variables) ja mahdollisesti save toiminnolla tallennettavan datan. Liittymä hakee tietoja tertusta. (ks. kyselykieli)

Liittymä voidaan jakaa seuraaviin toimintoihin (steps).

Tämä html-liittymä on siis ns yleisellä tasolla, eli kaikki asiakasohjelman kentät on määritelty terttu kantaan, eli ideassa asiakas määrittelee kentät itse. Samaa html liittymää toiveen mukaan käytetään kaikissa syöttöruuduissa.

Lue istuntoavain parametrista (sessionid)

Lue sovelluksen nimi html parametrista

Lue sovelluksen otsakekentät tertusta

Lue sovelluksen rivikentät tertusta

Lue edellinen syöttöruutu parametreista

Lue mode muuttujan tila istuntokohtaisista muuttujista

Tee vierasavain haut (tietovirta haut). ks tietovirta

Hae sovelluksen osasovellukset

Tulosta terttu valikko osasovelluksista

Tulosta nappulat

Ruudulla on tällä hetkellä nappulat: Submit, Reset, Fetch, Prev, Next, Save, Display, Change, Delete.

Submit

Submit nappula vastaa syöttölomakkeen perinteistä enter toimintoa, eli se lähettää lomakkeen tiedot palvelimelle, suorittaa kaikki nämä palvelimen toiminnot, eli tämän ketjun jota olemme tekemässä. Eli enterillä täytetään myös vierasavainkentät.

Reset

Fetch

Fetch toiminnolla haetaan otsakkeelle kirjoitetuilla hakutekijöillä ketju mätsääviä otsakkeita, ja valitaan ensimmäinen syöttökenttiin. Fetchin hakukriteerit (otsake) talletetaan session muuttujaan preville ja nextille.

Prev

Prev näppäimellä haetaan seuraava tietue Fetch toiminnon ketjusta, Ilmeisesti tuo fetch ketju haetaan uudestaan, ja haetaan seuraava hakemalla ensin tämä tietue, ja sitä edellinen valitaan.

Next

Nextillä taas haetaan seuraava tietue Fetch toiminnon ketjusta, Kuten edellisessä fetchin ketjusta haetaan tämä tietue ja valitaan uudeksi näytölle tulevaksi tietueeksi seuraava.

Tulosta html syöttöruutu

Lähde tietovirta

Kaikki oikeudet tietenkin pidätetään.

Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Olen välillä miettinyt ohjelman sorsien julkaisua, ja ajattelinkin taas julkaista pienen pätkän, jonka kirjoitin sunnuntaina. Se tekee vierasavainten käsittelyn. Esimerkiksi tilauksessa on tietoja jotka haetaan toisista sovelluksista esimerkiksi asiakas ja tuote. Ohjelman pätkä päättelee mitkä kentät pitää hakea ja sijoittaa ne sovelluksen kenttiin. Löytyi (mätsää tasan yhtä tietuetta) rutiinista puuttuu vielä jotain..

Tätä ei voi vielä kokeilla lelusovelluksessa, sen html osuus kaipaa pikku muutoksia. Seuraavana ovat nuo html osuudet ja sitten output tietovirrat.

Huomasin sisennyksiä tehdessäni, että ohjelmassa on ainakin yksi bugia, voit harjoitukseksi etsiä sen/ne. En alkanut korjata niitä lennossa, se ei kuitenkaan olisi mennyt oikein.

Ihan lopussa vielä nuo löytyi fprintäffät:

/* (C)1998-2014 Jari Kuivaniemi, All rights reserved! */
if(setuser!=NULL) { /* Näytöllä kenttiä */
  char kentta[64],kentta2[64],kentta3[64],kentta4[64];
  char savekentta2[64],savekentta3[64],savekentta4[64];
  char value[128];
  int c,longest;
  char *bones,*bones2;
  struct set *setinputdata,*sid;

  setallfields=NULL;

  /* Haetaan kaikki tertussa olevat kentät */
  skk_fetch2(&setallfields,"'memberid'",skk);
  sprintf(temps,"'sovellus', 'fromsovellus', 'tosovellus'=\"%s\"",sovellus);
  skk_fetch2(&setflow,temps,skk); /* Haetaan kaikki tätä sovellusta koskevat virrat */
  setf=setflow;

  while(setf!=NULL) { /* Selataan kaikki virrat */

 /* Haetaan sovelluksen nimi, lähdesovellus ja kohdesovellus */

    set_get_element("sovellus",&comp,sovellusname,setf->data);
    set_get_element("fromsovellus",&comp,fromsovellusname,setf->data);
    set_get_element("tosovellus",&comp,tosovellusname,setf->data);

    /* Jos tämä sovellus on kohdesovelluksena (tämän input) */
    if(!strcmp(tosovellusname,sovellus)) {
      /* Haetaan lähdesovelluksen kentät */
      sprintf(temps,"'sovellus'=\"%s\", 'memberid'",fromsovellusname);
      skk_fetch2(&setfromfields,temps,skk);

      /* Haetaan lähdesovelluksen kentät */
      sprintf(temps,"'sovellus'=\"%s\", 'memberid'",tosovellusname);
      skk_fetch2(&settofields,temps,skk);

      setff=setfromfields;
      bones=NULL;
      first=0;
      while(setff!=NULL) { /* Käydään läpi from kentät */
        /* Haetaan kentän nimi */
        set_get_element("memberid",&comp,kentta2,setff->data);
        longest=0; /* Haetaan pisintä yhteistä osaa, joka on tertun kentissä */
        savekentta2[0]='\0';
        savekentta3[0]='\0';
        settf=settofields;
        while(settf!=NULL) { /* Haetaan läpi to kentät */
          set_get_element("memberid",&comp,kentta3,settf->data);
          setaf=setallfields;
          while(setaf!=NULL) { /* Käydään läpi kaikki kentät */
            set_get_element("memberid",&comp,kentta4,setaf->data);
            /* Jos to kenttä ja from kenttä mätsäävät samaan tertun
             * kenttään, etsitään pisin mätsäävä.
             */
            if( (strstr(kentta2,kentta4)!=NULL) && /* from */
              (strstr(kentta3,kentta4)!=NULL) ) { /* to field */
              if((c=strlen(kentta3))>longest) {
                longest=c;
                strcpy(savekentta3,kentta3);
                strcpy(savekentta4,kentta4);
              }
            }
            setaf=setaf->next; /* Seuraava tertun kenttä */
          }
          settf=settf->next; /* Seuraava to kenttä */
        }
        if(longest>0) { /* Jos löytyi talletetaan kenttäpari */
          sprintf(temps,"'%s'='%s'",savekentta3,kentta2);
          set_add_element_noquotes(&bones,temps);
        }
        setff=setff->next; /* Seuraava from field */
      }

      if(setfromfields!=NULL)
        set_free(setfromfields);
        setfromfields=NULL;
        if(settofields!=NULL)
          set_free(settofields);
        settofields=NULL;

      setu=setuser; /* JariK 20141116 */
      while(setu!=NULL) {
        bones2=NULL;

        p=bones;
        while(*p!='\0') {
          set_get_next_equals(kentta,kentta2,&p);
          set_get_element(kentta,&comp,value,setu->data);
          if(value[0]!='\0')
            sprintf(temps,"'%s' = \"%s\"",kentta2,value);
          else
            sprintf(temps,"'%s'",kentta2,value);

          set_add_element_noquotes(&bones2,temps);
        }

        setinputdata=NULL;
        skk_fetch2(&setinputdata,bones2,skk);

         c=0;
         sid=setinputdata;
         while(sid!=NULL) {
           c++;
         sid=sid->next;
       }

       if(c==1) {
         fprintf(stdout,"*** LÖYTYI ***\n");
         fprintf(stdout,"bo[%s]b\n",bones);
         fprintf(stdout,"b2[%s]b\n",bones2);
         fprintf(stdout,"fo[%s]\n",setinputdata->data);
         fprintf(stdout,"da[%s]\n",setu->data);
       }

       if(setinputdata!=NULL)
         free(setinputdata);
       setinputdata=NULL;
       if(bones2!=NULL)
         free(bones2);
       bones2=NULL;

       setu=setu->next;
     }
     if(bones!=NULL)
       free(bones);
     bones=NULL;
   }
   setf=setf->next;
 }
 if(setallfields!=NULL)
 set_free(setallfields);
 setallfields=NULL;

*** LÖYTYI ***
bo['tilauksen asiakkaan nimi'='asiakkaan nimi', 'tilauksen asiakasnumero'='asiakasnumero']b
b2['asiakkaan nimi', 'asiakasnumero' = "1000"]b
fo['asiakkaan nimi'="Vene Oy", 'asiakasnumero'="1000"]
da['tilauksen asiakasnumero'="1000", 'tilauksen asiakkaan nimi'="", 'tilausnumero'="", 'tilauspäivä'="", 'tilauksen summa'=""]
*** LÖYTYI ***
bo['tilauksen tuotenumero'='tuotenumero', 'tilauksen tuotteen nimi'='tuotteen nimi', 'tilauksen tuotteen hinta'='tuotteen hinta']b
b2['tuotenumero' = "3000", 'tuotteen nimi', 'tuotteen hinta']b
fo['tuotenumero'="3000", 'tuotteen nimi'="Soutuvene", 'tuotteen hinta'="500"]
da['rivin summa'="", 'tilattu määrä'="", 'tilauksen tuotenumero'="3000", 'tilauksen tuotteen hinta'="", 'tilauksen tuotteen nimi'="", 'tilausnumero'="", 'tilausrivin numero'=""]

Prosessit jatkoa

Kaikki oikeudet tietenkin pidätetään.

Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Sain vihdoinkin aikaiseksi perustaa prosessien ja tietovirtojen hallinnalle. Uutena on näyttöjen alalaidassa kuvaus näytölle tulevista ja siitä lähtevistä tiedoista.

Esimerkiksi tilausnäyttö on seuraavanlainen:

tilauksen asiakasnumero
tilauksen asiakkaan nimi
tilausnumero
tilauspäivä
tilauksen summa
rivin summa tilattu määrä tilauksen tuotenumero tilauksen tuotteen hinta tilauksen tuotteen nimi tilausnumero tilausrivin numero
Debugging/test information for processes (c)
‘sovellus’=”tilaus”, ‘fromsovellus’=”asiakas”, ‘tosovellus’=”tilaus”:
’tilauksen asiakkaan nimi’=’asiakkaan nimi’, ’tilauksen asiakasnumero’=’asiakasnumero’
‘sovellus’=”tilaus”, ‘fromsovellus’=”tuote”, ‘tosovellus’=”tilaus”:
’tilauksen tuotenumero’=’tuotenumero’, ’tilauksen tuotteen nimi’=’tuotteen nimi’, ’tilauksen tuotteen hinta’=’tuotteen hinta’
‘sovellus’=”tilaus”, ‘fromsovellus’=”tilaus”, ‘tosovellus’=”toimitus”:
‘toimituksen asiakasnumero’=’tilauksen asiakasnumero’, ‘toimituksen tuotenumero’=’tilauksen tuotenumero’, ‘toimituksen tuotteen nimi’=’tilauksen tuotteen nimi’, ‘toimituksen tuotteen hinta’=’tilauksen tuotteen hinta’, ‘toimituksen tilauspäivä’=’tilauspäivä’, ’tilauksen tilattu määrä’=’tilattu määrä’, ‘rivin summa’=’rivin summa’

Kuva 1: Tilausnäytön kaappaus

Näytön yläosassa ovat nappulat, otsakkeen kentät, rivien kentät ja debukkailu/testailu osio, jossa kerrotaan tälle näytölle tulevat tietovirrat ja täältä lähtevät tietovirrat. Kentät ovat tietysti aakkosjärjestyksessä… Lopussa tilauksen configurointi:

edit: Mietin pitkästä aikaa sorsan julkaisua. Kysymys menee suoraan top teniin, kuten: Kannattaako tätä tehdä? Kannattaako julkaista? Onko tämä uusi, vai jo retroa? Itseasiassa pitäisi tehdä 10 000 sivuluvun jaarittelu. Jään vielä miettimään sorsia…

;(C)1998-2014 Jari Kuivaniemi, Kaikki oikeudet pidätetään!
'sovellus'="tilaus", 'sovelluksen nimi'="Tilaustietojen käsittely", 'link'="http://$host:$port/tilaus"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilausnumero"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilauksen asiakasnumero"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilauksen asiakkaan nimi"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilauspäivä"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilauksen summa"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilausnumero"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilausrivin numero"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilauksen tuotenumero"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilauksen tuotteen nimi"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilauksen tuotteen hinta"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilattu määrä"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="rivin summa"
'sovellus'="tilaus", 'fromsovellus'="asiakas", 'tosovellus'="tilaus",  
'sovellus'="tilaus", 'fromsovellus'="tuote", 'tosovellus'="tilaus",  
'sovellus'="tilaus", 'fromsovellus'="tilaus", 'tosovellus'="toimitus",  
'tilausnumero'="4000", 'tilauksen asiakasnumero'="1000", 'tilauksen asiakkaan nimi'="Vene Oy", 'tilauspäivä'="20140101", 'tilauksen summa'="500"
'tilausnumero'="4000", 'tilausrivin numero'="10", 'tilauksen tuotenumero'="3000", 'tilauksen tuotteen nimi'="Soutuvene", 'tilauksen tuotteen hin
ta'="500", 'tilattu määrä'="1", 'rivin summa'="500"
'tilausnumero'="4000", 'tilausrivin numero'="20", 'tilauksen tuotenumero'="3001", 'tilauksen tuotteen nimi'="Airot", 'tilauksen tuotteen hinta'=
"50", 'tilattu määrä'="2", 'rivin summa'="100"
'tilausnumero'="4000", 'tilausrivin numero'="30", 'tilauksen tuotenumero'="3002", 'tilauksen tuotteen nimi'="Tappi", 'tilauksen tuotteen hinta'=
"10", 'tilattu määrä'="1", 'rivin summa'="10"

Mikset jo koodaa, prosessit jatkoa

Kaikki oikeudet tietenkin pidätetään.

Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Tietovirrat

Aiemmassa postissa ajattelin, että prosesseja kuivattaisi lauseilla, kuten:

toimituspäivä<=tänään, toimituksen tilausnumero=tilausnumero, toimituksen asiakasnumero=tilauksen asiakasnumero, …, toimituksen tuotenumero = tilauksen tuotenumero, jne.

Tämä rikkoo kuitenkin perussääntöä, jossa sarakelistat pitäisi olla vain yhdessä paikassa. Ajattelin toista tapaa kuvata sama asia. Tässä esimerkki tilauksesta lähtevistä ja siihen tulevista tietovirroista.

(tietovirta=1, lähdesovellus=”tuote”, kohdesovellus=”tilaus”)

(tietovirta=2, lähdesovellus=”tilaus”, kohdesovellus=”toimitus”,ehto=”toimituspäivä<=\”kuluvapäivä\””)

(tietovirta=3, lähdesovellus=”tilaus”, kohdesovellus=”tapahtumat”)

(tietovirta=4, lähdesovellus=”tilaus”,kohdesovellus=”myynti”)

Tietovirtoja käsitellään siten, että otetaan lähdesovelluksen sarakkeet ja kohdesovelluksen sarakkeet ja etsitään saman nimiset (tuotenumero=tuotenumero), tai toisiaan muistuttavat (tuotenumero, tilauksen tuotenumero, toimituksen tuotenumero) ja laaditaan automaattisesti niistä tuo “tietokantalause”. Siihen siis tulevat sovelluksen kentät, joille toisessa sovelluksessa löytyy vastinpari (kuten tilauksen tuotenumero=tuotenumero, tilauksen tuotteen nimi=tuotteen nimi, tilauksen tuotteen hinta=tuotteen hinta). Tietovirtaa käsitellään eri tavalla tapauksesta riippuen. Eri tapauksien kuvausta varten tarvittavat alkiot myöhemmin. Ilmeisesti ne ovat yleensä tietovirran 2 ehto tyylisiä.

Tietovirtoja on ainakin kolmea mallia. Tietovirta 1 on esimerkki ensimmäisestä. Siinä tietovirran kohteesta löydettävä kentän arvo vaikuttaa lähteen tiedon valintaan. Jos esimerkiksi tässä tapauksessa tuotteesta on tilaukselle syötetty tuotenumero, haetaan tämän kentän perusteella muut kentät (tilauksella taisi olla tuotenumero, tuotteen nimi ja tuotteen hinta).

Toisesta mallista en keksinyt esimerkkiä, mutta siinä lähteen tiedot kopioidaan sellaisenaan kohteen tietoihin. Myös muutokset vain kopioidaan kohteen kenttiin. Esimerkki voisi olla myyntiraportti (tuotenumero, myynti), mutta siinä on summaus.

Kolmannessa mallissa aluksi tiedot kopioidaan sellaisenaan, ja muutokset kopioidaan kohteen päälle sellaisenaan, kunnes ehto toteutuu. Toimituksessa tämä ehto voisi olla (toimitus tehty==”1″. Eli tiedot kopioidaan aina päälle kunnes toimitus on tehty, ja sen jälkeen luodaan uusi toimitus. Eli jos asiakas on tilannut 5 tuotetta, ja haluaakin muuttaa tilaustaan siten että tilaa 10 tuotetta. Tuo tilatun määrän muuttaminen muuttaa toimitusta siihen asti kun alkuperäinen viiden tuotteen toimitus on tehty.

Neljännessä mallissa muutos luo aina uuden tapahtuman. Tästä esimerkkinä on kirjanpitotapahtumat. Kaikki muutokset tilaukseen luovat uuden tapahtuman/tapahtumat siten, että kirjanpidon tapahtumien summat=tilauksen summat. Kirjanpidon tapahtumissa voi olla useita summia sen mukaan mitä kenttiä kirjanpitoon on laitettu jos esimerkiksi kirjanpitotapahtumalla on tuotenumero, noiden tuotekohtaisten summat on saatava täsmäämään. Tuotenumeron lisäksi voi olla asiakasnumero ja toimittajanumero eli näiden kaikkien yhdistelmien summat on korjattava samalla tapahtumalla, tilauksella voi olla tuotteen hintojen muutoksia, asiakkaan muutoksia, toimittajan muutoksia. Onneksi tästä pitäisi saada aikaan vain yleinen tapaus.. (hehe)

Viidennessä mallissa lähde ja kohde ovat samat, esimerkiksi tilauksessa rivin summa kenttään lasketaan määrä*hinta. Silloin ilmeisesti tietokantalause pitää aina syöttää, muutenhan siihen tulee siirto kaikista kentistä itseensä.

Kuudes malli voi olla summattava kenttä, mutta se muistuttaa mallia 2 (?).

Siinä taisi olla suurinpiirtein tuo asia. Sitten uuden mallisen “tietokantalauseen” käsittelyyn:

Terttu lauseen uusi versio (lyhyt versio)

Terttu lauseen uutta versiota varten tarvitaan kyselyn muutos vanhaan muotoon, ja uuden lausekkeen suorittaminen. Alussa on esimerkiksi tarjottu lause:

(toimituspäivä<=tämäpäivä, toimituksen asiakasnumero=tilauksen asiakasnumero,…)

Tämä lause muutetaan vanhaan muotoon, jossa on lueteltuna kaikki lauseen kentät oli se kohdekenttänä tai lähdekenttänä, eli (tilaus, toimitus) -kysely tuottaa lajiteltuna vastauksena tilauksen kaikki kyselyssä olevat kentät ja toimituksen kyselyssä olevat kentät:

tilaussovelluksen kaikki kentät                toimitussovelluksen kaikki kentåt

tilausotsakkeen kentät, tilausrivin kentät, toimitusotsakkeen kentät, toimitusrivin kentät

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

Tilauksen ja toimituksen yhdistää tilauksen toimitusnumero, tai toimituksen tilausnumero. Ensimmäisessä rivissä ei ole vielä toimitusta, toisessa rivissä on.

Aiemmassa taulukossa on kaikki uuden lauseen tarvitsemat kentät, joten lause voidaan suorittaa. Periaatteessa tuloksena tulevan kyselyn perusteella voidaan toteuttaa nuo aiemmat kaikki versiot tietovirroista. Tarvitaankohan versiota neljä varten kaksi kyselyä, lähde ja kohde erikseen, ja näiden “erotus” kirjoitetaan uuteen tapahtumaan.

Tästä tuli vähän lyhyt, se kaipaa miettimistä.

Vastauksia kommentteihin

Kaikki oikeudet tietenkin pidätetään.

Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Seuraavaksi ohjelmassa olisi kyselykielen seuraavan version kirjoittaminen. Kyselykieleen tulisi nuo prosessien jatkamiseen ja tiedon talletukseen liittyvät asiat, mutta nyt kuitenkin vastauksia kommentteihin:

Kommentteja on kertynyt muutamia, jonkin verran kannustavia, kiitoksia rohkaisusta.

Sitten on joukko tuotetarjouksia, joissa kaikki maailman merkit tarjottuna, eivät ne tietenkään haittaa ketään, mutta niitä voisi olla vähemmän.

Sitten on erilaisia kysymyksiä, joihin tässä muutamia pikavastauksia:

Miten voin aloittaa bloggauksen? Käyttämäni ohjelma on wordpress, jolla voit aloittaa kirjoittelun osoitteessa wordpress.com. Joku muu osaa varmaankin vertailla kirjoitteluohjelmia kun minä (minulla on kokemusta vain tästä). Tekniikkakirjoittelun alkuun pääset esimerkiksi osallistumalla Tero Karvisen mainioille kursseille (terokarvinen.com). Jos suunnittelet ohjelmointiprojektia, tietysti kohtuullinen tuntemus ohjelmointikielestä on plussaa. Lisäksi helpottaa on jos sinulla on aihe, josta kirjoitat/jota tutkit mielelläsi.

Mistä voi lukea samanlaisista asioista, kun kirjoitat? Yleensä ohjelmisto projektit eivät raportoi verkossa, mutta selaile linux, open office projekteja tai aloita oma projekti (älä kuitenkaan varasta tätä). Omassa projektissa valmistumisen todennäköisyys ja aikataulu ovat hyvä olla ainakin kohtuullisia (älä julkaise ikuisuusprojektia).  Kaupan järjestelmistä voi lukea opiskelemalla kauppaa, järjestelmien toimittajilta Sap, Microsoft, ja muut. Sap ja Microsoft voivat olla hiukan liian monimutkaisia ensimmäiseksi kaupan järjestelmäksi. Lukupaikaksi sopii kauppaoppilaitos tai ammattikorkea. Toisaalta kaupan järjestelmiä voi miettiä tutkimalla olemassa olevia yrityksiä ja miettiä mitä toimintoja (tilaus, toimitus, laskutus, kirjanpito, valmistus jne.) niillä on ja mitä tietoja noiden toimintojen toteuttamiseksi tarvittaisiin. Itse kuvittelen että tämä on ensimmäinen tämän tyyppinen järjestelmä, samanlaista projektia ei varmaankaan ole.

Mitkä ovat projektisi perustat?

Yhden henkilön projekti, eli koodimäärä on minimoitu, tällä hetkellä rivimäärä ~3000. Ei ole kourallista select lauseita jokaiselle sovelluksille, näytön tulostusta joka sovellukselle, välimuistialueita kaikille perustiedoille ja niiden kombinaatioille kaikissa sovelluksissa. Pyrin kirjoittamaan vain yhteiset osuudet järjestelmästä ja configurointi määrittelee varsinaisen järjestelmän. Yhteisiä osia ovat näyttöjen tulostukset, tietokantahaut, kyselykieli, yhdistelyt, summaukset, talletus, tulostus, prosessien käsittely (ks 5000 sivuhakua jaarittelu). Ohjelma ei sisällä koodia laskutukselle, toimitukselle tai tilaukselle erikseen, vaan nuo sovellukset ovat määritelty ohjelman ulkopuolella. Esimerkiksi tilaus näyttö sisältää kentät tilausnumero, asiakasnumero, tuotenumerot, määrät jne (tilaus näytön v0 määrittely lopussa). Eli periaatteessa järjestelmä on alussa tyhjä ja asiakas tai käyttäjä lisää tarvitsemansa sovellukset ja tiedot. Sovellus huolehtisi tietojen välityksen prosessin vaiheelta toiseen(toivottavasti) tai perustiedoista (asiakas, tuote) sovellukseen (tilaus). Jos terttua yrittää luokitella se voisi kuulua tietokanta ja taulukkokaskenta tyylisiin sovelluksiin.

Dokumentoinnin vähäisyys, kuvia taulukoita yms? Olen koodaaja, ja järjestelmä ei vielä ole valmis, niin olen pitänyt päähuomion koodaamisessa. Dokumentointi on wysiwyg tyylistä alun linkistä. Lisäksi olen julkaissut jonkin verran lähdekoodi osuuksia aiemmissa posteissa.

Miksei verkkosivulla ei ole lahjoitusnappulaa? Lahjoitusnappulaa ei ole, koska suomessa laki kieltää lahjoitusten keräämisen jos olet yksityinen henkilö. Toisaalta lahjoitukset lisäisivät toimintaan mukavan pakon, toisaalta jo verkossa kirjoittaminen ajaa saman asian.(hehe). Olisin varmaankin jo tämän projektin aikana lopettanut/siirtänyt kirjoittamisen monta kertaa tulevaisuuteen ilman vastikään aloittamaani verkkojulkaisua (katso lopun copyright merkintä). Toisaalta lahjoitukset toisivat palkan ja valmistumispakon ja mahdolliset omistusongelmat(lahjoittajasta tulee ohjelmistotalo).

;(C)1998- Jari Kuivaniemi, Helsinki, Finland. Kaikki oikeudet pidätetään!
'sovellus'="tilaus", 'sovelluksen nimi'="Tilaustietojen käsittely", 'link'="http://moijari.com:5002/tilaus"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilausnumero"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="asiakasnumero"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilauspäivä"
'sovellus'="tilaus", 'chapter'="header", 'memberid'="tilauksen summa"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilausnumero"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilausrivin numero"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tuotenumero"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tuotteen nimi"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tuotteen hinta"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="tilattu määrä"
'sovellus'="tilaus", 'chapter'="lines", 'memberid'="rivin summa"

Tertun kyselykieli (5000 sivuhakua jaarittelu)

Pieni luova tauko oli paikallaan. Ongelmana olivat tietojen tallennus, ja prosessien tekninen toteutus yleisellä tasolla (esimerkiksi tilaus – toimitus – lasku). Toivottavasti ette ole painellut paljon F5:sta…

Kaikki oikeudet pidätetään.

Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Oletetaan että Tertussa on kaksi asiakasta, joiden muoto on (asiakasnumero, asiakkaan nimi), ne ovat vaikka (1000,”Asiakas 1″) ja (2000, “Asiakas 2″). Nämä voitaisiin kirjoittaa myös pidempään muotoon (asiakasnumero=”1000″, asiakkaan nimi=”Asiakas 1″) ja (asiakasnumero=”2000″, asiakkaan nimi=”Asiakas 2″).

Jos edellistä Terttu tietokantaa (löysästi) vastaan tehdään kysely (asiakasnumero, asiakkaan nimi) Terttu palauttaa molemmat rivit (1000,”Yritys 1″) (2000,”Yritys 2″). Jos tehdään kysely (asiakasnumero==1000, asiakasnumero, asiakkaan nimi), se palauttaa rivin (1000,”Yritys 1”).

Automaattiset yhdistelyt (~join)

Jos edellisten asiakastietojen lisäksi on seuraavat rivit muotoa (asiakasnumero, myynti) (1000,100) ja (2000,200) ja tehdään kysely (asiakasnumero, asiakkaan nimi, myynti) rivien automaattisen yhdistelyn tuloksena palautuvat rivit (1000,”Asiakas 1″,100) ja (1000,”Asiakas 2″,200). Tietysti (asiakasnumero == 2000, asiakasnumero, asiakkaan nimi, myynti) palauttaisi asiakas 2:n myynnin.

Tietueiden “leveys”

Edellisen kappaleen myyntitietojen lisäksi myyntitietoja voi olla muillakin tasoilla. Edelliset myynnit olivat kokonaismyyntejä asiakkaalle (asiakasnumero,myynti), lisäksi myyntitietoja voi olla esimerkiksi tuotteen kokonaismyynti (tuotenumero, myynti), tuotteen vuosittainen myynti (tuotenumero, vuosi, myynti). Nämä perustetaan kyselyiden perusteella. Tässä ilmeisesti sääntö on että leveämmästä voidaan laskea(vertaa tietokanta, relaatiotietokanta, laskennalliset kentät) aina kapeampi, jos kaikki leveämmän rivit ovat käytössä ja leveämpi sisältää kaikki kapeamman kentät. Tapahtumatietojen perusteella voidaan laskea kaikki tasot. Kyselyä varten tarvitaan versio jossa on minimimäärä tietueita ja minimimäärä kenttiä, mutta kaikki kyselyn kentät, tai viittaukset kyselyn kenttiin. Jos esimerkiksi kyselyssä on tuoteryhmä tai asiakasryhmä ja kyselyssä ovat viittaukset asiakkaaseen ja tuotteeseen jotka ovat määritelty (asiakasnumero, asiakkaan nimi, asiakasryhmä) ja (tuotenumero, tuotteen nimi, tuoteryhmä) kysely toimii. (vertaa automaattiset yhdistelyt)

Lukujen käsittely

Lukuja on ainakin avaimina (asiakasnumero), ei summattavana (tuotteen hankintahinta), summattava(myynti)

Prosessit

Prosessien käsittely tehdään siten että saman nimiset kentät siirretään seuraavaan prosessin vaiheeseen saman nimisiin kenttiin (toisiaan muistuttaviin kuten tilausnumero, toimituksen tilausnumero?). Jos kentät ovat erinimisiä, ne pitää siirtää erikseen (toimituksen tilausnumero  = tilausnumero). Prosessissa siirrossa tietueiden suhteet ja niiden kenttien suhteet määrittelevät, kuinka monta tietuetta perustetaan. Jos esimerkiksi yhdessä tilauksessa on useampien yritysten tuotteita (yritys on rivillä), ja toimituksessa voi olla vain yhden yrityksen tuotteita (yritys on otsakkeella) perustetaan useampia toimituksia. Voit korvata sanan yritys esimerkiksi sanalla varasto, myyntiorganisaatio.

Siirto prosessissa voidaan kuvata yhdellä lauseells(~kysely), esimerkiksi (toimituspäivä <= kuluvapäivä, toimituksen tilausnumero=tilausnumero, toimituksen asiakasnumero=asiakasnumero, toimituksen tuotenumero=tuotenumero, määrä=määrä, …) eli poimintaehto ja eri otsake- ja rivikenttien siirrot. Kentät siirretään omille tasoilleen riippuen siitä miten lähde ja kohde tiedot ovat määritelty (lähinnä otsakkeiden ja rivien suhde).

Kentät siirretään lähdetietojen tasoista omille kohdetietojen tasoilleen tasoilleen, ja kohdetietoja luodaan tarvittava määrä.

Terttu jatkuu

Olen vielä jatkanut tertun koodaamista, olen tehnyt seuraavia muutoksia:

Lisätty html parametrien käsittely. Lisätty koodia session id:lle, lisätty reset, fetch, prev ja next nappulat. Lisätty otsakkeen kenttien perusteella haku fetch nappulalla. Lisätty save nappula, se ei vielä toimi. Lisätty yksinkertaiset session muuttujat. Lisätty koodi prev ja next nappuloille. Korjattu pikku bugi css tekstin lähetyksestä.

Lopun koodipätkissä session id:n antamiseen tarvittavat rutiinit, eli session id:n muodostus ja satunnaisbittigeneraattori(genbytes). Satunnaisbittigeneraattori ei ole kai ole kovin hyvä sellaisenaan, koska se perustuu suoraan kelloon, mutta siitä saa kohtuullisen lähteen satunnaisbittigeneraattorille.

Edit: Katsoin uudestaan tuota istunnon avaimen generointirutiinia, ja huomasin että se ei käytä mitään muuta kuin genbyte:iä istunnon avaimen generointiin. Siksi lisäsin tuon linuxin satunnaislukugeneraattorin yhdeksi lähteeksi. Tein muutaman genbyte testiohjelman, ja kuten edellisessä kappaleessa kerroinkin, genbytes ei sellaisenaan riitä (kokeile generoida istuntoavaimia pelkällä genbytellä siten, että aloitat generaattorin aina samasta kellonajasta)… En onnistunut ohjeideni mukaan löytämään uudestaan samoja generoituja avaimia. Tämä editti taitaa olla ankka. Hups! Generaattori perustuu vaihteluihin, joita löytyy luettaessa kelloa toistuvasti. Jokainen luettu bitti vaikuttaa ~kaikkiin syntyvän satunnaisluvun bitteihin(f), Omissa testeissäni vaihtelu riitti, mutta jättäisin kuitenkin tuon linuxin satunnaislukugeneraattori rivin.

Kaikki oikeudet tietenkin pidätetään.

Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

#define SESSIONIDLEN 24 /* 6*24=144 riittävä? */
#define SESSIONIDRAW 18

unsigned char sessionid[SESSIONIDLEN+1]="";

char sessiondigits[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";

void get_session_id(unsigned char *id)
{
 int c;
 int slen=strlen(sessiondigits);
 unsigned char d,buffer[SESSIONIDRAW],*i;
 urandom_xor(sizeof(buffer),buffer);
 genbytes(sizeof(buffer),buffer,20);
#ifdef USE_RANDOM_GEN
 /* Read Schneier: Applied Cryptography, second edition, p 426 distilling randomness */
 random_gen_xor(sizeof(buffer),buffer);
#endif

#ifdef USE_FORT_RANDOM_DATA
 /* Read Schneier: Fortuna (PRNG) */
 fort_random_data_xor(sizeof(buffer),buffer);
#endif

 for(i=id,c=0;c<SESSIONIDRAW;c+=3) {
   d=(buffer[c]&0xfc)>>2;
   *i++=sessiondigits[d];
   d=((buffer[c]&0x03) << 4)| (buffer[c+1]&0xf0)>>4;
   *i++=sessiondigits[d];
   d=((buffer[c+1]&0x0f) << 2)| (buffer[c+2]&0xc0)>>6;
   *i++=sessiondigits[d];
   d=(buffer[c+2]&0x3f);
   *i++=sessiondigits[d];
 }

 *i='\0';

 fprintf(stdout,"sessionid: %sn",id);
 fflush(stdout);
}

#include <sys/time.h>

long genbyte()
{
 int byte;
 struct timeval tv;

 gettimeofday(&tv,NULL);

 byte=
   (tv.tv_usec&0xff)^
   ((tv.tv_usec>>8)&0xff)^
   ((tv.tv_usec>>16)&0xff)^
   ((tv.tv_usec>>24)&0xff)^
   (tv.tv_sec&0xff)^
   ((tv.tv_sec>>8)&0xff)^
   ((tv.tv_sec>>16)&0xff)^
   ((tv.tv_sec>>24)&0xff);
 return(byte);
}

genbytes(int size,unsigned char *buffer,int b)
{
 int c,d,e,f;
 long u;
 unsigned char byte;
 struct timeval tv;

 f=0;
 for(c=0;c<8*b;c++) {
   for(d=0;d<size;d++) {
     u=genbyte();
     e=buffer[d];
     e=((e&0x80)>>7) | ((e&0x7f)<<1);
     e=e^u;
     buffer[d]=e;
   }
   for(d=0;d<size;d++) { /* see rc4 */
     f=(f+buffer[d])%size;
     e=buffer[d];
     buffer[d]=buffer[f];
     buffer[f]=e;
   }
 }
}

void urandom_xor(int len2,unsigned char *buf2) {
  int n,c,len;
  unsigned char buffer2[32],*buf;

  FILE *fp1;
  fp1=fopen("/dev/urandom","rb");
  if(fp1!=NULL) {
    len=len2;
    buf=buf2;
    while(len!=0) {
      n=(len<32) ? len : 32;
      fread(buffer2,1,n,fp1);
      for(c=0;c<n;c++)
        buf[c]^=buffer2[c];
      len-=n;
      buf+=n;
    }
     fclose(fp1);
  }
}

int htmlevent(int news,char *sovellus) {
 /* ... */
 if(sessionid[0]=='�'
   get_session_id(sessionid);
 /* ... */
}

Kotisivu