You can use the detail bus object to read lines using GetResultSets or with a browse filter however you can not directly update lines from a detail bus object that was not spawned by the appropriate header object. So you could use one copy of the detail object to get the key information you need for each record and then use a copy of the header bus to first set the header to the correct sales order and then use its Lines property to update the appropriate lines.
I will add however that this project has room for some nasty issues to pop up, particularly around the timing of the update against the lines and the risk of one or more users having one of the affected sales orders open. In version 2018, if i open a sales order in Sales Order Entry and then use a script to open the same sales order using SO_SalesOrder_bus, i don't get any errors or warnings during the SetKey or SetValues, it isn't until i go to do the Write that it fails and the LastErrorMsg is "The record is in use by another user.".
These kind of things can be tricky depending on the target record to be updated since different objects behave differently when the same record is open by more than one user. CI_ItemCode_bus is one of these where it doesn't matter who opens the record first, the first instance to write changes to the record succeeds and any subsequent attempts to write changes where the record in memory is from before the changes written by the first instance will fail with the message that "the record has been changed by another user and the changes will not be saved.". This is due to the fact that when a task reads a record for Entry/Maintenance purposes, it keeps the original record's values in memory and compares that to the record's current values when attempting to write any changes to the record. If there is a mismatch, you get the message i described earlier. You can see this in action yourself with Item Maintenance, launch two of them and select the item code in each, you won't get any warnings. Then make a change in one and accept the changes, go to the other, make a change and attempt to accept the changes, you should then get the message i described earlier.
If you decide to pursue this, you will need to come up with some way to defer updates if the record is in use or figure out how you want to address these exceptions. Using a UDT could work to write the changes required to it with the key as the primary key for SO_SalesOrderDetail, a field for just the sales order number, a field for the line key, a field for the new date, you may want a field to act as a time stamp of sort for sorting purposes, and maybe a field to act has a flag of some sort depending on how you pursue the deferred update. You could try a table script on the UDT to attempt to update the sales order, if the update fails, then the record stays in the UDT, if the update succeeds, delete the record from the UDT (you could defer this logic to the scripts mentioned below). You could then use a scheduled VI Import job set up to use ODBC as the source and point it to either a silent DSN on the SOTAMAS90 DSN and use the key button to enter the user and company separated by the vertical pipe symbol and the password. You would then set up the query to just pull from the UDT and using each of the fields in it, you should be able to update the lines. You should sort the record set by time stamp if you decided to use it and then by the sales order number and line key. You would need a script on the sales order detail to check the UDT, this would probably be best on the detail's post write, in which you would need to get the key information and the promise date that was just set and then look for a matching record, if a matching record is found, you would then want to set a storage variable to act as a flag using the script object of either the header object or the session object so a script on the post write of the header can check if the flag was set, since this is in the post write, you can be positive the promise date was updated, you would then either need to loop through the lines again to find the matching lines between the detail and the UDT to delete the correct records from the UDT or use another storage variable to hold a delimited string compiled for each primary key of the matching records found in the UDT that was handled earlier by the detail's post write. Another way to handle clearing the correct records from the UDT would be to use a flag field in the UDT, so, on the post write of the detail, you would set the flag of the matching UDT record to something, then in the post write of the header, you could use GetResultSets against the UDT on the sales order number field and the flag field so they match the current sales order and whatever flag value you decide on. Also, i don't recall of the top of my head whether or not a sales order line can be linked to more than on PO (one to many), if it can, you are going to want to address this too, such as if one PO is expected to come in sooner but doesn't have sufficient quantity for the line, which required date should you use?
Depending on how close to real time they want the promise date updated, you might be able to just get away with a scheduled VI Import job set to run daily at a time when the client is positive sales orders are not going to be maintained, you would then just query against sage 100 like i described earlier but craft the SQL statement to have all the right info needed to update the lines from the queried PO data.
These are just some ideas but as you can see, it can get pretty messy. I'm sure there are other potential ways to handle this as well.
Just make sure you determine how critical that promise date is to your client and determine if you want to accept liability for when/if you solution fails to update a promise date because of the timing.
Well, enough of my ramblings, good luck.
------------------------------
David Speck II
Tennessee Software Solutions
------------------------------