UNPKG

kipon-xrmservice

Version:

Dynamics 365 CE web api wrapper for angular, making life easier when building angular based web resources

1,235 lines (1,234 loc) 286 kB
import { Injectable } from '@angular/core'; import { HttpHeaders } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { map } from 'rxjs/operators'; import { Expand } from './xrm.service'; import * as i0 from "@angular/core"; import * as i1 from "@angular/common/http"; import * as i2 from "./xrm.service"; const XRMCONTEXTSERVICE_EMPTY_GUID = "00000000-0000-0000-0000-000000000000"; export class FunctionPropertyValue { constructor(v) { this.v = ''; this.v = v; } functionPropertyValueAsString() { return this.v; } } export class Entity { constructor(pluralName, keyName, updateable = false, logicalname = null) { this._updateable = false; this._pluralName = pluralName; this._keyName = keyName; this._updateable = updateable; if (logicalname != null && logicalname != '') { this._logicalName = logicalname; } else { let x = this._pluralName != null ? this._pluralName.toLowerCase() : null; switch (x) { case "emails": this._logicalName = "email"; break; case "appointments": this._logicalName = "appointment"; break; case "letters": this._logicalName = "letter"; break; case "phonecalls": this._logicalName = "phonecall"; break; case "tasks": this._logicalName = "task"; break; case "campaignresponses": this._logicalName = "campaignresponse"; break; default: { if (this._keyName.toLowerCase() == "activityid" && x != null) { this._logicalName = x.substr(0, x.length - 1); } else { this._logicalName = this._keyName.substr(0, (this._keyName.length - 2)).toLowerCase(); } break; } } } } ToEntityReference(associatednavigationproperty = null) { return new EntityReference(this.id, this._pluralName, associatednavigationproperty, this._logicalName); } ignoreColumn(prop) { if (prop == "_pluralName" || prop == "_logicalName" || prop == "_keyName" || prop == "id" || prop == '_updateable' || prop == '$expand' || prop == 'access') { return true; } return false; } columns(webapi = false) { let result = []; let columns = this._keyName; for (var prop in this) { if (prop == this._keyName) continue; if (this.ignoreColumn(prop)) continue; let v = this[prop]; if (typeof v !== 'undefined' && v != null) { if (Array.isArray(v)) { continue; } if (v instanceof Entity) { continue; } } if (this.hasOwnProperty(prop)) { if (webapi && this[prop] instanceof EntityReference) { result.push("_" + prop + "_value"); } else { result.push(prop); } } } return result; } } export class Entities extends Array { constructor(fType, tType, refName, leftToRight, t) { super(0); Object.setPrototypeOf(this, new.target.prototype); this.parentType = fType; this.childType = tType; this.refName = refName; this.leftToRight = leftToRight; this.push(t); } add(entity) { let fromType = this.parentType; let fromId = this.parentId; let toType = this.childType; let toId = entity.id; if (!this.leftToRight) { fromType = this.childType; fromId = entity.id; toType = this.parentType; toId = this.parentId; } return this.xrmService.associate(fromType, fromId, toType, toId, this.refName).pipe(map(r => { this.push(entity); return null; })); } remove(entity) { let fromType = this.parentType; let fromId = this.parentId; let toType = this.childType; let toId = entity.id; if (!this.leftToRight) { fromType = this.childType; fromId = entity.id; toType = this.parentType; toId = this.parentId; } return this.xrmService.disassociate(fromType, fromId, toType, toId, this.refName).pipe(map(r => { var index = this.indexOf(entity); if (index >= 0) { this.splice(index, 1); } return null; })); } } export class EntityReference { constructor(id = null, pluralName = null, associatednavigationproperty = null, logicalname = null) { this.id = id; this.pluralName = pluralName; this.associatednavigationproperty = associatednavigationproperty; this.logicalname = logicalname; if (this.pluralName != null && logicalname == null) { switch (this.pluralName.toLowerCase()) { case "emails": this.logicalname = "email"; break; case "appointments": this.logicalname = "appointment"; break; case "letters": this.logicalname = "letter"; break; case "phonecalls": this.logicalname = "phonecall"; break; case "tasks": this.logicalname = "task"; break; default: { this.logicalname = this.pluralName.substr(0, (this.pluralName.length - 1)).toLowerCase(); break; } } } } replace(v1, v2) { this.id.replace(v1, v2); } meta(pluralName, associatednavigationproperty) { this.pluralName = pluralName; this.associatednavigationproperty = associatednavigationproperty; return this; } clone() { let result = new EntityReference(); result.id = this.id; result.name = this.name; result.logicalname = this.name; result.associatednavigationproperty = this.associatednavigationproperty; return result; } associatednavigationpropertyname() { if (this.associatednavigationproperty == null || this.associatednavigationproperty == '') { throw 'navigation property has not been set for this EntityReference instance'; } if (this.associatednavigationproperty.endsWith('@odata.bind')) { return this.associatednavigationproperty; } return this.associatednavigationproperty + '@odata.bind'; } equals(ref) { return this.id == ref.id && this.logicalname == ref.logicalname; } toJsonProperty() { return { '@odata.id': "'" + this.pluralName + "(" + this.id.replace("{", "").replace("}", "") + ")" + "'" }; } static same(ref1, ref2) { if (ref1 == null && ref2 == null) { return true; } let id1 = null; let id2 = null; if (ref1 != null) id1 = ref1.id; if (ref2 != null) id2 = ref2.id; return id1 == id2; } } export class OptionSetValue { constructor(value = null, name = null) { this.value = value; this.name = name; } equals(o) { if (this.value == null && (o == null || o.value == null)) return true; return this.value == o.value; } clone() { let r = new OptionSetValue(); r.name = this.name; r.value = this.value; return r; } toJsonProperty() { return this.value; } static same(o1, o2) { if (o1 == null && o2 == null) return true; let v1 = null; let v2 = null; if (o1 != null) v1 = o1.value; if (o2 != null) v2 = o2.value; return v1 == v2; } } export var Operator; (function (Operator) { Operator[Operator["And"] = 0] = "And"; Operator[Operator["Or"] = 1] = "Or"; })(Operator || (Operator = {})); export var Comparator; (function (Comparator) { Comparator[Comparator["Equals"] = 1] = "Equals"; Comparator[Comparator["NotEquals"] = 2] = "NotEquals"; Comparator[Comparator["Contains"] = 3] = "Contains"; Comparator[Comparator["NotContains"] = 4] = "NotContains"; Comparator[Comparator["DoesNotContainsData"] = 5] = "DoesNotContainsData"; Comparator[Comparator["ContainsData"] = 6] = "ContainsData"; Comparator[Comparator["StartsWith"] = 7] = "StartsWith"; Comparator[Comparator["NotStartsWith"] = 8] = "NotStartsWith"; Comparator[Comparator["EndsWith"] = 9] = "EndsWith"; Comparator[Comparator["NotEndsWith"] = 10] = "NotEndsWith"; Comparator[Comparator["GreaterThan"] = 11] = "GreaterThan"; Comparator[Comparator["GreaterThanOrEqual"] = 12] = "GreaterThanOrEqual"; Comparator[Comparator["LessThan"] = 13] = "LessThan"; Comparator[Comparator["LessThanOrEQual"] = 14] = "LessThanOrEQual"; Comparator[Comparator["Useroruserhierarchy"] = 100] = "Useroruserhierarchy"; Comparator[Comparator["Userteams"] = 101] = "Userteams"; })(Comparator || (Comparator = {})); export class ColumnBuilder { constructor() { this.columns = null; this.hasEntityReference = false; } } export class Filter { constructor() { this["raw"] = false; } toQueryString(prototype) { if (this["raw"] == true) { return this.field; } if (this.operator == 100) { return 'ownerid eq-useroruserhierarchy'; } if (this.operator == 101) { return 'ownerid eq-userteams'; } let result = ''; let _f = this.field; if (this.operator == Comparator.Equals && this.value == null) { // this.operator = Comparator.DoesNotContainsData; } if (this.operator == Comparator.NotEquals && this.value == null) { // this.operator = Comparator.ContainsData; } switch (this.operator) { case Comparator.ContainsData: { return _f + ' ne null'; } case Comparator.DoesNotContainsData: { return _f + ' eq null'; } } if (this.value == null) { throw new Error("value is required for operation type " + this.operator); } let _v = "'" + this.value + "'"; if (typeof this.value == 'number') { _v = this.value.toString(); } if (typeof this.value == 'boolean') { _v = this.value.valueOf() ? 'true' : 'false'; } if (prototype[this.field] instanceof OptionSetValue) { if (this.value != null && this.value.hasOwnProperty('value')) { _v = this.value.value; } } let isEref = false; if (prototype[this.field] instanceof EntityReference) { _f = "_" + this.field + "_value"; if (this.value != null) { if (typeof this.value == 'string') { _v = this.value.replace('{', '').replace('}', ''); } else { _v = this.value.id.replace('{', '').replace('}', ''); } } isEref = true; } if (!isEref && _f.startsWith('_') && _f.endsWith('_value') && _v != null && this.value != null) { if (typeof this.value === "string") { _v = this.value.replace('{', '').replace('}', ''); } else { if (this.value.hasOwnProperty("id")) { _v = this.value.id.replace('{', '').replace('}', ''); } } } var isDate = false; if (prototype[this.field] instanceof Date) { if (this.value instanceof Date) { _v = this.value.toISOString(); } else { _v = this.value.toString(); } isDate = true; } if (!isDate && this.value instanceof Date) { _v = this.value.toISOString(); isDate = true; } if (_f == prototype._keyName) { _v = this.value.replace('{', '').replace('}', ''); } if (_v != null && _v != '') { _v = encodeURIComponent(_v); } switch (this.operator) { case Comparator.Equals: { return _f + ' eq ' + _v; } case Comparator.NotEquals: { return _f + ' ne ' + _v; } case Comparator.GreaterThan: { return _f + ' gt ' + _v; } case Comparator.GreaterThanOrEqual: { return _f + ' ge ' + _v; } case Comparator.LessThan: { return _f + ' lt ' + _v; } case Comparator.LessThanOrEQual: { return _f + ' le ' + _v; } case Comparator.Contains: { return "contains(" + _f + "," + _v + ")"; } case Comparator.NotContains: { return "not contains(" + _f + "," + _v + ")"; } case Comparator.StartsWith: { return "startswith(" + _f + "," + _v + ")"; } case Comparator.NotStartsWith: { return "not startswith(" + _f + "," + _v + ")"; } case Comparator.EndsWith: { return "endswith(" + _f + "," + _v + ")"; } case Comparator.NotEndsWith: { return "not endswith(" + _f + "," + _v + ")"; } } return result; } toFetcmXml() { if (this.operator == 100) { return '<condition attribute="ownerid" operator="eq-useroruserhierarchy" />'; } if (this.operator == 101) { return '<condition attribute="ownerid" operator="eq-userteams" />'; } var result = ""; let _v = this.value; if (this.value != null) { if (typeof this.value == 'number') { _v = this.value.toString(); } if (typeof this.value == 'boolean') { _v = this.value.valueOf() ? 'true' : 'false'; } if (this.value != null && this.value.hasOwnProperty('value')) { _v = this.value.value; } } if (_v == null) { this.adjustOperation(); } var op = "eq"; switch (this.operator) { case Comparator.Contains: op = "like"; _v = "%" + _v + "%"; break; case Comparator.ContainsData: op = "not-null"; break; case Comparator.DoesNotContainsData: op = "null"; break; case Comparator.EndsWith: op = "like"; _v = "%" + _v; null; break; case Comparator.Equals: op = "eq"; break; case Comparator.GreaterThan: op = "gt"; break; case Comparator.GreaterThanOrEqual: op = "ge"; break; case Comparator.LessThan: op = "lt"; break; case Comparator.LessThanOrEQual: op = "le"; break; case Comparator.NotContains: op = "not-like"; "%" + _v + "%"; break; case Comparator.NotEndsWith: op = "not-like"; "&" + _v; break; case Comparator.NotEquals: op = "ne"; break; case Comparator.NotStartsWith: op = "not-like"; _v = _v + "%"; break; case Comparator.StartsWith: op = "like"; _v = _v + "%"; break; default: throw "condition type not supported in fetch xml " + this.operator; } var aliasString = ""; if (this.alias != null) { aliasString = " entityname='" + this.alias + "'"; } if (_v != null) { result += "<condition" + aliasString + " attribute='" + this.field + "' operator='" + op + "' value='" + _v + "' />"; } else { result += "<condition" + aliasString + " attribute='" + this.field + "' operator='" + op + "' />"; } return result; } adjustOperation() { if (this.operator == Comparator.Equals) { this.operator = Comparator.DoesNotContainsData; } else if (this.operator == Comparator.NotEquals) { this.operator = Comparator.ContainsData; } if (this.operator != Comparator.DoesNotContainsData && this.operator != Comparator.ContainsData) { throw "null in condition value is only supported for containsdata and doesnotdontainsdata:" + this.field + "/" + (this.alias != null ? "alias:" + this.alias : "") + "/" + this.operator; } } } export class Condition { constructor(operator = Operator.And) { this.operator = Operator.And; this.operator = operator; this.filter = []; this.children = []; } where(field, opr, value = null) { let f = new Filter(); f.field = field; f.value = value; f.operator = opr; this.filter.push(f); return this; } alias(alias, field, opr, value = null) { let f = new Filter(); f.alias = alias; f.field = field; f.value = value; f.operator = opr; this.filter.push(f); return this; } group(opr) { let result = new Condition(opr); result.parent = this; this.children.push(result); return result; } isActive() { return this.where("statecode", Comparator.Equals, 0); } isInactive() { return this.where("statecode", Comparator.Equals, 1); } owningUserIsCurrentUserOrHirachy() { return this.where("ownerid", 100); } currentUserIsMemberOfOwningTeam() { return this.where("ownerid", 101); } raw(filter) { let result = new Filter(); result.field = filter; result["raw"] = true; this.filter.push(result); return this; } toQueryString(prototype) { if ((this.children == null || this.children.length == 0) && (this.filter == null || this.filter.length == 0)) { return null; } let me = this; let result = ''; let opr = ''; if (this.filter != null && this.filter.length > 0) { this.filter.forEach(r => { result += opr + r.toQueryString(prototype); if (me.operator == Operator.And) { opr = ' and '; } else { opr = ' or '; } }); } if (this.children != null && this.children.length > 0) { this.children.forEach(c => { result += opr + "(" + c.toQueryString(prototype) + ")"; if (me.operator == Operator.And) { opr = ' and '; } else { opr = ' or '; } }); } return result; } toFetchXml() { let result = ""; result += "<filter type='" + (this.operator == Operator.And ? "and" : "or") + "'>"; if (this.filter != null && this.filter.length > 0) this.filter.forEach(f => { result += f.toFetcmXml(); }); if (this.children != null && this.children.length > 0) { this.children.forEach(c => { result += c.toFetchXml(); }); } result += "</filter>"; return result; } } class XrmTransactionItem { constructor(type, prototype, instance, field = null, value = null) { this.id = null; this.type = type; this.prototype = prototype; this.instance = instance; this.field = field; this.value = value; } } export class XrmTransaction { constructor() { this.oprs = []; } put(prototype, instance, field, value) { this.oprs.push(new XrmTransactionItem("put", prototype, instance, field, value)); } delete(instance) { this.oprs.push(new XrmTransactionItem("delete", null, instance)); } create(prototype, instance) { this.oprs.push(new XrmTransactionItem("create", prototype, instance)); } update(prototype, instance) { this.oprs.push(new XrmTransactionItem("update", prototype, instance)); } } export class XrmAccess { constructor(lazy = false) { this.lazy = null; this.resolved = null; this.lazy = lazy; } } class ExpandProperty { } export class XrmContextService { constructor(http, xrmService) { this.http = http; this.xrmService = xrmService; this.context = {}; this.changemanager = {}; this.tick = new Date().valueOf(); this.includeOriginalPayload$ = false; this.pageCookieMatch = /[-pagingcookie=\"][a-z,A-Z,0-9,%-_.~]+/g; } setVersion(v) { this.xrmService.setVersion(v); } includeOroginalPayload(v) { this.includeOriginalPayload$ = v; } getContext() { return this.xrmService.getContext(); } getCurrentKey(repeatForCreateForm = false) { return this.xrmService.getCurrentKey(repeatForCreateForm); } getServiceUrl() { return this.getContext().getClientUrl() + this.xrmService.apiUrl; } getCurrentUserId() { return this.xrmService.getCurrentUserId(); } get(prototype, id) { let me = this; let columnDef = this.columnBuilder(prototype); let expand = null; let eps = this.getExpandProperties(prototype); if (eps != null && eps.length > 0) { let comma = ","; eps.forEach(ep => { if (expand == null) { expand = this.$expandToExpand(ep); } else { if (expand.additional == null) { expand.additional = []; } expand.additional.push(this.$expandToExpand(ep)); } }); } return this.xrmService.get(prototype._pluralName, id, columnDef.columns, expand).pipe(map(r => { return me.resolve(prototype, r, prototype._updateable, null); })); } debug(setting) { this.xrmService.debug = setting; } count(xml) { let me = this; let headers = new HttpHeaders({ 'Accept': 'application/json' }); if (this.xrmService.token != null) { headers = headers.append("Authorization", "Bearer " + this.xrmService.token); } headers = headers.append("OData-MaxVersion", "4.0"); headers = headers.append("OData-Version", "4.0"); headers = headers.append("Content-Type", "application/json; charset=utf-8"); headers = headers.append("Prefer", "odata.include-annotations=\"*\""); headers = headers.append("Cache-Control", "no-cache"); let options = { headers: headers }; let fetchxml = xml.toCountFetchXml(); let url = this.getContext().getClientUrl() + this.xrmService.apiUrl + xml.entity().entityPrototype._pluralName + "?fetchXml=" + encodeURIComponent(fetchxml); this.xrmService.log(fetchxml); this.xrmService.log(url); return new Observable(obs => { this.http.get(this.forceHTTPS(url), options).toPromise() .then(response => { if (response.value && response.value.length && response.value.length == 1) { obs.next(response.value[0].count); } else { obs.next(0); } }) .catch(e => { if (e["error"] && e["error"]["error"] && e["error"]["error"]["code"]) { var val = e["error"]["error"]["code"]; if (val != null && val.toString() == "0x8004e023") { obs.next(50000); return; } } throwError(e); }); }); } fetch(xml) { let me = this; let headers = new HttpHeaders({ 'Accept': 'application/json' }); if (this.xrmService.token != null) { headers = headers.append("Authorization", "Bearer " + this.xrmService.token); } headers = headers.append("OData-MaxVersion", "4.0"); headers = headers.append("OData-Version", "4.0"); headers = headers.append("Content-Type", "application/json; charset=utf-8"); headers = headers.append("Prefer", "odata.include-annotations=\"*\""); headers = headers.append("Cache-Control", "no-cache"); let options = { headers: headers }; let fetchxml = xml.toFetchXml(); let url = this.getContext().getClientUrl() + this.xrmService.apiUrl + xml.entity().entityPrototype._pluralName + "?fetchXml=" + encodeURIComponent(fetchxml); this.xrmService.log(fetchxml); this.xrmService.log(url); let localPrototype = xml.entity().entityPrototype; return this.http.get(this.forceHTTPS(url), options).pipe(map(response => { let result = me.resolveFetchResult(localPrototype, response, xml.count, [url], 0, xml); return result; })); } fetchxml(prototype, fetchxml) { let me = this; let headers = new HttpHeaders({ 'Accept': 'application/json' }); if (this.xrmService.token != null) { headers = headers.append("Authorization", "Bearer " + this.xrmService.token); } headers = headers.append("OData-MaxVersion", "4.0"); headers = headers.append("OData-Version", "4.0"); headers = headers.append("Content-Type", "application/json; charset=utf-8"); headers = headers.append("Prefer", "odata.include-annotations=\"*\""); headers = headers.append("Cache-Control", "no-cache"); let options = { headers: headers }; let url = this.getContext().getClientUrl() + this.xrmService.apiUrl + prototype._pluralName + "?fetchXml=" + encodeURIComponent(fetchxml); this.xrmService.log(fetchxml); this.xrmService.log(url); return this.http.get(this.forceHTTPS(url), options).pipe(map(response => { let result = me.resolveQueryResult(prototype, response, 0, [url], 0, null); return result; })); } query(prototype, condition, orderBy = null, top = 0, count = false) { let me = this; let fields = this.columnBuilder(prototype).columns; let con = condition; let filter = null; if (condition != null) { while (con.parent != null) { con = con.parent; } ; filter = con.toQueryString(prototype); } let headers = new HttpHeaders({ 'Accept': 'application/json' }); if (this.xrmService.token != null) { headers = headers.append("Authorization", "Bearer " + this.xrmService.token); } headers = headers.append("OData-MaxVersion", "4.0"); headers = headers.append("OData-Version", "4.0"); headers = headers.append("Content-Type", "application/json; charset=utf-8"); headers = headers.append("Prefer", "odata.include-annotations=\"*\""); if (top > 0) { headers = headers.append("Prefer", "odata.maxpagesize=" + top.toString()); } headers = headers.append("Cache-Control", "no-cache"); let options = { headers: headers }; let url = this.getContext().getClientUrl() + this.xrmService.apiUrl + prototype._pluralName; if ((fields != null && fields != '') || (filter != null && filter != '') || (orderBy != null && orderBy != '') || top > 0) { url += "?"; } let sep = ''; if (fields != null && fields != '') { url += '$select=' + fields; sep = '&'; } if (filter != null && filter != '') { url += sep + '$filter=' + filter; sep = '&'; } if (orderBy != null && orderBy != '') { url += sep + '$orderby=' + orderBy; sep = '&'; } if (count) { url += sep + '$count=true'; sep = '&'; } this.xrmService.log(url); return this.http.get(this.forceHTTPS(url), options).pipe(map(response => { let result = me.resolveQueryResult(prototype, response, top, [url], 0, null); return result; })); } create(prototype, instance) { let newr = this.prepareNewInstance(prototype, instance); this.xrmService.log(newr); return this.xrmService.create(prototype._pluralName, newr).pipe(map(_response => { let response = _response; this.xrmService.log(response); if (response != null) { if (response.hasOwnProperty('$keyonly')) { instance._pluralName = prototype._pluralName; instance._logicalName = prototype._logicalName; instance._keyName = prototype._keyName; instance.id = _response.id; instance._updateable = true; let key = response._pluralName + ':' + response.id; for (let prop in prototype) { if (typeof prototype[prop] === 'function') { response[prop] = prototype[prop]; continue; } } this.context[key] = instance; this.updateCM(prototype, instance); return instance; } else { this.resolveNewInstance(prototype, instance, response); return this.resolve(prototype, response, true, null); } } return null; })); } createAll(prototype, instances) { if (prototype != null && instances != null && instances.length > 0) { let trans = new XrmTransaction(); instances.forEach(r => { trans.create(prototype, r); }); return this.commit(trans); } throw "You must parse a prototype and at least one instance to be created"; } update(prototype, instance, deleteReferenceAsEmptyGuid = false) { let me = this; let upd = this.prepareUpdate(prototype, instance, deleteReferenceAsEmptyGuid); if (upd == null) { upd = {}; } this.xrmService.log(upd); let fields = this.columnBuilder(prototype).columns; return this.xrmService.update(prototype._pluralName, upd, instance.id, fields).pipe(map(response => { var wasnull = response[prototype._keyName] == null || response[prototype._keyName] == ''; if (wasnull || this.getContext().getVersion().startsWith("8.0") || this.getContext().getVersion().startsWith("8.1")) { this.xrmService.log('version 8.0 update'); this.updateCM(prototype, instance); return instance; } this.xrmService.log('version 9.0 or higher update'); return me.resolve(prototype, response, true, null); })); } put(prototype, instance, field, value = 'ko-@value-not-parsed!') { let v = value; if (value == 'ko-@value-not-parsed!') { v = instance[field]; } let pvx = this.preparePutValue(prototype, field, v); return this.xrmService.put(prototype._pluralName, instance.id, pvx.field, pvx.value, pvx.propertyAs).pipe(map(response => { if (value != 'ko-@value-not-parsed!') { this.assignValue(prototype, instance, field, value); return null; } })); } putAll(prototype, instances, field, value) { if (instances != null && instances.length == 1) { return this.put(prototype, instances[0], field, value); } if (instances != null && instances.length > 1) { let trans = new XrmTransaction(); let pvx = this.preparePutValue(prototype, field, value); instances.forEach(r => { trans.put(prototype, r, field, pvx); }); return this.commit(trans); } throw 'you must parse at least one instance in the instances array'; } delete(t) { let me = this; return this.xrmService.delete(t._pluralName, t.id).pipe(map(r => { let key = t._pluralName + ":" + t.id; if (me.context.hasOwnProperty(key)) { delete me.context[key]; } return null; })); } deleteAll(instances) { if (instances != null && instances.length == 1) { return this.delete(instances[0]); } if (instances != null && instances.length > 0) { let trans = new XrmTransaction(); instances.forEach(r => { trans.delete(r); }); return this.commit(trans); } throw 'you must parse at least one instance in the instances array'; } markChangesCommitted(prototype, instance) { this.updateCM(prototype, instance); } func(name, data, entity = null) { var parameters = data; if (parameters != null) { parameters = this.toFuncParameterString(data); } if (entity == null) { return this.xrmService.func(name, parameters); } else { return this.xrmService.func(name, parameters, entity._pluralName, entity.id); } } action(name, data, entity = null) { if (entity != null) { return this.xrmService.action(name, data, entity._pluralName, entity.id); } else { return this.xrmService.action(name, data); } } commit(transaction) { let oprs = transaction["oprs"]; if (oprs != null && oprs.length > 0) { this.tick++; let batch = 'batch_KO' + new Date().valueOf() + "$" + this.tick.toString(); let change = 'changeset_KO' + new Date().valueOf() + "!" + this.tick.toString(); let headers = new HttpHeaders({ 'Accept': 'application/json' }); if (this.xrmService.token != null) { headers = headers.append("Authorization", "Bearer " + this.xrmService.token); } headers = headers.append("Content-Type", "multipart/mixed;boundary=" + batch); headers = headers.append("OData-MaxVersion", "4.0"); headers = headers.append("OData-Version", "4.0"); let body = '--' + batch + "\n"; body += "Content-Type: multipart/mixed;boundary=" + change + "\n"; body += "\n"; let count = 1; oprs.forEach(r => { r.id = count; if (r.type == "put") { let nextV = this.preparePutValue(r.prototype, r.field, r.value); body += '--' + change + "\n"; body += "Content-Type: application/http\n"; body += "Content-Transfer-Encoding:binary\n"; body += "Content-ID: " + count.toString() + "\n"; body += "\n"; if (nextV.value != null) { body += "PUT " + this.getContext().$devClientUrl() + r.prototype._pluralName + "(" + r.instance.id + ")/" + nextV.field + " HTTP/1.1\n"; } else { body += "DELETE " + this.getContext().$devClientUrl() + r.instance._pluralName + "(" + r.instance.id + ")/" + nextV.field + " HTTP/1.1\n"; } let xr = {}; if (nextV.value != null) { xr[nextV.propertyAs] = nextV.value; } body += "Content-Type: application/json;type=entry\n"; body += "\n"; body += JSON.stringify(xr) + "\n"; } if (r.type == "delete") { body += '--' + change + "\n"; body += "Content-Type: application/http\n"; body += "Content-Transfer-Encoding:binary\n"; body += "Content-ID: " + count.toString() + "\n"; body += "\n"; body += "DELETE " + this.getContext().$devClientUrl() + r.instance._pluralName + "(" + r.instance.id + ")" + " HTTP/1.1\n"; body += "Content-Type: application/json;type=entry\n"; body += "\n"; body += "{ }\n"; } if (r.type == "create") { let nextI = this.prepareNewInstance(r.prototype, r.instance); body += '--' + change + "\n"; body += "Content-Type: application/http\n"; body += "Content-Transfer-Encoding:binary\n"; body += "Content-ID: " + count.toString() + "\n"; body += "\n"; body += "POST " + this.getContext().$devClientUrl() + r.prototype._pluralName + " HTTP/1.1\n"; body += "Content-Type: application/json;type=entry\n"; body += "\n"; body += JSON.stringify(nextI) + "\n"; } if (r.type == "update") { let nextU = this.prepareUpdate(r.prototype, r.instance, false); if (nextU != null) { let fields = "?$select=" + this.columnBuilder(r.prototype).columns; body += '--' + change + "\n"; body += "Content-Type: application/http\n"; body += "Content-Transfer-Encoding:binary\n"; body += "Content-ID: " + count.toString() + "\n"; body += "\n"; body += "PATCH " + this.getContext().$devClientUrl() + r.prototype._pluralName + "(" + r.instance.id + ")" + fields + " HTTP/1.1\n"; body += "Content-Type: application/json;type=entry\n"; body += "\n"; body += JSON.stringify(nextU) + "\n"; } } count++; }); body += '--' + change + '--\n'; body += "\n"; body += "--" + batch + "--\n"; this.xrmService.log(body); let url = this.getContext().getClientUrl() + this.xrmService.apiUrl + "$batch"; this.xrmService.log(url); return this.http.post(this.forceHTTPS(url), body, { headers: headers, responseType: "text" }).pipe(map(_txt => { let txt = _txt; this.xrmService.log(txt); oprs.forEach(r => { if (r.type == 'put') { this.assignValue(r.prototype, r.instance, r.field, r.value); this.updateCM(r.prototype, r.instance); } if (r.type == 'update') { this.updateCM(r.prototype, r.instance); } if (r.type == 'delete') { let key = r.instance._pluralName + ':' + r.instance.id; delete this.context[key]; delete this.changemanager[key]; } }); let index = 0; txt.split('\n').forEach(l => { if (l.startsWith('Content-ID:')) { index = Number(l.split(':')[1].trim()); return true; } if (l.startsWith('OData-EntityId:')) { let opr = oprs.find(o => o.id == index); if (opr != null && opr.type == 'create') { let id = l.split('OData-EntityId:')[1].split('/' + opr.prototype._pluralName + '(')[1].replace(')', '').trim(); opr.instance.id = id; let key = opr.prototype._pluralName + ':' + opr.instance.id; this.context[key] = opr.instance; opr.instance._updateable = true; this.updateCM(opr.prototype, opr.instance); } } return true; }); return null; })); } throw 'you must parse at least one operation to the transaction by calling put, create, update or delete'; } log(type) { if (type == 'context') { this.xrmService.log(this.context); return; } if (type == 'xrmcontext') { this.xrmService.log(this.getContext()); return; } if (type == 'url') { this.xrmService.log(this.getContext().getClientUrl()); } if (type == 'version') { this.xrmService.log(this.getContext().getVersion()); } this.xrmService.log('xrmContextService supported the current log types: context, xrmcontext, url, version'); } clone(prototype, instance) { let r = new Entity(prototype._pluralName, prototype._keyName); for (let prop in prototype) { if (prototype.ignoreColumn(prop)) continue; let pv = prototype[prop]; if (typeof pv == 'function') { r[prop] = pv; continue; } let v = instance[prop]; if (v == null) { r[prop] = null; continue; } if (v instanceof Date) { r[prop] = new Date(v.valueOf()); continue; } if (v instanceof EntityReference) { r[prop] = v.clone(); continue; } if (v instanceof OptionSetValue) { r[prop] = v.clone(); continue; } if (v != null) { r[prop] = v; } } r.id = null; this.xrmService.log('clone'); this.xrmService.log(r); return r; } applyAccess(prototype, instance) { if (!prototype.hasOwnProperty('access')) throw 'The metadata must define a property "access" of type XrmAccess'; if (instance.hasOwnProperty('access') && instance['access']['resolved']) { return Observable.create(obs => obs.next(instance)); } return this.mapAccess(prototype, instance); } prepareUpdate(prototype, instance, deletedReferenceAsEmptyGuid) { let me = this; let upd = {}; let countFields = 0; let key = instance._pluralName + ':' + instance.id; let cm = this.changemanager[key]; if (typeof cm === 'undefined' || cm === null) { throw 'the object is not under change control and cannot be updated within this context'; } for (let prop in prototype) { if (prototype.hasOwnProperty(prop) && typeof prototype[prop] != 'function') { if (prototype.ignoreColumn(prop)) continue; let prevValue = cm[prop]; let newValue = instance[prop]; if ((prevValue === 'undefined' || prevValue === null) && (newValue === 'undefined' || newValue === null)) continue; if (instance[prop] instanceof EntityReference) { if (instance[prop].associatednavigationproperty != null && instance[prop].associatednavigationproperty != '' && instance[prop]['pluralName'] != null && instance[prop]['pluralName'] != '') { if (!EntityReference.same(prevValue, newValue)) { if (deletedReferenceAsEmptyGuid && newValue != null && (newValue["id"] == null || newValue["id"] == '')) { newValue["id"] = XRMCONTEXTSERVICE_EMPTY_GUID; } if (newValue != null && newValue["id"] != null && newValue["id"] != '') { let x = newValue["id"]; x = x.replace('{', '').replace('}', ''); upd[instance[prop]['associatednavigationpropertyname']()] = '/' + instance[prop]['pluralName'] + '(' + x + ')'; } else { // this does not work, navigation properties can only be removed with SDK deleted method upd["_" + instance[prop]['logicalname'].toLowerCase() + "_value"] = null; } countFields++; } continue; } } if (prototype[prop] instanceof EntityReference) { if (!EntityReference.same(prevValue, newValue)) { if ((newValue == null || newValue["id"] == null || newValue["id"] == "") && deletedReferenceAsEmptyGuid) { newValue["id"] = XRMCONTEXTSERVICE_EMPTY_GUID; } if (newValue != null && newValue["id"] != null && newValue["id"] != '') { let x = newValue["id"]; x = x.replace('{', '').replace('}', ''); upd[prototype[prop]['associatednavigationpropertyname']()] = '/' + prototype[prop]['pluralName'] + '(' + x + ')'; } else { upd["_" + instance[prop]['logicalname'].toLowerCase() + "_value"] = null; } countFields++; } continue; } if (prototype[prop] instanceof OptionSetValue) { if (!OptionSetValue.same(prevValue, newValue)) { let o = newValue; if (o == null || o.value == null) { upd[prop.toString()] = null; } else { upd[prop.toString()] = o.value; } countFields++; } continue; } if (prototype[prop] instanceof Date) { if (prevValue instanceof Date && newValue instanceof Date) { var d1 = prevValue.toISOString(); var d2 = newValue.toISOString(); if (d1 == d2) { continue; } } if (prevValue != newValue) { this.xrmService.log('pre-value-update-date:[' + prevValue + ']/[' + newValue + ']'); if (newValue == null) { upd[prop.toString()] = null;