SuperOffice CRM Professionals

Experienced experts in SuperOffice CRM - those primarily responsible for the setup, configuration, maintenance of SuperOffice in an organization, as well as those responsible for the implementation of applications or integrations, or provide consultancy services as to how an integration should be implemented using current industry standards.


Technical blog posts

Zip it up!

In the dynamic world of online application development , change is the constant companion of progress. Today, we're thrilled to announce a significant transformation that will streamline your access to essential resources and supercharge your development journey.   Since we launched the Developer Portal and its documentation a year ago, the existing general online application development documentation (such as onboarding and certification requirements) and the new portal-specific content have resided in two distinct sections. This division has existed both in our GitHub repository and on the SuperOfficeDocs website.   Simplified access and streamlined navigation   As of today, we are excited to reveal the seamless zip of these documentation sections. This merger represents a significant milestone in our ongoing commitment to enhancing your experience.   Navigating through documentation can sometimes feel like a maze, with valuable information hidden around corners. We've heard your feedback and concerns, and we're taking action. Finding what you need is now simpler than ever, whether you're an experienced developer or just starting out. There's no need to toggle between different sections or wonder where crucial information is located.   The Online apps / Application development section now seamlessly integrates with the Developer Portal section, unifying your access to a wealth of development resources. The URLs to the API references remain unchanged, ensuring consistency.   The merger's motivation   Why did we embark on this journey of merging these two vital sections? The answer is straightforward: efficiency and clarity . By consolidating these resources, we've eliminated redundancy and established a more logical flow of information. This translates into quicker and more effective learning, with less time spent searching and more time devoted to coding and building.   Content refinement   In tandem with this merger, we've used the opportunity to meticulously review and update our content. Our aim is to ensure that every piece of information is as relevant and accurate as possible, providing you with the most up-to-date guidance.   Can't Find What You're Looking For?   We understand that even with these improvements, you may occasionally find yourself searching for a specific page. In such cases, remember that in addition to the site-wide search functionality, you can easily filter the table of contents to pinpoint the exact page you need. Pages previously under and have been moved! If you get a 404 on your bookmarked link, try developer-portal instead. Also note that we introduced language specific URLs this spring and inserted / en after the domain.   In closing   We're excited about this change, and we believe it will significantly elevate your experience as developers and partners. The merged documentation is now live at , so go ahead and explore.   Your feedback remains a valuable compass as we continue to evolve and refine our resources to meet your ever-evolving needs. Thank you for being an integral part of the SuperOffice community!

Bergfrid Skaara Dias
thumb_up2 mode_comment1 visibility0

New Failures feature in Developer Portal

Hello, SuperOffice developers! We are excited to announce a new feature in the SuperOffice Developer Portal that will help you troubleshoot and fix any issues with your online integrations and applications. The feature is called **Failures** and it is documented at . The Failures feature allows you to view and analyze general application exceptions, and drill down into exceptions from Inbound requests. The Failed Outbound Requests tab is reserved for requests sent from SuperOffice to your applications, such as Quote/ERPSync calls and webhooks, but this part of the feature is still a work in progress (awaiting on more logging capabilities). To access the Failures feature, you need to sign in to the Developer Portal and select your app from the list of your apps. Then, click on the Failures tab on the left side of the app page. You will see a table with all the failed requests for your app in the last 30 minutes. You can click on any row to see more details about the failure. You can also use the filters on the top of the table to select the correct environment , and time range to narrow down the results. The Failures feature is available for all apps that use SuperOffice Online APIs.We hope that this feature will help you develop better apps for SuperOffice CRM Online and provide a better experience for your customers. If you have any feedback or questions about this feature, please let us know here in the forums or contact Thank you for using SuperOffice Developer Portal!

Tony Yates
thumb_up2 mode_comment0 visibility34

TravelTransactionLog changes

The TravelTransactionLog ("TTL") table dates back to the deep past of SuperOffice, when the Internet was a nice idea and crappy/expensive hotel phone lines were the reality. Being able to take the database with you was a real power feature. Even better, you took only a subset that was relevant to you, saving expensive megabytes on your little harddisk. NetServer still notes changes in TTL, and it's still valuable as a record of changes being made - at least to tables that were part of the Travel system. Most of this logging is relatively cheap and worth keeping. However, there are several special events that complicate the code and have some performance overhead. These are the changes that would, in the old days, have influenced the filters used to define the subset of the database that users would take with them: changes to contact.associate_id (and a few obscure tables only used for the Area Management configuration in Travel). The other category is logging of per-field changes on Udef tables. Both of these can result in additional database traffic (because we need to know the previous value), and we can't write the actual change until that information has been secured. In the TTL table, these events have event ID's higher than 4864. The basic events for Insert (4352, 0x1100), Update (4608, 0x1200) and Delete (4864, 0x1300) will be kept; but we want to stop logging all other events. If you have no idea what I'm writing about here - fine! No problem!   But if this causes cold sweat and incipient panic then I need to know. Cheers, Marek  

Marek Vokáč
thumb_up2 mode_comment3 visibility16

The TicketSentry is getting teeth

There has been a Sentry mechanism in NetServer for requests (well, the table is called ticket ) for many years. Ticket rights are not calculated in the same way as for Sales entities: instead of an owner and group system and a data access matrix, tickets belong to categories ( ej_category ) and those categories have masters and members. Finally, users belong to roles that have specific functional rights, and those functional rights are used to grant types of access to tickets. As with sale or appointment, access rights calculated for the root table will also lead to specific rights for derived tables (think appointment -> text). In the case of ticket, the ej_message table is the most interesting derived case. Think of it as category replacing the owner/group distance concept in Sales; and the functional rights correspond to the different columns in the data access right matrix in admin, for Sales entities. This TicketSentry in NetServer has, up to now, only worked with the type of access required for so-called List of tickets. In Service that means you get to see tickets in grids, but you can't change them and you can't see the messages belonging to those tickets. The List/View/Edit levels in Service don't have a direct counterpart in Sales entities, but it actually maps pretty well to table rights. List => ticket - Read , ej_message - None View => ticket - Read , ej_message - Read Edit => ticket - Full , ej_message - Full This is the mapping we'll have from now on. But, until now it was " if you have List, you get FULL on everything; otherwise you get NONE on everything; and we don't care about View or Edit ".   That wasn't really good, and it will now change to the rights mapping as outlined above. The rights calculations themselves do not change (no refactoring there!), so the various rules for members/masters/non-service-users etc are exactly the same as before. But they will now be enforced by the database layer in NetServer, and they will appear properly in the FieldRight dictionaries on TicketEntity carriers. As an example, if you perform a query that takes its first field from the ej_message table, and you do not have the right to see the ej_message part of some record in the result set, then that record will disappear completely. Even if it is joined to things that you do have access to - the ej_message part of the record will decide the outcome, because it is the first field you refer to. In other words - GetTicketMessageEntity(123) will start returning null if you don't have access to that message (calculated via its ticket, and you relationship to that). Likewise, a dot-syntax search where the first field is ej_message. will be treated in the same way - either you have access to that row, or you never see it. If you are getting hold of ej_message fields in some other way - say, as a dotsyntax query that starts with ticket - then the ticket may be there (if you have access), but fields belonging to ej_message may be blank (if you don't have access to that message). I will just reiterate that the rules have not changed. But we are starting to enforce them, within NetServer, using the standard mechanisms and behaviours there. Hopefully you're not dependent on the old, laissez-faire behaviour... it's going away.

Marek Vokáč
thumb_up3 mode_comment0 visibility5

From the Curiosity Cabinet: .ForEach()

Sometimes you discover somthing in a corner of the code and think, "it must have seemed like a good idea at the time". As you delve into it, you get a sense of why; and at the same time, understand why it wasn't through a somewhat deeper insight. Get a coffee and bear with me while I explain. This time it's about LINQ in .NET and how it works. I expect everyone who uses C# is familiar with it, and even loves the power. Here in SuperOffice we've generally landed on the fluent-chain-of-methods syntax rather than the sql-like one. To me that's because it saves me from a mental gear-change. But in any case, the resulting code is the same and the same thing happens. One of the core features of LINQ is deferred execution . When you say var things = myCollection.Where(x => x.ShouldKeep).Select(x => x.TheThing); then nothing is actually done - yet. myCollection is not called upon to enumerate its members, no filtering and no projection happens. Only the chain is built. Then, when you say foreach(var item in things) { ... } ... and only then ... are the operations performed. This comes from the signatures of the methods - they take an IEnumerable , and return an IEnumerable . The important aspect here is that an IEnumerable is something that can be enumerated/iterated over; but it's not an enumerator/iterator in itself and it certainly doesn't mean the thing-to-be-enumerated is an already-existing collection. It's very powerful. But this deferral of execution needs to end, sooner or later: we actually want things to happen and we want to see the result. In the case of a foreach loop, we get handed the items one at a time. Another way is to say .ToList() , or .ToArray() , .ToDictionary() (one of my favourites); there are others. Their return types are not IEnumerable - instead they return some result; and their implementations contain a foreach loop that "pulls" on the incoming IEnumerable, thereby triggering the execution of the whole pipeline. With this background, consider the following code: public static IEnumerable ForEach(this IEnumerable enumeration, Action action) { if (enumeration == null) throw new ArgumentNullException("enumeration"); if (action == null) throw new ArgumentNullException("action"); foreach (var item in enumeration) { action(item); yield return item; } } With this in place, you might think it reasonable to write listOfAppointments.ForEach(m => Assert.IsFalse(String.IsNullOrEmpty(m.AssociateName))); But... no. The catch here is that the ForEach method does have a loop iterating over the incoming elements, sure... but in turn it yields elements through its return value. Therefore, it's not going to do anything unless the next method in the chain performs an iteration. It simply won't be called, because of the special way IEnumerable works and how the compiler generates code. In the example above, there is no next method; these Asserts were simply never executed. I'm pretty sure the author of that code didn't think that was going to (not?) happen. Years ago I got yelled at by a partner because of this, and because our .ForEach was in the global namespace and masking his own (better) implementation - which took him some time to figure out. I recently removed this curious and confusing construction. While LINQ is great for data set processing, I still think that updating elements in place or complex processing can just as well be in an explicit foreach loop. It makes it more clear that this stuff is actually happening to each element. The somewhat contrived "convert foreach to LINQ" example on Microsoft Learn is actually a case of a foreach loop that yields something - obviously a LINQ candidate, but very different from a loop that computes or stores things (what I'd call an action).  Of course this is a matter of opinion. But I think everyone agrees that our little curiosity, a .ForEach that doesn't actually execute by itself, is not what you expect or want. There. Done, removed, get on with Friday :-) Marek  

Marek Vokáč
thumb_up2 mode_comment0 visibility6