One to Many Form

You can share your experience with HMG. Share with some screenshots/project details so that others will also be benefited.

Moderator: Rathinagiri

User avatar
swapan
Posts: 242
Joined: Mon Mar 16, 2009 4:23 am
Location: Kolkata, India
Contact:

Re: One to Many Form

Post by swapan »

Sudip good-going!

I believe two kind of entry modules are mainly required for business applications. One is Master Entry form and another is Transaction Entry (Sales, Purchase, Voucher etc.). A good transaction entry module with detail part (the kind of Sudip has demonstrated here) helps us to learn many things.

Sudip, your this transaction entry needs many enhancements and fixes before it qualifies for a transaction entry module at a professional level.

My 2 cents (based on the modified code uploaded by Giri):
1) When I press Enter in the Amount column, the cursor/focus should move to the next row and 1st column. I believe it takes time to have a control over a grid entry. Giri has introduced "Insert" option but that can be an optional, user will not press/click "insert" everytime.

2)You are allowing entry in the Amount column (which is valid), but if u change the amount, your calculation function changes it again (qty*rate). Though this is not HMG issue.

3)In this grid entry - the user should have all & same flexibility during entry & edit mode.

4)ONE NEW IDEA:
---------------
In HMG grid, can wallpaper like feature be inserted as background picture. If yes this a grid can look attractive and more presentable in certain cases.

5)U've used combo box in grid - well do u remember voucher entry interface of Tally. How about this:
start typing in the product column and a help list will appear - quick search ur product and press enter. The value gets returned to cell. Combo box is not good when there is huge items - we can't quick search...only the 1st char search works, I suppose. Yes if ur combo box comes with textbox where one write and the cursor of the combobox moves as per the text, then can be considered.

6)Master Help list (customer/product etc.):
ur filling them in array. Right? But will it b not time-consuming when real-life data is there. And if u fill the arrays initially say at the start of the module or application then what about the real time data if the application is running in a network. We should try to fetch data/datasource directly from the dbf/database and pass it to the control which gets invoked when Help list is called.

7)How to make new invoice entry in ur this module?!

With regards,

Swapan
Thanks & Regards,
Swapan Das

http://www.swapandas.com/
User avatar
Rathinagiri
Posts: 5477
Joined: Tue Jul 29, 2008 6:30 pm
DBs Used: MariaDB, SQLite, SQLCipher and MySQL
Location: Sivakasi, India
Contact:

Re: One to Many Form

Post by Rathinagiri »

Hi Swapan,

Regarding Combobox, the combobox items can be searched by pressing repeated key strokes if you had sorted it out already.

Regarding Grid background image, it can't be possible as of now, but we can have colors. (even for alternative rows/cols)

Regarding array handling too, while processing, it shall be in the main memory and every time we take network and database resource would be a problem too. However, I am afraid an invoice can't have more than 100 items. Isn't it? Anyway, it is programmers' problem. :)

I had introduced a new option here in Sudip's code. You can verify that too.

Except background image, I think we can achieve most of your suggestions. Thanks.
East or West HMG is the Best.
South or North HMG is worth.
...the possibilities are endless.
User avatar
sudip
Posts: 1455
Joined: Sat Mar 07, 2009 11:52 am
Location: Kolkata, WB, India

Re: One to Many Form

Post by sudip »

Dear Swapan,

Thank you very much for all your advice and I must say that all of them are very PRACTICAL specially in the environment we are working.

I am learning HMG. If you tell me to do all those things in VFP, I have no problem. But, I don't know the language thoroughly and please remember Designer/Creator of HMG designed it in a very Simple way, where you may feel something missing at first sight. After some times when you get clarification from persons who are more experienced, everything will be clear.

I also have many doubts (may be more than you ;)), but I always want to seek the solution in my off-time. Sometimes it becomes clear, sometimes it takes more time to be clear. :)

But, I am sure, I can solve all your advice within a month with HMG :)

Please try SET NEVIGATION EXTENDED in your software (regarding Tab/Enter problem) :)

(Now I have to write next part of "OUR" one-to-many form, i.e., Add, Save, Delete)

With best regards.

Sudip
With best regards,
Sudip
User avatar
sudip
Posts: 1455
Joined: Sat Mar 07, 2009 11:52 am
Location: Kolkata, WB, India

Re: One to Many Form

Post by sudip »

Thank you Rathinagiri,

Thanks for the help. As, I know the language relatively less, I can't answer Swapan's questions ;(

Regards.

Sudip
With best regards,
Sudip
User avatar
sudip
Posts: 1455
Joined: Sat Mar 07, 2009 11:52 am
Location: Kolkata, WB, India

Re: One to Many Form Ver 1.0

Post by sudip »

Hi,

Thank you all for your hearty support in my learning HMG :)

I added some attributes and functions in "OUR" One2Many Form Demo Project. I sincerely believe in using the word "OUR", because it's not only me who is learning HMG through the project, "WE", the learners of HMG are learning through this project. :)

Add, Delete, Save, Cancel functions are added. One check is added to check any change is made or not.

Please advise as before to improve the project.

One thing more. As it becomes bigger, next time I shall upload zip file (Is zip allowd?, .prg is not allowed. I tried it)

Thank you Rathinagiri for helping so much. Hi Alex and Swapan, thank you all. And thank you CCH, you always kept my focus on HMG :) (Sorry, I couldn't answer your mail due to the project)

With best regards.

Sudip

Code: Select all


#include "dbstruct.ch"
#include "minigui.ch"

static aGrid := {}, aCustSource := {}, aCustDisp := {}, ;
   aItemSource := {}, aItemDisp := {}, aItemRate := {}, ;
   lChanged := .f., lAddMode := .f.

Function Main
   local i, cb

   SetProg()
   CreateTable()
   select sale
   set order to tag invno
   go top
   CreateLookup()

   DEFINE WINDOW Form_1 ;
      AT 0,0 ;
      WIDTH 640 ;
      HEIGHT 430 ;
      TITLE "One To Many Form (Ver 1.0)" ;
      MAIN ;
      on init RefreshWin()

      @ 10, 10 label lblInvno value "Invoice No.:"
      @ 10, 100 textbox txtInvno ;
         readonly

      @ 10, 300 label lblInvdt value "Invoice Dt.:"
      @ 10, 400 datepicker dpkInvdt ;
         on change ValChanged()



      @ 40, 10 label lblCustcd value "Customer:"
      @ 40, 100 COMBOBOX cboCustcd ;
         items aCustDisp ;
         width 200 ;
         on change ValChanged()

      @ 70,10 GRID grdSaledtl ;
         WIDTH 550 ;
         HEIGHT 250 ;
         HEADERS {'Item', 'Rate', 'Qty', 'Amt'} ;
         WIDTHS {200,100,100,100} ;
         COLUMNCONTROLS { {'COMBOBOX', aItemDisp}, ;
         {'TEXTBOX', 'NUMERIC', '999999.99'}, ;
         {'TEXTBOX', 'NUMERIC', '999999'}, ;
         {'TEXTBOX', 'NUMERIC', '99999999.99'}} ;
         edit ;
         justify {0,1,1,1};
         on lostfocus calcamt();
         on change ValChanged()

      @ 320, 10 label lblMesg1 value "(Ins: Add New, Del: Delete)" width 200

      @ 330, 300 label lblTotamt value "Total:"
      @ 330, 400 textbox txtTotamt ;
         readonly ;
         numeric ;
         inputmask "9999999.99"

      @ 360, 10 button cmdPrev caption "&Previous" ;
         action MovePrev()

      @ 360, 110 button cmdNext caption "&Next" ;
         action MoveNext()

      @ 360, 210 button cmdAdd caption "&Add" ;
         action AddRec()

      @ 360, 310 button cmdDelete caption "&Delete" ;
         action DeleteRec()

      @ 360, 410 button cmdSave caption "&Save" ;
         action SaveRec()

      @ 360, 510 button cmdCancel caption "&Cancel" ;
         action CancelRec()

   END WINDOW
   on key DELETE of form_1             action dodel()
   on key INSERT of form_1             action doins()

   CENTER WINDOW Form_1

   ACTIVATE WINDOW Form_1

Return


FUNCTION CreateLookup()
   local i

   aItemSource := {}
   aItemDisp := {}
   aItemRate := {}
   aCustSource := {}
   aCustDisp := {}

   select item
   set order to tag itemnm
   do while !eof()
      aadd(aItemSource, item->itemcd)
      aadd(aItemDisp, item->itemnm)
      aadd(aItemRate, item->rate)
      skip
   enddo

   select cust
   set order to tag custnm
   do while !eof()
      aadd(aCustSource, cust->custcd)
      aadd(aCustDisp, cust->custnm)
      skip
   enddo

return nil


FUNCTION RefreshWin()
   if lAddMode
      select sale
      go bottom
      form_1.txtInvno.value := str(val(sale->invno)+1, 10)
      form_1.dpkInvdt.value := date()
      form_1.cboCustcd.value := 0
      form_1.txtTotamt.value := 0.00

      aGrid := {}
      form_1.grdSaledtl.DeleteAllItems()
   else
      form_1.txtInvno.value := sale->invno
      form_1.dpkInvdt.value := sale->invdt
      form_1.cboCustcd.value := ascan(aCustSource, {|x| x == sale->custcd})
      form_1.txtTotamt.value := sale->totamt

      aGrid := {}
      select saledtl
      set order to tag invno
      locate for invno = sale->invno
      do while !eof()
         aadd(aGrid, {ascan(aItemSource, {|x| x == saledtl->itemcd}), ;
            saledtl->rate, saledtl->qty, saledtl->rate*saledtl->qty})
         continue
      enddo
      form_1.grdSaledtl.DeleteAllItems()
      for i = 1 to len(aGrid)
         form_1.grdSaledtl.additem(aGrid[i])
      next

   endif
return nil


PROCEDURE CalcAmt()
   local i, nIctr, aTemp := {}, mTotAmt := 0.00
   // lChange := .t.
   i := form_1.grdSaledtl.value
   nIctr :=  form_1.grdSaledtl.cell(i, 1)
   aTemp := form_1.grdSaledtl.item(i)
   if i < 1
      return
   endif
   aTemp[2] := iif(nIctr>0 .and. nIctr<=len(aItemRate), aItemRate[nIctr], 0.00)
   aTemp[4] := aTemp[2]*aTemp[3]
   form_1.grdSaledtl.item(i) := aTemp

   for i = 1 to form_1.grdSaledtl.Itemcount
      mTotAmt += form_1.grdSaledtl.cell(i, 4)
   next
   form_1.txtTotamt.value := mTotAmt
return


PROCEDURE MovePrev()
   if IsChanged()
      return
   endif
   select sale
   if !bof()
      skip -1
   endif
   if bof()
      go top
   endif
   RefreshWin()


   PROCEDURE MoveNext()
   if IsChanged()
      return
   endif
   select sale
   if !eof()
      skip
   endif
   if eof()
      go bottom
   endif
   RefreshWin()


   FUNCTION ValChanged()
   lChanged := .t.
return nil

FUNCTION IsChanged()
   if lAddMode .or. lChanged
      msgbox("Record Changed. Please Save or Cancel."); return .t.
   endif
return .f.


FUNCTION AddRec()
   if IsChanged()
      return
   endif
   lAddMode := .t.
   lChanged := .t.
   RefreshWin()
return nil


FUNCTION DeleteRec()
   if IsChanged()
      return
   endif
   if !MsgYesNo("Do you really want to delete?")
      return
   endif
   select sale
   if eof()
      msgbox("Cannot Delete"); return
   endif
   select saledtl
   delete all for invno = sale->invno
   select sale
   delete
   skip
   if eof()
      go bottom
   endif
   RefreshWin()



   FUNCTION SaveRec()
   local i
   select sale
   if eof()
      msgbox("Please Add and then Save...");return
   endif
   if empty(form_1.dpkInvdt.value)
      msgbox("Please specify Invoice date!"); form_1.dpkInvdt.setfocus(); return
   endif
   if empty(form_1.cboCustcd.value)
      msgbox("Please specify Customer!"); form_1.cboCustcd.setfocus(); return
   endif
   if empty(form_1.grdSaledtl.itemcount)
      msgbox("Invoice must have Items!"); form_1.grdSaledtl.setfocus();return
   endif
   for i := 1 to form_1.grdSaledtl.itemcount
      do case
      case empty(form_1.grdSaledtl.cell(i, 1))
         msgbox("Please specify Item!");return
      case empty(form_1.grdSaledtl.cell(i, 2))
         msgbox("Blank Rate not allowed!");return
      case empty(form_1.grdSaledtl.cell(i, 3))
         msgbox("Blank Qty not allowed!");return
      endcase
   next
   select sale
   if lAddMode
      append blank
   endif
   sale->invno := form_1.txtInvno.value
   sale->invdt := form_1.dpkInvdt.value
   sale->custcd := aCustSource[form_1.cboCustcd.value]
   sale->totamt := form_1.txtTotamt.value

   select saledtl
   if !lAddmode
      delete all for invno = sale->invno
   endif
   for i := 1 to form_1.grdSaledtl.itemcount
      append blank
      saledtl->invno := sale->invno
      saledtl->itemcd := aItemSource[form_1.grdSaledtl.cell(i, 1)]
      saledtl->rate := form_1.grdSaledtl.cell(i, 2)
      saledtl->qty := form_1.grdSaledtl.cell(i, 3)
   next
   lAddMode := lChanged := .f.
   RefreshWin()
return nil


FUNCTION CancelRec()
   lAddMode := lChanged := .f.
   RefreshWin()
return nil


Function SetProg()
   set talk off
   set dele on
   set date brit
   set cent on
   set epoch to 1950
   SET BROWSESYNC ON
   SET NAVIGATION EXTENDED
   SET INTERACTIVECLOSE QUERY MAIN
   set font to "Arial", 09
   REQUEST DBFCDX , DBFFPT
   RDDSETDEFAULT( "DBFCDX" )
return nil



FUNCTION CreateTable()
   local aDbf := {}

   if !file("item.dbf")
      aDbf := {}
      aadd(adbf,   {"itemcd",   "c",   10,   0})
      aadd(adbf,   {"itemnm",   "c",   20,   0})
      aadd(adbf,   {"rate",     "n",    8,   2})
      dbcreate("item", adbf)
      use item
      append blank
      replace itemcd with "COMP", itemnm with "Computer", rate with 20000.00
      append blank
      replace itemcd with "CD", itemnm with "Compact Disk", rate with 10.00
      append blank
      replace itemcd with "OPM", itemnm with "Optical Mouse", rate with 400.00
      use
   endif

   if !file("cust.dbf")
      aDbf := {}
      aadd(adbf,   {"custcd",    "c",   10,   0})
      aadd(adbf,   {"custnm",   "c",   20,   0})
      dbcreate("cust", adbf)
      use cust
      append blank
      REPLACE CUSTCD WITH "CUST1", CUSTNM WITH "Customer One"
      append blank
      REPLACE CUSTCD WITH "CUST2", CUSTNM WITH "Customer Two"
      append blank
      REPLACE CUSTCD WITH "CUST3", CUSTNM WITH "Customer Three"
      use
   endif

   if !file("sale.dbf")
      aDbf := {}
      aadd(adbf,   {"invno",   "c",   10,   0})
      aadd(adbf,   {"invdt",   "d",    8,   0})
      aadd(adbf,   {"custcd",  "c",   10,   0})
      aadd(adbf,   {"totamt",  "n",   10,   2})
      dbcreate("sale", adbf)
      use sale
      append blank
      replace invno with str(1, 10), invdt with date(), custcd with "CUST1", totamt with 40200.00
      append blank
      replace invno with str(2, 10), invdt with date(), custcd with "CUST2", totamt with 420
      use
   endif

   if !file("saledtl.dbf")
      aDbf := {}
      aadd(adbf,   {"invno",    "c",   10,   0})
      aadd(adbf,   {"itemcd",   "c",   10,   0})
      aadd(adbf,   {"rate",     "n",    8,   2})
      aadd(adbf,   {"qty",      "n",    6,   0})
      dbcreate("saledtl", adbf)
      use saledtl
      append blank
      replace invno with str(1, 10), itemcd with "COMP", rate with 20000.00, qty with 2
      append blank
      replace invno with str(1, 10), itemcd with "CD", rate with 10.00, qty with 20
      append blank
      replace invno with str(2, 10), itemcd with "CD", rate with 10.00, qty with 5
      append blank
      replace invno with str(2, 10), itemcd with "OPM", rate with 400.00, qty with 1
      use
   endif
   close databases
   use item new
   use cust new
   use sale new
   use saledtl new

   select item
   index on itemcd tag itemcd
   index on upper(itemnm) tag itemnm

   select cust
   index on custcd tag custcd
   index on upper(custnm) tag custnm

   select sale
   index on invno tag invno
   index on invdt tag invdt
   index on custcd tag custcd

   select saledtl
   index on invno tag invno
   index on itemcd tag intemcd

return nil


function dodel
   local lineno := form_1.grdsaledtl.value
   if  lineno > 0
      form_1.grdsaledtl.deleteitem(lineno)
      if lineno > 1
         form_1.grdsaledtl.value := lineno - 1
      else
         if form_1.grdsaledtl.itemcount > 0
            form_1.grdsaledtl.value := 1
         endif
      endif
   endif
   calcamt()
return nil

function doins
   //if form_1.grdSaledtl.cell(form_1.grdsaledtl.itemcount, 4) > 0.0
   //   form_1.grdsaledtl.additem({1,aitemrate[1],0,0.00})
   //endif
   form_1.grdsaledtl.additem({0,0.00,0,0.00})
   form_1.grdSaledtl.value := form_1.grdsaledtl.itemcount
   return nil
With best regards,
Sudip
User avatar
sudip
Posts: 1455
Joined: Sat Mar 07, 2009 11:52 am
Location: Kolkata, WB, India

Re: One to Many Form

Post by sudip »

Hi,

Please incorporate following code in Grid definition:- (Please check columnwhen)

Using this, user can change Item and Qty fields only, not Rate and Amt fields.

Code: Select all

     @ 70,10 GRID grdSaledtl ;
         WIDTH 550 ;
         HEIGHT 250 ;
         HEADERS {'Item', 'Rate', 'Qty', 'Amt'} ;
         WIDTHS {200,100,100,100} ;
         COLUMNCONTROLS { {'COMBOBOX', aItemDisp}, ;
         {'TEXTBOX', 'NUMERIC', '999999.99'}, ;
         {'TEXTBOX', 'NUMERIC', '999999'}, ;
         {'TEXTBOX', 'NUMERIC', '99999999.99'}} ;
         edit ;
         justify {0,1,1,1};
         on lostfocus calcamt();
         on change ValChanged();
         COLUMNWHEN { {|| .t.},{|| .f.}, {|| .t.}, {|| .f.}}
Tank you Rathi, I did it !

Regards.

Sudip
With best regards,
Sudip
User avatar
swapan
Posts: 242
Joined: Mon Mar 16, 2009 4:23 am
Location: Kolkata, India
Contact:

Re: One to Many Form

Post by swapan »

Thanks Giri & Sudip for going through my post and taking my feedback in good spirit. Acutally Sudip I'm also considering your this project as my learning example. Yes, its "ours" project and we all can reap benefits from it provided we work on it.

2day, hope to go through this project more seriously..............

BTW: One more feature grid feature came 2 my mind!
Do/Can we have HIDDEN COLUMNS in our HMG grid?

Regards,

Swapan
Thanks & Regards,
Swapan Das

http://www.swapandas.com/
User avatar
Rathinagiri
Posts: 5477
Joined: Tue Jul 29, 2008 6:30 pm
DBs Used: MariaDB, SQLite, SQLCipher and MySQL
Location: Sivakasi, India
Contact:

Re: One to Many Form

Post by Rathinagiri »

Do/Can we have HIDDEN COLUMNS in our HMG grid?
Yes. By defining the width of the column as 0.
East or West HMG is the Best.
South or North HMG is worth.
...the possibilities are endless.
User avatar
Rathinagiri
Posts: 5477
Joined: Tue Jul 29, 2008 6:30 pm
DBs Used: MariaDB, SQLite, SQLCipher and MySQL
Location: Sivakasi, India
Contact:

Re: One to Many Form

Post by Rathinagiri »

Thank you Rathi, I did it !
Yes. You did it, even without any modification in the source code. I didn't know this feature. Thanks for sharing.

And, you had not provided code for valchanged() function.
East or West HMG is the Best.
South or North HMG is worth.
...the possibilities are endless.
User avatar
sudip
Posts: 1455
Joined: Sat Mar 07, 2009 11:52 am
Location: Kolkata, WB, India

Re: One to Many Form

Post by sudip »

Dear Rathi,

I already gave ValChanged() function in my previous post. But due to bad indentation (I use latest version of xEditw from xHarbour.com and Indent program came as utility from MiniGui Ext) it looks hidden.

Please look next to "PROCEDURE MoveNext()" of my given code. If by any chance you don't find it, I am giving the following code.

Code: Select all

FUNCTION ValChanged()
   lChanged := .t.
return nil
BTW, can anyone suggest me a good source code editor which has color mark for Harbour/xHarbour syntax?

Thank you all for helping me to learn HMG :)

With best regards.

Sudip
With best regards,
Sudip
Post Reply