Event handling on save button click - abap

I need to simply check if entered data in maintenance view, which is called via 'call transaction' fits under certain criteria. But the main requirement - is to perform this check when user clicks on save button. Is such handling even possible or at least sane?

To check whether the data exists in another table, define a foreign key relationship between the tables. This will make the generated view maintenance program automatically check the validity - no need to code anything.
To call the view maintenance, use the function module VIEW_MAINTENANCE_CALL (and read its documentation). This will give you a much more fine-grained control over the application.

Related

Instantly "locking" a record in multi-user Access environment

So, record-locking in Access is pretty awful. I can't use the built-in record locking because it locks a "page" of records instead of just the individual records (I've tried changing the settings for using record-level locking, but it's still locking a page instead of just one record), but even if I could get that working, it wouldn't solve my issue because the record doesn't lock until the user starts to make changes in the form.
The issue is, when two people open the same record, they can start making changes and both save (thus overwriting the earlier change). To make matters worse, there are listboxes on the form that link to other tables (keyed on an ID) and the changes they make to those tables are then overwritten by any change that comes after if they both opened the same record.
Long story short, I need to make sure it's impossible for two people to even open the same record at the same time (regardless of whether or not they've made any edits to it yet).
To do this, I added a field to the table which indicates if a record has been locked by a user. When they open a form, it sets their name in the field and other users who try to open that record get a notification that it's already locked. The problem is, this "lock" isn't instantaneous. It takes a few seconds for other users to "detect" that the record is locked, so if two people try to open the same record at roughly the same time, it will allow them both to open it. I've applied a transaction to the UPDATE statement that sets the lock, but it still leaves a short window wherein the lock doesn't "take" and two people can open the same record.
So, is there a way to make an UPDATE instantaneous (so all other users immediately see its results), or better yet, a robust and comprehensive way to lock records in an Access multi-user environment?
It not clear why you only receiving “page” locking.
If you turn on row locking in file->options, then you ALSO need to set the particular form to lock the current record. So just turning on record locking will not help you. That setting ONLY sets the default for new forms - it is not a system wide setting.
If you correctly turn on locking for a form, then if two users are viewing the same record and one user starts to edit the record, then all others CANNOT edit the record. Any other user attempting to edit a record will see a “lock” icon in the record selector bar (assuming record selector is turned on for the given form). They also will receive a "beep" if they try to type into any editable control on the given form.
And when they try to edit, they will see a visible "lock" icon on the selector bar like this:
A few things:
If two users are able to edit a record, then you not have turned on locking for that given form. This feature MUST be set on a form-by-form basis. Changing the setting in file->options->client setting ONLY SETS THE DEFAULT for NEW forms you create! So the setting ONLY applies to the default for new forms – it does NOT change existing forms.
So setting record locking is ONLY a form-by-form setting.
So you ALWAYS MUST set each form you want locking to the current edited record. You set this in form design, in the data tab of the properties sheet like this:
And also keep in mind that the setting of record level locking (a different setting and feature) is an Access client setting and does NOT travel with the given application.
So since you state that two users can edit the same record, then CLEARLY you NEVER turned on record locking for that given form. The systemwide “default” record locking ONLY sets the above form default (so existing forms you have are NOT changed).
Next up:
The setting of [x] Open database by using record-level locking is an Access client setting and NOT saved with the application. So this is an Access-wide setting, not an application setting, nor one that travels with the application.
So you have to set this on each client workstation, or you have to set this in your start-up code.
If you can’t go around and change each workstation to change this setting (or you are using the Access runtime), then you can use this VBA in your start-up code to set this feature:
Application.SetOption "Use Row Level Locking", True
Note that the setting does NOT take effect until exit the application, but that’s really a “non” issue since this means the first time you run this code, some users might well be in page locking mode, and others in row locking mode. Most of the time this causes little issue.
However the next time any user launches the application then they will be in row locking mode.
I have in the past also written custom locking code. And can outline how to make this work well, but from what you posted so far, you never turned on or set locking nor had locking working correctly for any of the forms you have now anyway.
OK, I finally figured out all of the issues contributing to this and worked out a solution.
The problem is multi-faceted so I'll cover the issues separately:
First issue: My custom locks weren't instantaneous. Even though I was using a transaction, there were several seconds after a lock was placed where users could still access the same record at the same time. I was using CurrentDb.Execute to UPDATE the record and Workspaces(0).BeginTrans for the transaction. For some reason (despite Microsoft's assurances to the contrary from here: https://msdn.microsoft.com/en-us/library/office/ff197654.aspx) the issue was that the transaction wasn't working when using the Workspaces object. When I switched to DBEngine.BeginTrans, the lock was instantaneous and solved my immediate problem.
The irony is that I almost always use DBEngine for my transactions but went with Workspaces this time for no reason, so that was a bad move obviously.
Second issue: The reason I had to use custom locking in the first place was because record-level locking wasn't working as expected (despite being properly configured). It was still using page-level locking. This was due to a performance trick I was using from here: https://msdn.microsoft.com/en-us/library/dd942824%28v=office.12%29.aspx?f=255&MSPPError=-2147217396
The trick involves opening a connection to the database where your linked tables are contained, which speeds up linked table operations. The problem is that the OpenDatabase method is NOT compatible with record-level locking so it opens the db using page-level locking, and since the first user to open a database determines its lock level (as explained here: https://msdn.microsoft.com/en-us/library/aa189633(v=office.10).aspx), all subsequent connections were forced to page-level.
Third issue: My problem is that my forms are not just simple bound forms to a single table. They open a single record (not allowing the user to navigate) and provide several functions which allow the user make modifications which affect other records in other tables that are related to the record they're editing (through comboboxes and pop-up forms and what not). As a result, I can't allow two people to open the same record at the same time as it leaves way too many opportunities for users to walk over each others' changes. So even if I remove the OpenDatabase performance trick, I'd still have to force the Form to be Dirty as soon as they open it so the record locks immediately and no one else can open it. I don't know if this would be as instantaneous as my custom locking and haven't yet tested that aspect.
In any event, I need a record to be locked the instant a user opens it and for now I've decided to keep using my custom locking (with the fix for the transaction). If something else comes to light that makes that less than ideal, I can try removing the OpenDatabase trick and switching to Access's built-in locking and force an immediate lock on every record when it is opened.
You could use the method described here:
Handle concurrent update conflicts in Access silently
to handle your lock field.
Since Access doesn't make locking records easy, I'm wondering if you were to add a table with locked record entries whether that would solve the problem even though it would be the "duct tape, soup can and coat hanger" solution: You create a "Locked_Record" table with 2 fields a) record ID being updated and b) the user name of the person updating that record. That table would control exactly who owns and therefore can edit what record. Your form would have a search field and when the search term is entered and "Enter" pressed the form would search for the record by looking for it in the data and looking for it in the Locked_Record table. If found in the Locked_Record table, then you user gets an error saying "Record in use already" and display who owns the record. If not found in the data then the appropriate message is displayed. If found in the data and not found in the Locked_Record table, then a Locked_Record entry would be created and the user would then get the data displayed in the form. At this point nobody else can edit that record. When the user is done updating, either the user would need to press a button saying "Done updating" or the form would have to be closed. Either way the Locked_Record entry would be deleted so others could use that record. If the record owner doesn't close out the form or doesn't press the button then that is a training issue. This method could be user for multiple entities such as Customers, Employees, Departments, etc. You would just have to assure your application and DB is set up so any sub-forms used which might lock other tables would ONLY affect that record's entries in the other tables.
I know this is a bit old but the information here inspired me to to use the following. Basically, the me.txtApplication is a text box on the bound form. The form is bound to the table and is set to lock the edited record in the property section. This code won't do anything other than trigger that editing lock and promptly undo the change. If another user tries to load the same record it will attempt to do the same edit, trigger the error, and move to the next record or start a new record without the user being the wiser.
'Lock current record with edit-level lock by editing and removing the edit from a
field.
'If record is already locked, move to next record.
On Error Resume Next
Me.txtApplication = Me.txtApplication & "-%$^$^$$##$"
Me.txtApplication = Replace(Me.txtApplication, "-%$^$^$$##$", "")
If Err.Number = -2147352567 Then
If Me.CurrentRecord < Me.Recordset.RecordCount Then
DoCmd.GoToRecord , , acNext
Else
MsgBox "No available records.", vbOKOnly, "No Records"
DoCmd.GoToRecord , , acNewRec
'[If the condition is not true, then we are on the last record, so don't go
to the next one]
End If
End If
End Sub

Is there any way to revoke access from modules after a particular time?

We want to make the modules readonly. After clicking on the parent menus, all the submenus should also be readonly.
Tried fields_view_get() but there are too many forms to write the method for!
So is there any other way to do it. We are disabling the modules forever based on a particular date.
Please help.
The algorithm that you can follow to achieve this will depend on what kind of access do your users have to your server. If they do not have access to the Settings menu, then what you can do is have a cron job run every X minutes that validates their subscription for your modules.
If the cron job finds that the their subscription is invalid and you want to make some menus unclickable or some views read only you can run the following query against the database to find out what views does a particular module create:
select * from ir_model_data where module = 'your_module_name';
The ir_model_data model actually contains all the keys for data your module contains. All the menus, views, groups etc you created using XML files (or not) are there.
Once you get all the records in your cursor you can cr.fetchall() and then loop on the return value. Each record will have a model and an res_id. When you see that you have a menu or a view that interests you you can set its active field to false to completely hide the view or menu.
When you loop on your results and find a field (that is a record that has model='ir.model.fields', you can set the readonly field to True so that this field is readonly. You can do this for every field.
As you can understand this approach is not bullet proof. If your users have access to the settings (ie. admin rights) then you must not use Odoo's cron jobs and rather configure a cron job to be run from your Operating System to invoke an external Python script that will do the process described above.
Also, if you set the views/menus/fields readonly, they might be rendered as readonly on the web browser, but there is nothing stopping an end user for crafting a rpc request and sending inappropriate json data.
If the users have SSH access to your server there is not much you can do.

Access: Update and Append in same form

I am new to using the front end tools/VBA in access, but I am an advanced programmer.
I have a situation where I need to record the date, and method of commute, for a particular individual on a monthly basis. What I have done so far is programatically create a form that lists every day of the month and has a blank space next to it for the user to enter in how he commuted. User only has to fill in if he/she clean commuted. The question is, how do I:
1. Select Records that have already been entered for a month and populate them to a line on my form.
2. Show a blank space for a user to enter info on any date that is not associated with a record.
3. Update any records they changed.
4. Append any new records they enter in the blanks.
I need to do this all on one form so it is easy. I cannot ask the users to navigate.
I appreciate any suggestion that might lead me to a solution. Many thanks.
A standard bound form will do that.
Use the wizard to get started and specify your table as the source.
After much testing and working (strange not find examples of this), I have determined the way to do this.
Do not programmatically create the form.
a. Forms in Access are tied so closely to accesses "form binding" functionality that there are to many limitations trying to work with a programmatically created form. It's very difficult to do.
A better solution is to programmatically create a temporary table that has the correct data structure and bind the form to that temp table.
a. thus the only programmatic part of your form is setting the bindings. Once that is done, you can rely on the form binding in Access to work easily with your Select, Update, and Append functions.

How to explicitly call TIBDataSet.RefreshSQL

I have list of records in TIBDataSet (Embarcadero Delphi) and I need to locate and modify one record in this list. There is chance that underlying database record has been changed by other queries and operations since TIBDataSet had been opened. Therefor I would like to call RefreshSQL for this one record (to get the latest data) before making any changes and before making post. Is it possible to do so and how?
I am not concerned about state of other records and I am sure that the record under consideration will always be updated and those updates will be commited before I need to changes this record from TIBDataSet.
As far as I understand then RefreshSQL is used for automatic retrieve of changes after TIBDataSet has posted upates to database. But I need manual (explicit) retrieval of the latest state before doing updates.
Try adding a TButton to your form and add the following code to its OnClick handler:
procedure TForm1.btnRefreshClick(Sender: TObject);
begin
IBQuery1.Refresh; // or whatever your IBX dataset is called
end;
and set a breakpoint on it.
Then run your app and another one (e.g. 2nd instance of it) and change a row in the second app, and commit it back to the db.
Navigate to the changed row in your app and click btnRefresh and use the debugger to trace execution.
You'll find that TDataSet.Refresh calls its InternalRefresh which in turn calls TIBCustomDataSet.InternalRefresh. That calls inherited InternalRefresh, which does nothing, followed by TIBCustomDataSet.InternalRefreshRow. If you trace into that, you'll find that it contructs a temporary IB query to retrieve the current row from the server, which should give you what you want before making changes yourself.
So that should do what you want. The problem is, it can be thoroughly confusing trying to monitor the data in two applications because they may be in different transaction states. So you are rather dependent on other users' apps "playing the transactional game" with you, so everyone sees a consistent view of the data.

Lotus Notes: Replication conflict caused by agent and user running on the document at same time

In one of the lotus notes db, too frequent replication/save conflicts are caused reason being a scheduled agent and any user working on the document at the same time.
is there any way to avoid this.
Thanks,
H.P.
Several options in addition to merging conflicts:
Change the schedule The best way to avoid it is to have your scheduled agents running at times when users are not likely to be accessing the system. If the LastContact field on a Client document is updated by an agent every hour as it checks all Contact documents, maybe the agent should run overnight instead.
Run the agent on user action It may also be the case that the agent shouldn't be running on a schedule, but should be running when the user takes some action. For example, run the agent to update the Client document when the user saves the supporting Contact document.
Break the form into smaller bits A third thing to consider is redesigning your form so that not every piece of data is on a main form. For example, if comments on recent contacts with a client are currently held in a field on the Client document, you might change the design to have a separate ClientMeeting form from which the comments on the meeting are displayed in an embedded view or computed text (or designed using Xpages).
Despite the fact that I am a developer, I think rep/saves are far more often the result of design decisions than anything else.
You can use the Conflict Handling option on the form(s) in question and select either Merge Conflicts or Merge/No Conflicts in order to have Notes handle merging of edit conflicts.
From the Help database:
At the "Conflict Handling" section of the Form Info tab, choose one of the following options for the form:
Create Conflicts -- Creates conflicts so that a replication conflict appears as a response document to the main document. The main document is selected based on the time and date of the changes and an internal document sequence number.
Merge Conflicts -- If a replication conflict occurs, saves the edits to each field in a single document. However, if two users edit the same field in the same document, Notes saves one document as a main document and the other document as a response document marked as a replication conflict. The main document is selected based on the criteria listed in the bullet above.
Merge/No Conflicts -- If replication occurs, saves the edits to each field in a single document. If two users edit the same field in the same document, Notes selects the field from the main document, based on time and date, and an internal document sequence number. No conflict document is generated, instead conflicting documents are merged into a single document at the field level.
Do Not Create Conflicts -- No merge occurs. IBM® Lotus® Domino(TM) simply chooses one document over another. The other document is lost.
In later versions of Notes there is the concept of document locking, and used properly that can prevent conflicts (but also add complexity).
Usually most conflicts can be avoided by planning to run the agents late at night when users aren't on the system. If that's not an option, then locking may be the best solution. If the conflicts aren't too many, you might benefit from adding a view filtered to show only conflicts, which would make findind and resolving them easier.
IMHO, the best answer to conflicts between users and agents is to make sure that they are operating on different documents. I.e., there are two documents with a common key. They can be parent and child if it would be convenient to show them that way in a view, but they don't have to be. Just call them DocA and DocB for the purposes of this discussion.
DocA is read and updated by users. When a user is viewing DocA, computed field formulas can pull information from DocB via DbLookup or GetDocField. Users never update DocB.
DocB, on the other hand, is read and updated by agents, but agents only read DocA. They never update them.
If you design your application any other way, then you either have to use locking -- which can create the possibility of not being able to update something when you need to, or accepting the fact that conflicts can happen occasionally and will need to be resolved.
Note that even with this strategy, you can still have conflicts if you have multiple replicas of the database. You can use the 'Conflict Handling' section of the Form properties to help minimize replication conflicts, as per #Per Henrik Lausten's answer, but since you are talking about an existing please also see my comment to his answer for additional info about what you would have to do in order to use this feature.
If this is a mission critical application, consider creating a database with lock-documents. That means, every time a user opens a document, a separate lock-document is created.
Then code the agent to see if lock-documents exist for every document that the agent wants to modify. If it does, skip that document.
Document-close should remove the doc-lock.
The lock-doc should be created on document open, not just read. This way, when a user has the document open in read mode, the agent will not be able to modify as well. This is to prohibit, that the user might change to editmode afterwards and make changes.
If the agent has a long modification time, it should create lock-docs as well.