Enrico Parisi 路 Mar 11, 2025 go to post

InterSystems offers specific training course for Building and Managing HL7 integration periodically available in InterSystems offices, remote/online and on customer site. 

To get detailed info and take advantage of what it's available in InterSystem Learning, click the Learning link on left top of this page.

There you can find plenty of material regarding HL7v2 and MUCH more.

An excellent starting point would be to start with what's freely available for self learning, with a quick search I found couple of Learning Path on HL7v2:

Building Basic HL7 V2 Integrations with InterSystems

Building Advanced HL7 V2 Integrations with InterSystems

In addition to these Learning Paths there are additional more specific resources for specific topics.

In the Developer Community there are many HL7v2 articles you can learn from and maybe browsing HL7v2 related questions and provided answers can help learning.

Last but not least, in Open Exchange (fourth link on top of this page)  also contains many HL7v2 projects you can learn from.

Regarding certification, please check InterSystems Certification Program, link on top of this page.

Enjoy learning! 馃槉

Enrico Parisi 路 Mar 13, 2025 go to post

Can you please provide some more details on "columns that are larger than the typical string length"?

How long (max) can be?
What's the data type for these columns in the external database?
What database are you connecting to? Using ODBC or JDBC?

Enrico Parisi 路 Mar 13, 2025 go to post

Hi Scott,

I don't consider 255 as larger than the typical string length and I'm surprised of your issue and I don't fully understand your code, probably because it's not complete (set tSC = rs.Insert(pInput) make no sense to me).

Anyway, my suggestion is to find some more info that may give you some hint.

For example I'd add the following lines in your OnProcessInput() method:

Set colId=pInput.GetColumnID("ExternalName")
$$$LOGINFO("ColumnType is "_pInput.GetColumnType(colId))
$$$LOGINFO("ColumnSQLType is "_pInput.GetColumnSQLType(colId))
$$$LOGINFO("ColumnSize is "_pInput.GetColumnSize(colId))

Please test this using a query that extract a few records to avoid flooding your event log.

I'm curious to see what you get.

Enrico Parisi 路 Mar 18, 2025 go to post

I wish I could see the code in $$macroERROR^%occMsgXML() (used by $$$ERROR() macro and other) to store the stack in a Status.

Enrico Parisi 路 Mar 19, 2025 go to post

Status already includes stack-trace

I  know, that is the very reason why I'm curious to see how it's implemented there!

My guess is that is using $ZU(41,-2).

Enrico Parisi 路 Mar 22, 2025 go to post

If the SetValueAt() method is not working I guess it's also returning an error, my suggestion is to check the returned error, so:

Set sc=tOBXSegment.SetValueAt(tOBXText, 5)
Do $system.OBJ.DisplayError(sc)

Enrico Parisi 路 Mar 23, 2025 go to post

For training and certification you get a corresponding Credly badge that you can add/share in your Developer Community profile or your Linkedin and other social accounts.

When you take an InterSystems training course you get attendance badge, for example:

Building and Managing HL7 Integrations Training

Certification is separate from training, when you pass a certification exam (performed by a third party entity) you get the corresponding certification Credly badge, for example:

InterSystems HL7庐 Interface Specialist

Enrico Parisi 路 Mar 25, 2025 go to post

I doubt it can be done with a generic process for any VDOC type.

For HL7 to get Virtual Property Path using names ("symbolic ones") you must know and use the "DocType" (that is the schema category and message structure) of the message.

In HL7, for a given HL7 message, it's not clear to me what you need/want as output, you want to visit all nodes/fields....defined? All possible nodes/fields defined in the schema structure? All nodes/fields that contains values?

If you need all possible nodes/fields for a given DocType, then you can get them from the Schema Definition.

Enrico Parisi 路 Mar 25, 2025 go to post

An idea, not sure if it fits in your case, if not...disregard it! 馃槉

1) Get the schema of your HL7 message (for example "2.5")
2) Loop the segments contained in your HL7 message (this should be fairly easy)
3) For each segment get all the fields defined for that segment on the schema it belongs to and check if contains data

To get all the possible fields of a segment, example for 2.5:PID (demo version):
Set rs = ##class(%ResultSet).%New("EnsLib.HL7.Message:EnumerateSegTypes")
Do rs.Execute("2.5:PID", 4, 1)
Do rs.%Display(",")

This way you should get all virtual path for fields with values in your message.

Repeating fields (like PatientIdentifierList()) of course require some extra coding to get/check them all.

P.S.: I think/guess 4 levels (second param in Execute()) is the maximum, otherwise....increase it.

Enrico Parisi 路 Mar 25, 2025 go to post

You keep not checking the status returned by SetValueAt() calls, that would probably gives clues/hints on the problem.

I suggest to change all calls with:
Set tSC=xxxx.SetValueAt(...)
$$$LOGSTATUS(tSC)
 

Enrico Parisi 路 Mar 25, 2025 go to post

In the code you posted that's not true for all SetValueAt().

Does any call returns an error? If so, what's the error?

Enrico Parisi 路 Mar 26, 2025 go to post

It's difficult to tell if the bad value is in the passed parameter or in the returned value.

Just as curiosity, what's the value of pRequest.Warehouse and what's the datatype of Warehouse column in the prod.stocks table?

Enrico Parisi 路 Mar 27, 2025 go to post

Hi @Aya Heshmat , please consider adding the host Pool Size in the Production Configuration page as it once was.

For details on this topic see my post/discussion:

Display Pool Size in Production Configuration page

and related Idea:

Add pool size in production configuration page

For us is REALLY important, at the moment (where we can) we have hacked/changed the Prod. Config. Portal page, in the future with the new modernized page this change may be difficult to implement/hack.

Enrico Parisi 路 Mar 27, 2025 go to post

I came across this old question and I was (almost) shocked by the answers because we are considering using System Default Settings for a project and one of the settings value we need to customize per system is Pool Size!

Fortunately things have changed since this was posted an now Pool Size can be defined/configured in System Default Settings and this is also documented:

Settings That Can Act As System Overrides

I'm not sure when this has changed,I believe this has been implemented in 2024.1, the version I tested and it works.

The behavior of this default setting a a little different then others (cannot be modified/overridden from within the production settings) but, IMHO, that's fine or even better!

I'm writing this to warn anyone using a recent product version looking/reading this question that now, from 2024.1 Pool Size can indeed be configured in System Default Settings.

Enrico Parisi 路 Mar 27, 2025 go to post

%ResultSet in deprecated, use %SQL.Statement instead.
Never concatenate parameters to query text, use placeholders ("?").

Set sql = "SELECT COUNT(*) FROM "_tableName_" WHERE "_fieldName_">=? AND "_fieldName_"<=?"Set resultset = ##class(%SQL.Statement).%ExecDirect(,sql,fromDate,currentDate)
Do tResult.%Display()
Enrico Parisi 路 Mar 28, 2025 go to post

Never concatenate parameters to query text, use placeholders ("?").

What's the status (SC) returned by the called methods?

In your code after each "SET SC=....:" add the line:

$$$LOGSTATUS(SC)

What's the status logged?

Enrico Parisi 路 Mar 29, 2025 go to post

It the issue of this question/post the "?wsdl" not working or that the SOAP pass-through does not work?

To get "?wsdl" to work you need an HTTP pass-through, technically it's not a SOAP call.
In fact the "?wsdl" parameter is interpreted by IRIS and you get the "wsdl" returned by EnsLib.SOAP.GenericService. 

I created the production and tested the call to the SOAP service (I'm using 2024.1 but I don't thin is relevant) and it works.
I'm using SOAP UI to call the service through the IRIS production and I get the correct result.

Apart from the "?wsdl" call, have you tried calling the actual SOAP service?

For simple cases I found that often using a HTTP pass-through instead of SOAP pass-through makes thing easier.

Enrico Parisi 路 Mar 29, 2025 go to post

Character set conversion should be /configured/applied in the receiving service/adapter.

Unfortunately you did not provide any useful details, so it's impossible to help.

Enrico Parisi 路 Mar 30, 2025 go to post

For the incoming HL7 messages, does the field MSH:CharacterSet (MSH:18) contains a value? Is so, what's the value?

You write that the receiver message is encoded using utf-8, have you tried to configure the "Default Char Encoding" setting in the Business Service to "utf-8"?

If the incoming HL7 message is actually/really encoded using utf-8 and MSH:18 contains a value different than "utf-8", you can enforce conversion configuring the "Default Char Encoding" setting in the Business Service to "!utf-8".

I hope the message is not malformed and contains data/fields with different codes, if so...it can be tricky.

Enrico Parisi 路 Mar 30, 2025 go to post

First you MUST be positively sure on what the character set is actually used in the incoming HL7 message.

What's the character set of the incoming message?

If it's really utf-8 then setting "Default Char Encoding" to utf-8 should work, if it does not, then evidently the incoming message use a different character set. 

Just in case, try setting "Default Char Encoding"  to "!utf8"

Enrico Parisi 路 Mar 30, 2025 go to post

While the suggested method should still work (haven't tested), I'd advise using the official method $system.SQL.Schema.ExportDDL()

See Class Reference for documentation details  and samples.

Enrico Parisi 路 Apr 21, 2025 go to post

In these cases I use Ens.InboudAdapter with a very high call interval so it runs only once when the BS is enabled (START action in schedule definition)

Enrico Parisi 路 Apr 22, 2025 go to post

Yes, you need to create your own class that  Extends Ens.BusinessService  that use Ens.InboundAdapter and and copy or call my code from OnProcessInput() method.

You can ignore pInput & pOutput arguments.

Enrico Parisi 路 Apr 24, 2025 go to post

Hi @Benjamin De Boe 馃槉

In general I agree 100%, BUT, if you need to add a favorite now, then the "right thing to do" would be:

  • Open a WRC and explain the issue...may take some time depending on your luck...
  • WRC open an issue, wait for developers to look at it and fix it
  • Wait for patch/ad-hoc, how long? Probably 2~3 weeks for this not so critical issue
  • Install the ad-hoc and...finally you get your favorite in Management Portal!

Another option is to fix it yourself, the problem is due to the property Data in %SYS.Portal.Users class that is defined as %List but in fact is used by the portal code as a string.
The "misuse" of Data property went unnoticed until version 2025 that implements/introduce %IsValid() method for %List datatype and that's why the %AddFavorite() method does not work anymore. Note that if you call %AddFavorite() passing (correctly) a %List ($list), then the method works, the favorite does not, because the portal code that read the Data property treat it as a String, not a $List!
Fixing this is simple, just edit %SYS.Portal.Users class (after modifying IRISLIB db to R/W and back when you are done...) and change Data datatype from %List to %String.

OR....just set the damn global, enjoy your favorite and go back to business! 馃榿

Anyone willing to report this to WRC? ...not me 馃槈

Enrico Parisi 路 Apr 26, 2025 go to post

"But the app could be installed in any database, right?"
I believe it's wrong, the app could be installed in any NAMESPACE.

Now the question is, what role have access to the databases associated with the namespace?

Leaving mappings aside, a namespace uses two databases, "Default Database for Globals" and "Default Database for Routines" (code), usually the two databases coincide but you cannot assume it's so.
When I configure two databases for a namespace I use a single resource for both, I consider this a good practice but, again, this cannot be assumed.

A generalized solution should find the resources used by the installation destination namespace.

This is how you can get the databases used by the namespace "MYAPP":

%SYS>Set sc=##Class(Config.Namespaces).Get("MYAPP",.NsProperties) 
%SYS>Write NsProperties("Routines")
MYAPP-R
%SYS>Write NsProperties("Globals")
MYAPP-G

Now, for each database you can get the associated resource with:

%SYS>Set dbr=##class(SYS.Database).%OpenId(##class(Config.Databases).GetDirectory(NsProperties("Routines"))) 
%SYS>Write dbr.ResourceName%DB_MYAPP%SYS> 
%SYS>Set dbg=##class(SYS.Database).%OpenId(##class(Config.Databases).GetDirectory(NsProperties("Globals"))) 
%SYS>Write dbg.ResourceName%DB_MYAPP

In this case for the MYAPP namespace you only need permission to the %DB_MYAPP resource.

If the two databases use different resources, then you need permission to both the associated resources.

Enrico Parisi 路 Apr 26, 2025 go to post

To address similar cases some time ago I developed a little utility to export to a %DynamicArray the output from an SQL query.
In my case I had to export existing classes that did not extend %JSON.Adaptor (I'm not even sure %JSON.Adaptor existed at that time).
 

Class Community.SQL2JSON
{

/// Execute am SQL query and returns a %DynamicArray containing the rows (as %DynamocObject) returned by the query/// RetDynArray is the returned %DynamicArray containing the results/// Parameters used by the query must be included in the query using placeholders (?) e passed By Reference in the local variable array ParamArray/// where the root node contains the number of parameters, ie:/// ParamArray = 1/// ParamArray(1) = 123ClassMethod QueryToJSON(ByRef SQLQuery As%String, Output RetDynArray As%DynamicArray, ByRef ParamArray As%String) As%Status
{
	Set sc=$$$OKTry {
		Set stSql=##class(%SQL.Statement).%New()
		Set stSql.%ObjectSelectMode=0Set rsSql=##class(%SQL.Statement).%ExecDirect(.stSql, .SQLQuery,ParamArray...)
		If rsSql.%SQLCODE < 0 {
			Set sc = $$$ERROR($$$GeneralError,"%SQLCODE="_rsSql.%SQLCODE_", %Message="_rsSql.%Message)
			Quit
		}
		If '$IsObject($g(RetDynArray)) Set RetDynArray = []
		While rsSql.%Next(.sc) {
			If$$$ISERR(sc) QuitSet RowDynObj={}

			Set sc=..RowToDynObj(stSql,rsSql,.RowDynObj)
			If$$$ISERR(sc) QuitDo RetDynArray.%Push(RowDynObj)
		}
	} Catch CatchError {
		#dim CatchError as%Exception.SystemExceptionSet sc=CatchError.AsStatus()
	}
	Quit sc
}

/// Convert a recordset in a %DynamicObject with property name equal to the column name/// If RowDynObj is passed, then adds the properties are added to it, otherwise creates and returns a new dynamic objectClassMethod RowToDynObj(StSql As%SQL.Statement, RsSql As%SQL.StatementResult, ByRef RowDynObj As%DynamicObject) As%Status
{
	Set sc=$$$OKTry {
		If '$IsObject($g(RowDynObj)) Set RowDynObj= {}
		
		For col=1:1:StSql.%Metadata.columnCount {
			Set ColumnName=StSql.%Metadata.columns.GetAt(col).colName
			Set ColumnValue=$Property(RsSql,ColumnName)
			Do RowDynObj.%Set(ColumnName,ColumnValue)
		}
	} Catch CatchError {
		#dim CatchError as%Exception.SystemExceptionSet sc=CatchError.AsStatus()
	}
	Quit sc
}

}

Using it is very simple:

EPTEST>Set SQLQuery="select Name, DOB as ""Birth Date"", Home_City as City from Sample.Person where Home_City=?" 
EPTEST>Set ParamArray=1 
EPTEST>Set ParamArray(1)="Newton" 
EPTEST>Set sc=##class(Community.SQL2JSON).QueryToJSON(SQLQuery,.RetDynArray,.ParamArray) 
EPTEST>Do RetDynArray.%ToJSON()
[{"Name":"Uhles,Susan D.","Birth Date":31836,"City":"Newton"},{"Name":"Ubertini,Debby N.","Birth Date":42513,"City":"Newton"},{"Name":"Harrison,Rob E.","Birth Date":62265,"City":"Newton"},{"Name":"Adams,Robert E.","Birth Date":62769,"City":"Newton"}]
EPTEST>
Enrico Parisi 路 Apr 26, 2025 go to post

Yes, it's normal, Web Applications are local configuration on each mirror member.

If/when you create a new Web Application you need to create it in all mirror members. Personally I prefer to create the application using a script (a class method) that is deployed and run (typically along with code etc.) in all members nodes.

In addition to Web Applications note that also all security related configuration (Users, Roles, Resources, SQL privileges etc.) are local to each mirror member.

It's my understanding that InterSystems is working on enhancing the mirror to include security configuration synchronization (Web Applications are part of the Security) between mirror member.

Enrico Parisi 路 Apr 28, 2025 go to post

If during installation your code "knows" what namespace is used, then you can get the associated resources as described in my previous post.

If you don't know the namespace, then...game over, no way.

Maybe you can run a setup script after install that add to your role the required resources?

Enrico Parisi 路 May 1, 2025 go to post

Configure a VIP for the mirror using built in IRIS VIP configuration, or, if you cannot use built in VIP, you can use an external balancer (F5, HAProxy etc.) to handle/create the VIP "externally".

Then instead of using the IP/name of the mirror members you use the VIP address/name to connect to the active mirror member node. Use VIP for ODBC and any other connection to IRIS.

You did not specified what kind of HealthShare environment you have in place (UCR?), depending of your HealthShare setup a VIP is a prerequisite for configuring an HealthShare  federation.