Skip to main content
12-Amethyst
April 21, 2025
Solved

How to create WTPartReferenceLink using Windchill REST Services in Windchill PDMLink

  • April 21, 2025
  • 3 replies
  • 2437 views

Version: Windchill 12.1

 

Use Case: Using Windchill REST Services to creating a Referenced By link between a Part and a Document


Description:

I have followed this guide to successfully create a customization to create a WTPartDescribeLink using WRS: https://www.ptc.com/en/support/article/CS285572?source=search

 

The guide also mentions how to apply the same steps for WTPartReferenceLink, but I am receiving an error and would appreciate some guidance.

 

URL: POST https://mywindchill.portal.ptc.io:8443/Windchill/servlet/odata/ProdMgmt/Parts('OR:wt.part.WTPart:226444')/References

 

{
 "ReferencedBy@odata.bind": "PTC.ProdMgmt.CustomCompanyParts('OR:wt.part.WTPart:226444')",
 "References@odata.bind": "PTC.DocMgmt.Documents('OR:wt.doc.WTDocument:226427')"
}

 

The error:

{
 "error": {
 "code": null,
 "message": "Unable to create entity - Cannot cast wt.doc.WTDocumentMaster to wt.vc.wip.Workable"
 }
}

 

I would really appreciate some help resolving this issue.

 

Attached is my code for this, which was uploaded to "/codebase/rest/ptc/domain/ProdMgmt/v7/entity". I took the working copy used in the tutorial for PartDescribesLink and modified it for this. I must be missing something!

 

Part of my struggle is that I do not know how to troubleshoot this. I attempted to add logging statements following this guide like so:

 

var mylogger = log4javascript.getLogger("PTC.mypackage");
mylogger.trace("...");

 

However, this returns the error "log4javascript is not defined". I suspect there's more to do than what the guide says. I feel like I'm flying blind, I am editing some code with Notepad++ and having to restart the server takes 15 minutes anytime I want to test something.

 

Perhaps this is an easy fix, but I looked at other related topics for my issue, but no resolution was posted, so any additional help would be very much appreciated!

Best answer by CB_12625553

Here is the solution code that you will need:

 

PartReferenceLink.js

function getRelatedEntityCollection(navigationData) {
 var HashMap = Java.type('java.util.HashMap');
 var WTArrayList = Java.type('wt.fc.collections.WTArrayList');
 var Collections = Java.type('java.util.Collections');
 var VersionControlHelper = Java.type("wt.vc.VersionControlHelper");
 var SessionHelper = Java.type('wt.session.SessionHelper');
 var AccessControlHelper = Java.type('wt.access.AccessControlHelper');
 var AccessPermission = Java.type('wt.access.AccessPermission');
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var AccessResource = Java.type('wt.access.accessResource');

 var targetName = navigationData.getTargetSetName();
 var sourceLinks = new WTArrayList(navigationData.getSourceObjects());
 var user = SessionHelper.getPrincipal();

 if ("References".equals(targetName)) {
 var map = new HashMap();

 try {
 SessionHelper.manager.setAdministrator();

 for (var i = 0, l = sourceLinks.size(); i < l; i++) {
 var sourceLink = sourceLinks.getPersistable(i);
 var document = sourceLink.getRoleBObject();

 var latestDocument = VersionControlHelper.service.getLatestIteration(document, false);
 var hasAccessToReferencedBy = AccessControlHelper.manager.hasAccess(user, latestDocument, AccessPermission.READ);

 if (hasAccessToReferencedBy) {
 map.put(sourceLink, Collections.singletonList(latestDocument));
 } else {
 var errorMsg = WTMessage.getLocalizedMessage(AccessResource.class.getName(),
 AccessResource.SECURED_INFORMATION, null, navigationData.getLocale());
 throw new ODataApplicationException(errorMsg, HttpStatusCode.FORBIDDEN.getStatusCode(),
 navigationData.getLocale());
 }
 }
 } finally {
 // de-elevate administrator
 SessionHelper.manager.setPrincipal(user.getName());
 }
 return map;
 } 

 return new HashMap();
}

function processAdditionalAttributes(entity, object, data, processor) {
 var ArrayList = Java.type('java.util.ArrayList');
 var WTPart = Java.type('wt.part.WTPart');
 var WTDocument = Java.type('wt.doc.WTDocument');
 var NmOid = Java.type('com.ptc.netmarkets.model.NmOid');
 var EntityIDUtils = Java.type('com.ptc.odata.core.entity.processor.EntityIDUtils');
 var AttributeData = Java.type('com.ptc.odata.windchill.entity.processor.AttributeData');
 var EntityOperation = Java.type('com.ptc.odata.core.entity.processor.EntityOperation');
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var ProdMgmtRB = Java.type('com.ptc.odata.windchill.part.entity.ProdMgmtRB');
 var AssociationConstraintHelper = Java.type('com.ptc.core.meta.type.mgmt.server.impl.association.AssociationConstraintHelper');
 var ClientTypedUtility = Java.type('wt.type.ClientTypedUtility');

 var attributes = new ArrayList(2);
 if (data.getOperation() == EntityOperation.CREATE) {
 var referencesIdStr = EntityIDUtils.getInstance().getBindingLinkId(data, entity, "References");
 var referencesOid = referencesIdStr != null ? NmOid.newNmOid(referencesIdStr) : null;
 if (referencesOid != null && referencesOid.isA(WTDocument.class)) {
 attributes.add(new AttributeData("references", referencesOid.getRefObject().getMaster()));
 }

 var referencePart = data.getSourceObjects().iterator().next();
 attributes.add(new AttributeData("referencedBy", referencePart));
 
 // make sure our objects are valid for the link constraints
 if (!AssociationConstraintHelper.service.isValidAssociation(
 referencePart,
 ClientTypedUtility.getTypeIdentifier("wt.part.WTPartReferenceLink"),
 referencesOid.getRefObject())) {
 var errorMsg = "Invalid types for link, does not meet relationship constraints";
 throw new ODataApplicationException(errorMsg, HttpStatusCode.FORBIDDEN.getStatusCode(), data.getLocale())
 }
 }

 return attributes;
}


function toObjects(entities, processorData) {
 var ObjectAttributeProcessor = Java.type('com.ptc.odata.windchill.entity.processor.ObjectAttributeProcessor');
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var ProdMgmtRB = Java.type('com.ptc.odata.windchill.part.entity.ProdMgmtRB');

 // create the object the normal way
 var entityToObjectMap = ObjectAttributeProcessor.newInstance().toObjects(entities, processorData);


 return entityToObjectMap;
}

function checkForCheckout(newObject, createdEntity, processorData) {
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var ProdMgmtRB = Java.type('com.ptc.odata.windchill.part.entity.ProdMgmtRB');
 var WorkInProgressHelper = Java.type('wt.vc.wip.WorkInProgressHelper');
 var SessionHelper = Java.type('wt.session.SessionHelper');
 
 var referencePart = newObject.getReferencedBy();
 
 // check that the part is checked out and it is checked out to us
 if (!WorkInProgressHelper.isCheckedOut(referencePart, SessionHelper.getPrincipal()) ||
 !WorkInProgressHelper.isReservedWorkingCopy(referencePart)) {
 var errorMsg = "Part must be checked out and be the working copy to add, update, or delete WTPartReferenceLink";
 throw new ODataApplicationException(errorMsg, HttpStatusCode.FORBIDDEN.getStatusCode(), processorData
 .getLocale())
 } else {
 processorData.setContinueProcessing(true);
 return newObject;
 }
}

function storeNewObject(objectToSave, entity, processorData) {
 return checkForCheckout(objectToSave, entity, processorData);
}

function saveObject(objectToSave, entity, processorData) {
 return checkForCheckout(objectToSave, entity, processorData);
}

 

PartReferenceLink.json

{
 "name":"PartReferenceLink",
 "collectionName":"PartReferenceLinks",
 "type":"wcType",
 "wcType":"wt.part.WTPartReferenceLink",
 "description":"Part reference link",
 "operations":"CREATE,UPDATE,DELETE",
 "includeInServiceDocument":false,
 "defaultForWcType":true,
 "parent":{
 "name":"WindchillEntities"
 }, 
 "attributes":[],
 "inherits":[
 {
 "name": "softattributable",
 "version": "5"
 }
 ], 
 "navigations":[
 {
 "name":"ReferencedBy",
 "target":"Parts",
 "type":"Part",
 "isCollection":false,
 "partner":"",
 "internalNameForDisplayName":"referencedBy" 
 },
 {
 "name":"References",
 "target":"PTC.DocMgmt.Documents",
 "type":"PTC.DocMgmt.Document",
 "isCollection":false,
 "partner":"", 
 "internalNameForDisplayName":"references" 
 }
 ]
}

 

Now you can hit this endpoint:

POST /ProdMgmt/Parts('{PartId}')/References

{
 "ReferencedBy@odata.bind": "PTC.ProdMgmt.MyCustomPartEntity('{PartId}')",
 "References@odata.bind": "PTC.DocMgmt.Documents('{DocumentId}')"
}

 

You can add it to your CCD package by putting the files in this big nested folder structure:

/my_CustomModule/main/resources/rest/custom/domain/ProdMgmt/v7/entity/

 

3 replies

16-Pearl
April 22, 2025

I didn’t try the code but did you change the preference Part To Document Association Logic.

 

12-Amethyst
April 22, 2025

I have not, where would I find that?

16-Pearl
April 23, 2025

In Windchill > Utilities > Preferences, I think it needs to be set to Yes

 

Here's a snippet of log4j logging

 

package ext.MA.tools;

import org.apache.logging.log4j.Logger;
import wt.log4j.LogR;

public class ConversionHelper {

	private static final String CLASSNAME = ConversionHelper.class.getName();

	private static final Logger LOGGER = LogR.getLoggerInternal(CLASSNAME);
	
 public static Boolean getBooleanFromBLOB(final Object o, final String s) throws WTException {
		
		LOGGER.debug("Workflow variable '" + s + "' was not found in ProcessData.");
		
	}
}

 

In js it would be something like this,

//include the library, e.g. add it via cdn 

<script src="https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.13/log4javascript.js"></script>

const logger = log4javascript.getLogger("ConversionHelper");

// Add a browser console appender
logger.addAppender(new log4javascript.BrowserConsoleAppender());

// Set the level to debug
logger.setLevel(log4javascript.Level.DEBUG);

logger.debug(`Workflow variable '${s}' was not found in ProcessData.`);

 

13-Aquamarine
May 14, 2025

Logging in WRS extensions (javascript files) can be done as follows:

var LogManager = Java.type('wt.log4j.LogManager');
var Logger = Java.type('wt.log4j.Logger');
var logger = LogManager.getLogger("rest.custom.domain.mycustompackage");
logger.debug("CUSTOMIZATION START ...");

 

With Windchill REST Services 12.1.2.14, the Product Management v7 domain:

◦ Introduced the ability to create a part describe link between an existing part and a document.
◦ Introduced the ability to modify an existing part describe link.
◦ Introduced the ability to delete an existing part describe link.

See https://support.ptc.com/help/windchill/plus/r12.1.2.0/en/#page/Windchill_Help_Center/NewandChanged_WRS/wrs_ProdMgmt_domain_12_1_2_14.html#

 

CB_1262555312-AmethystAuthorAnswer
12-Amethyst
May 19, 2025

Here is the solution code that you will need:

 

PartReferenceLink.js

function getRelatedEntityCollection(navigationData) {
 var HashMap = Java.type('java.util.HashMap');
 var WTArrayList = Java.type('wt.fc.collections.WTArrayList');
 var Collections = Java.type('java.util.Collections');
 var VersionControlHelper = Java.type("wt.vc.VersionControlHelper");
 var SessionHelper = Java.type('wt.session.SessionHelper');
 var AccessControlHelper = Java.type('wt.access.AccessControlHelper');
 var AccessPermission = Java.type('wt.access.AccessPermission');
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var AccessResource = Java.type('wt.access.accessResource');

 var targetName = navigationData.getTargetSetName();
 var sourceLinks = new WTArrayList(navigationData.getSourceObjects());
 var user = SessionHelper.getPrincipal();

 if ("References".equals(targetName)) {
 var map = new HashMap();

 try {
 SessionHelper.manager.setAdministrator();

 for (var i = 0, l = sourceLinks.size(); i < l; i++) {
 var sourceLink = sourceLinks.getPersistable(i);
 var document = sourceLink.getRoleBObject();

 var latestDocument = VersionControlHelper.service.getLatestIteration(document, false);
 var hasAccessToReferencedBy = AccessControlHelper.manager.hasAccess(user, latestDocument, AccessPermission.READ);

 if (hasAccessToReferencedBy) {
 map.put(sourceLink, Collections.singletonList(latestDocument));
 } else {
 var errorMsg = WTMessage.getLocalizedMessage(AccessResource.class.getName(),
 AccessResource.SECURED_INFORMATION, null, navigationData.getLocale());
 throw new ODataApplicationException(errorMsg, HttpStatusCode.FORBIDDEN.getStatusCode(),
 navigationData.getLocale());
 }
 }
 } finally {
 // de-elevate administrator
 SessionHelper.manager.setPrincipal(user.getName());
 }
 return map;
 } 

 return new HashMap();
}

function processAdditionalAttributes(entity, object, data, processor) {
 var ArrayList = Java.type('java.util.ArrayList');
 var WTPart = Java.type('wt.part.WTPart');
 var WTDocument = Java.type('wt.doc.WTDocument');
 var NmOid = Java.type('com.ptc.netmarkets.model.NmOid');
 var EntityIDUtils = Java.type('com.ptc.odata.core.entity.processor.EntityIDUtils');
 var AttributeData = Java.type('com.ptc.odata.windchill.entity.processor.AttributeData');
 var EntityOperation = Java.type('com.ptc.odata.core.entity.processor.EntityOperation');
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var ProdMgmtRB = Java.type('com.ptc.odata.windchill.part.entity.ProdMgmtRB');
 var AssociationConstraintHelper = Java.type('com.ptc.core.meta.type.mgmt.server.impl.association.AssociationConstraintHelper');
 var ClientTypedUtility = Java.type('wt.type.ClientTypedUtility');

 var attributes = new ArrayList(2);
 if (data.getOperation() == EntityOperation.CREATE) {
 var referencesIdStr = EntityIDUtils.getInstance().getBindingLinkId(data, entity, "References");
 var referencesOid = referencesIdStr != null ? NmOid.newNmOid(referencesIdStr) : null;
 if (referencesOid != null && referencesOid.isA(WTDocument.class)) {
 attributes.add(new AttributeData("references", referencesOid.getRefObject().getMaster()));
 }

 var referencePart = data.getSourceObjects().iterator().next();
 attributes.add(new AttributeData("referencedBy", referencePart));
 
 // make sure our objects are valid for the link constraints
 if (!AssociationConstraintHelper.service.isValidAssociation(
 referencePart,
 ClientTypedUtility.getTypeIdentifier("wt.part.WTPartReferenceLink"),
 referencesOid.getRefObject())) {
 var errorMsg = "Invalid types for link, does not meet relationship constraints";
 throw new ODataApplicationException(errorMsg, HttpStatusCode.FORBIDDEN.getStatusCode(), data.getLocale())
 }
 }

 return attributes;
}


function toObjects(entities, processorData) {
 var ObjectAttributeProcessor = Java.type('com.ptc.odata.windchill.entity.processor.ObjectAttributeProcessor');
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var ProdMgmtRB = Java.type('com.ptc.odata.windchill.part.entity.ProdMgmtRB');

 // create the object the normal way
 var entityToObjectMap = ObjectAttributeProcessor.newInstance().toObjects(entities, processorData);


 return entityToObjectMap;
}

function checkForCheckout(newObject, createdEntity, processorData) {
 var ODataApplicationException = Java.type('org.apache.olingo.server.api.ODataApplicationException');
 var HttpStatusCode = Java.type('org.apache.olingo.commons.api.http.HttpStatusCode');
 var WTMessage = Java.type('wt.util.WTMessage');
 var ProdMgmtRB = Java.type('com.ptc.odata.windchill.part.entity.ProdMgmtRB');
 var WorkInProgressHelper = Java.type('wt.vc.wip.WorkInProgressHelper');
 var SessionHelper = Java.type('wt.session.SessionHelper');
 
 var referencePart = newObject.getReferencedBy();
 
 // check that the part is checked out and it is checked out to us
 if (!WorkInProgressHelper.isCheckedOut(referencePart, SessionHelper.getPrincipal()) ||
 !WorkInProgressHelper.isReservedWorkingCopy(referencePart)) {
 var errorMsg = "Part must be checked out and be the working copy to add, update, or delete WTPartReferenceLink";
 throw new ODataApplicationException(errorMsg, HttpStatusCode.FORBIDDEN.getStatusCode(), processorData
 .getLocale())
 } else {
 processorData.setContinueProcessing(true);
 return newObject;
 }
}

function storeNewObject(objectToSave, entity, processorData) {
 return checkForCheckout(objectToSave, entity, processorData);
}

function saveObject(objectToSave, entity, processorData) {
 return checkForCheckout(objectToSave, entity, processorData);
}

 

PartReferenceLink.json

{
 "name":"PartReferenceLink",
 "collectionName":"PartReferenceLinks",
 "type":"wcType",
 "wcType":"wt.part.WTPartReferenceLink",
 "description":"Part reference link",
 "operations":"CREATE,UPDATE,DELETE",
 "includeInServiceDocument":false,
 "defaultForWcType":true,
 "parent":{
 "name":"WindchillEntities"
 }, 
 "attributes":[],
 "inherits":[
 {
 "name": "softattributable",
 "version": "5"
 }
 ], 
 "navigations":[
 {
 "name":"ReferencedBy",
 "target":"Parts",
 "type":"Part",
 "isCollection":false,
 "partner":"",
 "internalNameForDisplayName":"referencedBy" 
 },
 {
 "name":"References",
 "target":"PTC.DocMgmt.Documents",
 "type":"PTC.DocMgmt.Document",
 "isCollection":false,
 "partner":"", 
 "internalNameForDisplayName":"references" 
 }
 ]
}

 

Now you can hit this endpoint:

POST /ProdMgmt/Parts('{PartId}')/References

{
 "ReferencedBy@odata.bind": "PTC.ProdMgmt.MyCustomPartEntity('{PartId}')",
 "References@odata.bind": "PTC.DocMgmt.Documents('{DocumentId}')"
}

 

You can add it to your CCD package by putting the files in this big nested folder structure:

/my_CustomModule/main/resources/rest/custom/domain/ProdMgmt/v7/entity/

 

7-Bedrock
July 24, 2025

Hi, I am trying to implement this. After putting the files, I am trying to make the request. I checked out the Part before making the request. Even with checked out part I am getting the error code 403 with following response body.

"

{ "error": { "code": null, "message": "Part must be checked out and be the working copy to add, update, or delete WTPartReferenceLink"
 }
}

"

Also I used the request body as following 

"

{
"ReferencedBy@odata.bind": "PTC.ProdMgmt.MechanicalPart('OR:wt.part.WTPart:349320081')",
"References@odata.bind": "PTC.DocMgmt.Documents('OR:wt.doc.WTDocument:349319669')"
}

"

Is there anything similar you have faced. I would be grateful for any help or suggestion. Thanks 

13-Aquamarine
July 24, 2025

Hello,  instead of debugging above (are you using the OID of the working copy?), you might upgrade to the latest Windchill version. The functionality discussed here is now available in standard Windchill:

With Windchill REST Services 13.1.1.0, the Product Management v7 domain has been updated:

◦ Introduced the ability to create a part reference link between an existing part and a reference document.

◦ Introduced the ability to modify an existing part reference link.

◦ Introduced the ability to delete an existing part reference link.

See https://support.ptc.com/help/windchill/r13.1.1.0/en/#page/Windchill_Help_Center/NewandChanged_WRS/wrs_ProdMgmt_domain_13_1_10.html#