Archive for October 2014
From the Sage 300 ERP to Sage CRM integration there is the ability to run a number of Sage 300 ERP screens. These are the older VB screens being run as ActiveX controls from the IE browser. Not to be confused with the newer Quote to Order web based screens. A common request is how to customize these screens to you run the customized screen from Sage CRM rather than the base screen.
This blog posting covers how to run customized screens from Sage CRM. As a bonus, as part of this it also shows how to wrap a Sage 300 screen, so that it handles version updates seamlessly and doesn’t require you to re-compile your solution when we release a new version of the base screen. As a result this mechanism requires you use VB to wrap the base control for deployment. The ideas presented here probably can be ported to other programming systems, but it may not be easy.
A sample project that wraps Order Entry is located on Google Drive here. This project will be used for most of the examples in the document, so feel free to load it up and follow along. In order to view the wrapper, simply unzip the file, and open up the CRMOEOrderUI.vbp.
Create the Wrapper
The following instructions will show the basic steps on how to create a Sage 300 UI Browser Wrapper. The wrapper can then be referenced by an ASP page. There should be a constant interaction between the UI, the wrapper, and the ASP page (ie. UI calls UI_OnUIAppOpened in the wrapper, the wrapper raises the UIWasUnLoaded event to the ASP page, and the ASP page in turn catches the event, and closes the window containing the wrapper (and attached Accpac UI).
1. Open up Visual Basic and select a new Active X Control. Click Open.
2. Go to Project/ References, and select ACCPAC COM API Object, ACCPAC Data Source Control, ACCPAC Signon Manager, VB IObjectSafety Interface, ACCPAC Application Installer, and ACCPAC Session Manager.
3. The project name determines the name of the wrapper (OCX). In this case, the wrapper name will be “eCRMOEOrderUI”.
4. The name that you give the UserControl should be descriptive of what is contained on it. In this case, give the UserControl the same name as the Accpac UI that is wrapped (in this case, OEOrderUI).
5. When you are coding refer to the Accpac UI as “UserControl” (ie. UserControl.Width, UserControl.height).
6. We use the VBControlExtender to wrap the Order Entry OCX control dynamically when UserControl_Show is called (see code for UserControl_Show accompanied with this document). When referencing elements and methods within the Order Entry OCX control you would use ctlDynamic.object. The control is installed and opened using the AccpacOcxRegHelper.CLS which makes entries in to the registry that allows the VBControlExtender to reference the control by name as opposed to CLSID which is returned from Roto.
7. Now you are ready to begin writing the code that will catch the events thrown by the Accpac UI, and raise your own events to the ASP that will contain your wrapper.
8. Go into your code view and begin instantiating your events, objects, and variables.
9. Begin by declaring your objects that are going to handle events thrown by the AccpacDataSource controls in the related Accpac OCX controls. In this case, event handlers of the AccpacOE1100 class are being declared so that they can detect the events thrown by the class.
10. Next, declare the events that you will want to raise to the ASP page.
11. Declare your public variables
12. Declare your remaining variables. In this case, mSignonMgr is going to be used to sign on the Accpac UI with the signon manager so that the signon screen does not keep popping up every time that the UI is loaded. mlSignonID is going to be the signon ID.
13. Outline your functions that will be called by the ASP Page. In this case, the ASP page will give the values that are to be used to populate the UI, or to insert the customer ID into the UI’s customer field for a new customer quote.
14. Next, list out the events that can be called by the UI AccpacDataSources. In the screenshot below, you can see that the wrapper is checking the eReason variable being passed, and depending on what eReason is being passed, a different event will be raised to the ASP page (AddNew, Delete etc) in the RaiseEventEX sub.
15. Other functions are also called by the Accpac UI. The wrapper will be notified of these events through ctlDynamic_ObjectEvent (see below). Once the UI has opened ctlDynamic_ObjectEvent is called with an event name of “OnUIAppOpened” and a private sub UI_OnUIAppOpened is called and objects in the wrapper are initialized, and the UIWasLoaded event is raised to the ASP page notifying it that the UI has been opened.
16. Finally, define the Get properties that are available to the ASP page so that it can resize its windows when the UI has been loaded onto the ASP page. In this case, the ASP page will resize its windows to be the same width, height, and unit of measurement as the UI.
17. Now, you have successfully entered all the code that the wrapper will use to receive the function calls from the UI, as well as raise the events to the ASP page.
Customize the Sage CRM ASP Page
You now have a wrapped OCX now you can follow the ASP page in Sage CRM (for example, OE_OrderUI.asp as follows) to call your customized OCX.
Then it will open the OE Order Entry screen for order ORD000000000076.
In OE_OrderUI.asp file, it has following code:
eCRMOEOrderUI raises the following events:
UIWasLoaded(), UIWasUnLoaded(), AddNew(), Delete(), Update(), FieldChange(), Init(), Read(), Fetch()
eCRMOEOrderUI exposes the following Properties:
UIWidth(Read Only), UIHeight(Read Only), TwipsPerPixelX(Read Only), TwipsPerPixelX(Read Only)
eCRMOEOrderUI exposes the following Functions:
PopulateUI(OrderID As String, CustomerID As String);
CreateNewQuote(CustomerID As String);
<SCRIPT for=”eCRMOEOrderUI” Event=”UIWasLoaded()”>
var width = eCRMOEOrderUI.UIWidth / eCRMOEOrderUI.TwipsPerPixelX;
var height = eCRMOEOrderUI.UIHeight / eCRMOEOrderUI.TwipsPerPixelY;
if ((BrowserDetect.browser==”Explorer”) && (BrowserDetect.version >= 7))
width += 35;
height += 130;
width += 35;
height += 100;
var left = (screen.width – width) / 2;
var top = (screen.height – height) / 2;
width = eCRMOEOrderUI.UIWidth / eCRMOEOrderUI.TwipsPerPixelX;
height = eCRMOEOrderUI.UIHeight / eCRMOEOrderUI.TwipsPerPixelY;
BorderWidth = ClientWidth() – width;
BorderHeight = ClientHeight() – height;
bLoaded = true;
Hopefully you find this helpful in customizing Sage 300 ERP screens. Even if you don’t run them from Sage CRM, not having to re-build them for each Product Update can save you some time.
Back in January I wrote a blog on Branching by Feature. In this article I want to talk about some problems with branching by feature as well as talk about some other approaches. We’ve been doing branching by feature since a bit before the previous article was written and although some things are working out, there are definitely some disadvantages.
Becoming Overly Cautious
The idea of branching by feature is that features aren’t committed into the trunk until they are complete and fully tested. This sounds great, but it leads to some bad behaviors. People get overly cautious about merging their feature back into the trunk. People want to have a perfect merge and just keep delaying it and delaying it. Further the people using the trunk tend to resist merges and keep delaying them asking for more testing or more reviews.
This then leads to all the features being off on separate branches. But what if you are doing something that requires two of these features? You have no way to get them together. Then you have build servers continuously building all these branches, automated testing servers testing them and various other infrastructure being tied up.
Lack of Continuous Integration
To me the main drawback of branch by feature is a lack of continuous integration. Any bad interactions by the outstanding features are not found until much later. A lot of times when these problems are found, people claim it’s due to a lack of testing on the branch and that it was merged too early. But generally these problems couldn’t be discovered until the merge happens.
I tend to find that merge by feature just prolongs integration testing so long that a lot of serious problems are found so much later. Generally the longer between when a bug is introduced and when it’s discovered makes fixing it that much harder and more disruptive. If things are left too long then people have moved on to other projects and don’t like the distraction of going back.
Reducing Merge Hell
Another problem is that the longer things remain on branches, the more work it is to merge them back into the trunk. You can minimize this by continuously merging the trunk back into your branch. But then you have the overhead of continuously managing any conflicts. Plus there is a lot of room for error in this process. Every time you are resolving conflicts in a merge, you have the possibility of making a mistake and erasing someone else’s changes or introducing a bug.
This can lead to an extreme case of having to resolve hundreds of merge conflicts which always leads to errors and worse some pretty extreme conflicts between people or teams that have messed up each other’s code.
For an ERP package like Sage 300, they are composed of many modules like G/L, A/P, A/R, I/C, O/E, P/O, etc. We can source control the whole thing in one repository. This has the advantage that you get everything you need by extracting everything in the one repository. This can be quite convenient. However when you create branches when you merge them back in you do run the risk of conflicting with things that you didn’t expect. Often people just push those conflicts they don’t understand with their own changes. This usually then overwrites someone else’s work.
Currently we have everything together in one big repository, but we are going to break it up into separate repositories, one for each Accounting Module, one for System Manager, one for language translated strings, one for documentation, etc. This way we reduce the number of branches we need and we also reduce the danger of affecting things on too global a scale.
Reducing the number of branches greatly reduces the amount of complexity in the whole process. It also simplifies the process of merging features into the trunk.
This does mean there may be features that can’t be committed atomically as one transaction, it will require two commits in two separate repositories. However we feel that keeping down the scope of the commits is more important than strictly maintaining this atomicity.
You could do your branches at a lower level in the source code tree, but then if you find you need something elsewhere, it’s a fair bit of work to re-branch. This generally leads to people just including everything in their branch which then leads to all the merge problems.
Merge to Trunk Quicker
We are also working to get the different groups and teams to merge features back into trunk much quicker. We make it clear that we do expect to find problems, but that we want to find these earlier and have no expectation that all problems be found and fixed on feature branches. This way we can run the main full set of automated tests off of trunk and don’t need farms of automated test machines testing every branch.
Also for the consumers of these features, they will be all together quicker for people to use off the main builds. This way you won’t need to install multiple branch build to test multiple features.
As we move more and more into a cloud mode of software delivery, major releases become a thing of the past. In fact for cloud services we don’t really talk about releases anymore. We really just have a live service that is being continuously updated. To do this we need a good continuous delivery infrastructure and a reliable mechanism to develop individual features and merge them to trunk for immediate integration, full automated testing and then deployment to the live cloud service.
As we’ve been on this journey we keep tweaking our branching and build procedures to achieve this goal. Our first branching strategy was progress, but still led to a lot of problems that we are addressing with this new strategy.