[UPDATE February 13, 2011]
PLEASE NOTE!
DI Commander has been embedded to DI Construction Kit, which supports also MS SQL 2008 and SAP B1 8.8.
You can download DI Construction Kit with documentation and full source code from these sites:
http://www.mediafire.com/di_construction_kit
If you have been involved with SAP Business One implementation projects, I'm sure you have at some point faced a situation where you needed to quickly modify a large amount of records in the customer's database. Here are some real-world examples from my past projects:
Being familiar with relational databases, I've pretty often found myself mumbling something like this:
"Dude, if they just would allow me to run a simple SQL update query in the database. This task would be completed in a few seconds."
Well, this is prohibited by SAP for rather obvious reasons. We need to get by with somewhat less powerful tools.
The most usual alternatives available to the consultant (ordered by hairyness factor, not by preference) to handle these tasks are:
None of the methods discussed above are intrinsically superior over each other. Sometimes a Caterpillar is better than a shovel and a shovel is better than a spoon, but not always. The selection of the most optimal tools depends on several variables, such as:
For instance, if you only need to delete 20 items once, the fastest method is obviously just to do it manually. However, when the number of items rises or the task is more complex and needs to be repeated over time (for instance, monthly contract invoicing), quite soon we are in a situation where taking the time to write a small application to do the task might save you valuable time and effort. That is, of course, if you have a developer available. While there are a big number of B1 consultants out there that do have a developer background, I think it's safe to assume that most of them don't.
Wouldn't it be nice to have a tool that had the full power of DI API but was about as easy to use as the Data Transfer Workbench or SQL queries ? Something that could be learned and used by a non-developer who is somewhat familiar with such tools as spreadsheet formulas/macros, SQL queries and/or command line shells. This is where DI Commander (DIC) comes into picture. DIC makes it easier, faster and more flexible to execute data modification tasks such as those mentioned above, but simultaneously remain within the safe boundaries provided by DI API. While the main target audience is consultants who are not developers, I think there are lots of developers who also might find some use for DIC.
With DIC, you don't need to care about initiating B1 sessions, as DIC does it for you. DIC supports multiple simultaneous sessions, so that you can for example read stuff from one B1 instance and write to another. The most interesting part of DIC is however the command host. My aim was to hide the complexities of "traditional" languages such as C# and VB in order to provide a more business logic -oriented user experience. While you can't totally avoid writing code when using DIC, there are obvious quantitative and qualitative differences. In the quantitative sense, there is a lot less code to be written in order to get a job done. While the quantity of the code has been reduced, the code is also much more transparent, revealing what is happening on the business logic level. This should make it much more approachable for non-developers. DIC has a code editor window, but it also has a shell window that imitates a command prompt. For many potential users, the command prompt may be more familiar than a developer-oriented IDE.
I am not endorsing DIC as a replacement for real task-specific business applications. However, if you just want a job done quick'n'dirty, it will provide you with the tools of the trade.
When I was reflecting on all the stuff I had previously done with C# and DI API, I realized that it was pretty much always variations from the same theme:
Based on these findings, I decided to equip DIC with some extras in order to get the much needed performance boost for the mentioned tasks. To be more precise, I added the following functions to DIC by modifying the embedded IronPython engine:
The get function is used for retrieving existing objects from the database or handles for creating new objects. It has four different variants:
For instance, to get a handle to the order nr. 50 that exists in the database, all you need to type is:
myOrder=get(ORDER, 50)
Now that you've got the handle, you could access the values like this:
myOrder.CardCode
...or perhaps update some value in the order:
myOrder.Comments="Just testing"
update(myOrder)
The objecttype parameter is a member of the BoObjectTypes enumeration. DIC generates a helper constants for each BoObjectType in DI API as follows:
If you are familiar with DI API documentation or DTW, you perhaps noticed that the helper constants are without the 'o' prefix and in singular form for clarity. If you prefer to use the original DI API notation for BoObjectTypes, you can do that as well. Just remember to type the whole namespace (for instance, SAPbobsCOM.BoObjectTypes.oInvoices instead of INVOICE).
By far the most important of the added functions is browse. Because DI API has been implemented as a COM object, its enumeration support in .NET environment leaves a lot of room for improvement. The browse function is used to create a multipurpose .NET enumeration wrapper for a variety of B1 objects:
With the enumeration interface in place, these object collections can be treated as any list in Python. With remarkable ease, that is.
There are five variants of browse():
For instance, to iterate the items in an order, you might type:
myOrder=get(ORDER, 50)
for myline in browse(myOrder):
print myline.ItemCode
print ","
The query function is used to retrieve a Recordset from DI API according to the specified query. It has two variants:
The Resultset retrieved by the query function can then be enumerated using the browse function.
The only reason for the existence of the add function is syntactical consistency with the other new functions. All is does is call the Add() method of the specified object instance):
As with the add function, the only reason for the existence of the update function is syntactical consistency with the other new functions. All is does is call the Update() method of the specified object instance):
The remove method can be used to removing any object that has the Remove() method. Naturally, the object can only be deleted if the consistency rules of B1 allows it to be deleted.
There are three variants of remove:
As you can see from the image above, the current user interface of DIC is rather spartan.
The shell window imitates a typical command prompt. It includes such functions as command history that can be accessed using the arrow keys. Currently, the shell window does not support statements that span several lines. However, you can combine multiple commands on a single line by separating them with semicolons.
The shell window will only show a short error message when an internal exception is caught. The Trace window will contain more details (stack traces etc). Normally you should not need to use the trace tab at all.
DIC can do the standard Python tricks even without logging in. However, in order to use the DI API, you need to initiate at least one B1 session by using the login screen (To be precise, you could do it straight from the code, but why bother?). DIC also supports the promiscuous mode, in which you can initiate multiple simultaneous sessions into different databases and do cross-database operations. For each session you initiate, you need to select a handle that can be used to refer to the session from the code. If you only have one session open, you don't need to use the handle (as the single session is automatically defined as the default session), but even then you need to give the handle when logging in. The system suggests "session1" as the handle, but you can change it if you wish.
This is an alternative to the Shell window. It works like a typical text editor: in addition to typing text, you can for instance copy and paste stuff around. Executing the code works a bit like the Query Analyzer window in MS SQL 2000: if nothing is selected, the application tries to execute every bit of code on the screen. If something is selected, the application will only execute the selected commands. Even when using the code window, the executed commands will be added to the command history of the shell.
Compared with the shell, the code window has a couple of bonuses. First of all, it supports multiple-line statements (just remember the indentation). Second, it also provides colour coding for the standard Python commands as well as for the functions added in DIC. Sorry folks, there's currently no IntelliSense function in DIC. However, in the Shell window you can get a list of the available fields and methods of any object by using the dir() function (please see the image below). I guess you could call that poor man's IntelliSense.
I will only scratch the surface of Python here. If you wish to get more thorough overview of the language, check out the Python.org website for a description of Python.
I chose Python as the language of DIC because Python has a very clear, human-readable syntax and it also lets you interact with live objects as you build your code.
As there are loads of excellent documents on Python out there in the web, I don't want to write another Python tutorial here. Instead, I will just give a couple of pointers to some of the available tutorials:
If you prefer real books, I warmly recommend Learning Python by Mark Lutz and David Ascher (publiched by O'Reilly).
Learning to program Python is not really required in order to benefit from DIC. Basically you just need to know the new functions added in DIC and some very basic commands in Python to get by. That's definitely not any harder than learning SQL.
I hope to be able to establish a public library of well-written DIC snippets in the future. This would make DIC even more approachable by the Layman.
Before diving deeper into the sample scripts, there's a few issues and conventions you should be aware of.
First of all, indentation (spaces and tabs) matter a lot in Python. They are used for identifying blocks of code that span several lines but belong to a single statement. These include for loops, function and class definitions etc. When you see an indentation in the sample code, it is there for a purpose. Omitting it will cause the code to fail (Please see the following image for an example on incorrect and correct indentation)
If you are a seasoned Python developer, you should also notice that there are a couple of Python conventions that are not currently supported by DIC (although they are supported in IronPython):
MyList = ["A","B","C"]
...is valid but the following isn't (although it is valid according to Python conventions):
MyList = ["A",
"B",
"C"]
I apologize for these limitations. I was forced to skip some cleaning up in order to get DIC out in a decent amount of time. After all, remember that I'm distributing DIC for free and doing it just for fun.
Let's add a new order (with two lines) to the system:
myorder=get(ORDER)
myorder.CardCode='CUSTOMER1'
myorder.DocDate=System.DateTime.Now
myorder.DocDueDate=System.DateTime.Now
myorder.Lines.ItemCode='DEMOITEM1'
myorder.Lines.Quantity=10
myorder.Lines.UnitPrice=5.5
add(myorder.Lines)
myorder.Lines.ItemCode='DEMOITEM2'
myorder.Lines.Quantity=8
myorder.Lines.UnitPrice=3.7
add(myorder)
To update a couple of fields for a single Business Partner with the CardCode 'CUSTOMER1', you might type something like this:
bp1=get(BUSINESSPARTNER, 'CUSTOMER1')
bp1.Cellular="+358 50 4324 1332"
bp1.Notes="Just testing."
update(bp1)
To do a similar change for all the business partners who belong to the default customer group:
for customer in browse(BUSINESSPARTNER, query("select cardcode from ocrd where groupcode=100")):
customer.Notes="Testing batch."
customer.Freetext="Testing batch."
update(customer)
To change the password for all users in the database (for instance after making a copy of the production database for testing purposes):
for user in browse(USER, query("select user_code from ousr")):
user.UserPassword="test"
if update(user)==0:
print user.UserName + ":OK "
else:
print user.UserName + ":Update failed "
To lock the warehouse 01 on all items in the database:
for itm in browse(ITEM, query("select itemcode from oitm")):
for whs in browse(itm.WhsInfo):
if whs.WarehouseCode=="01":
print " Locking warehouse 01 for item "+itm.ItemCode+":"
whs.Locked=SAPbobsCOM.BoYesNoEnum.tYES
update(itm)
The following examples focus on deleting items from the database. This is something you cannot do with the DTW.
To delete a single item with ItemCode 'DEMOITEM1' from the database, just type:
remove(ITEM,'DEMOITEM1')
...you may also build the list of items by defining a list such as this:
for itemcode in['A123','B124','C125']:
remove(ITEM,itemcode)
The above samples are nice for testing, but in a real world scenario you might perhaps want to read the itemcodes from a textfile with one itemcode per line:
for itemcode in open("c:\\itemstodelete.txt"):
remove(ITEM, itemcode)
...OR you might wish to use a resultset from a query as a source for the item codes:
rs=query("select itemcode from oitm where qrygroup64='Y'")
for itemcode in browse(rs):
remove(ITEM, itemcode)
Function calls can be nested for a more compact expression:
for itemcode in browse(query("select itemcode from oitm where qrygroup64='Y'")): remove(ITEM, itemcode)
(As the above can be expressed on a single line, you can use it in the shell window)
Naturally, any of the above samples could just as well have been done for any DI API object types (BusinessPartners, Quotation documents etc). Simply replace the value of the objecttype parameter with the one you need. of the box (as long as the objecttype in question has the Remove() method in DI API).
For instance:
for cardcode in browse(query("select cardcode from ocrd where cardtype='L'")):
remove(BUSINESSPARTNER, cardcode)
...this will remove all the BP's with type 'Lead' from your database (Please note! The same limitations apply as for the Remove() method in DI API. Thus, if there are transactions linked to a BP, it cannot be deleted).
Click here to get the latest version (R1.0 PL 3) of DIC. Currently only the binary version is available. I have not yet decided whether to publish the source code.
Installation is about as quick'n'dirty as you might expect: just unzip it and you're ready to rock'n'roll.
In order to get DIC up and running, you need to have a correct version of DI API installed. DIC was compiled against DI API 2005 SP1 PL23, but hopefully it will work fine against any version of DI API 2005.
In addition to DI API, you also need to have version 2.0 of the .NET Framework installed.
I've recently received comments and warnings from several people who've assumed that DIC is poking the database directly using SQL queries. I thought it would become clear from all that's written above, but obviously it wasn't: ALL THE DATA MANIPULATION OPERATIONS DONE WITH DIC ARE EXECUTED VIA THE DI API CALLING THE Update(), Add() and Remove() METHODS PROVIDED BY THE DI API OBJECTS. Thus, DIC is safe as milk. Or at least as safe as the DI API.
Recently, I was contacted by the moderators of this portal. It turned out that some people had somehow associated the acronym formed from the previous name (B1 Turbo Command Host) with some kind of an insult. Well, I apologize for that.
Still, if we start going down that road, it quickly becomes a slippery slope. There are simply too many words with several meanings and too many acronyms that may give wrong impressions when pronounced as a word. Try replacing "1" with "i" and pronouncing some of the most common acronyms used in the B1 Forum, then look up those words in Wikipedia. You might be surprised by what you find.
Anyway, I decided that it's better to change the name than to be totally censored. Thus, B1 Turbo Command Host is now DI Commander. I sincerely hope that this new name does not aggravate anyone. If it does, then the Beauty is certainly in the Eye of the Beholder.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
6 | |
2 | |
2 | |
2 | |
2 | |
2 | |
2 | |
1 | |
1 |