I think you have a different problem than Vilhelm-Ion addressed.
You want to create the order ref no, being the order primary key, right?
Well, that's typically the job of the database and only created with the insert via the default value. The easiest to do is create an inter autoinc field, also this is about buffering changes, you don't need to insert a grid cursor into a table, you use the buffer to buffer not yet complete orders and order items, like this:
Code:
* Tables (should already exist of course), here using cursors just for demonstrating the principles
Create cursor Orders (iID I AutoInc, dOrderDate D Default Date(), iClerkID I)
Create cursor OrderItems (iID I AutoInc, iOrderID I, iProductID I, iAmount I Default 1)
* Use Buffering (later this is automatic when you implement form load code doing this general for all workareaas)
Set Multilocks On
CursorSetProp("Buffering",5,"Orders")
CursorSetProp("Buffering",5,"OrderItems")
* App Init
#Define yourappclass "empty" && should be changed to your appllication class
goApp=CreateObject(yourappclass)
* Clerk Login, assume clerk 42 logs in
Addproperty(goApp,"iLoggedinClerkID",42) && this property should be set at the log in of a clerk into your app
* Form Load code (after the form datasession exist and before any tables are opened in the DE, means Form.Load() event)
Set Multilocks On
Cursorsetprop("Buffering",5,0) && all workareas are table buffered (also locked optimistic, that means no locks until writes)
* A new order starts (eg in form init or in a button "new order")
Insert Into Orders (iClerkID) Values (goApp.iLoggedinClerkID) && other fields are set automatic, order date by default value and iid by autoinc
* The form may display some order attributes (even some I did not mention here, whatever head data)
* Add order item (eg add item button calls a form for choice of a product):
* Do Form productchoice To liChoosenProductID, let's skip this partt and simply set liChoosenProductID=21
liChoosenProductID = 21 && may also be 0 or whatever form choice
If !Empty(liChoosenProductID) && clerk did not cancel product choice (in which case the form returns 0)
Insert Into OrderItems (iOrderID, iProductID) Values (Orders.iID, liChoosenProductID)
Endif
* Now the clerk may add more items or change items amount, etc.
* The grid may display product details like name and price by a SET RELATION from order items to the product table (don't add redundant infos into the orderitems table)
*Finally save:
liInitialTransactionLevel = TxnLevel()
Begin Transaction
If Tableupdate(1,.f.,"Orders") && save the order head record
If Tableupdate(1,.f.,"OrderItems") && save the order items
End Transaction
Endif
Endif
If TxnLevel()>liInitialTransactionLevel
* Something went wrong (network error, hdd error, some conflict)
Rollback
Endif
If you run this, you have one order, then run just the part from *App Init onwards and you have a second (of course similar) order each with one order item.
There are some essential things to understand this:
1. You let the db create primary keys, they are created buffered, not immidately saved into the DBF, yet other clerks won't get the same id, even if it takes many minutes before the order is completed and saved. That's vbery essential aqdvantage of the autoinc fields, their counter is maintained in the dbf header, which is updated immidiately always, even in buffered mode.
2. Set Multilocks On: Needed for table buffering, for buffering multiple record in one table. Actually this sample makes no use of this, but in general you will have more than 1 order item
4. Buffering Setting: I do this for workarea 0, means all future opened tables or cursors or views are table buffered, needs to be done AFTER the private datasession exists and BEFORE tables are opened, so ideally in Form Load.
5. goApp.iLoggedinclerkID: Why? Such an information is often needed in many places in your app: What is the current user? Here it is stored in the order to see which clerk put the order
6. Insert Into Orders: As said in (2) the clerk is mainained here, but you also need the iID for order items. There is no goApp property needed for that, no public nor private variable, you can always address the current order iID, as the Insert also puts the record pointer to this new (and buffered) record, so your Orders table is your "variable" for the Order head data like its ID.
7. Insert Into OrderItems using Orders.iID is making use of the local record pointer positioned on the current buffered order record. You don't needd to be afraid parallel users move this, the record pointer is of your workarea, not of the table file, it's local.
All that said it's surely hard to comprehend all this just from this post, so please ask back.
The general idea is to NOT use a separate grid cursor. Actually in the most advanced version of how to do data entry you use a cursoradapter class which does create a separate cursor you use for the form grid, but that cursor is directly linked to the table (like an updatable view) and the save machanism therefore is exactly the same as when working with the table itself. I won't show this heree, as it adds even more complexity to the example without changing the general way to use buffers, transactions and TABLEUPDATE() to save a full order with all its items or nothing. Including creating the primary key (id, ref no) also needed as foreign key (reference of the order in order items).
That's the way you do it, mainly.
Cursoradapters or more general client/server and N-tier is another topic.
Conflict handling is yet another topic...
The main reason I show this is: You ARE programming buffering and how the buffer is added to the DBF etc yourself, you do what already is implemented in VFP, you do stuff not needed, you waste your valuable time for programming something that the system does for you. With a separate grid cursor you implement the buffering of data until you want to save it, but that's already a feature of workareas, you simply need to use it, not program your own buffering machanism via a separate cursor. Think about what situations you come across when editing an order, some of your cursor records then would need to go back into the DBF as Inserts, others as Updates and delted items also need to be deleted in the DBF, all this is done by the buffer and a single TABLEUPDATE() call. You better know buffers and TABLEUPDATE().
You may say it's fun to do this on my own, I can do it in my way, I have full control etc., but then you refuse to use something that exists for 15 to 20 years already and has proven to work in time and be secure and stable. Use the VFP internal features and learn about them.
What you'd need is more than a tutorial, you'd need a workshop, about a week learning with an instructor, not only about this topic.
Bye, Olaf.