diff --git a/netsuite/Ballerina.toml b/netsuite/Ballerina.toml index 6051a61..920a82d 100644 --- a/netsuite/Ballerina.toml +++ b/netsuite/Ballerina.toml @@ -2,7 +2,7 @@ distribution = "2201.2.1" org = "ballerinax" name = "netsuite" -version = "3.0.0" +version = "3.0.1" authors = ["Ballerina"] repository = "https://github.com/ballerina-platform/module-ballerinax-netsuite" keywords = ["Business Management/ERP", "Cost/Paid"] diff --git a/netsuite/Invoice.bal b/netsuite/Invoice.bal index 831eebb..e3a4c0b 100644 --- a/netsuite/Invoice.bal +++ b/netsuite/Invoice.bal @@ -16,24 +16,24 @@ import ballerina/http; -isolated function mapInvoiceRecordFields(Invoice invoice) returns string|error { +isolated function mapInvoiceRecordFields(Invoice invoice, boolean replaceAll = false) returns string|error { string finalResult = EMPTY_STRING; map|error invoiceMap = invoice.cloneWithType(MapAnyData); - if (invoiceMap is map) { + if invoiceMap is map { string[] keys = invoiceMap.keys(); int position = 0; foreach var invoiceField in invoice { - if (invoiceField is string|decimal) { + if invoiceField is string|decimal { finalResult += setSimpleType(keys[position], invoiceField, TRAN_SALES); - } else if (invoiceField is RecordRef) { + } else if invoiceField is RecordRef { finalResult += getXMLRecordRef(invoiceField); - } else if (invoiceField is Item[]) { + } else if invoiceField is Item[] { string itemXMLList = EMPTY_STRING; foreach Item item in invoiceField { string itemElements = check buildInvoiceItemElement(item); itemXMLList += itemElements; } - finalResult += string`${itemXMLList}`; + finalResult += string`${itemXMLList}`; } position += 1; } @@ -113,11 +113,44 @@ isolated function mapInvoiceRecord(xml response) returns Invoice|error { status: extractStringFromXML(response/**//*), entity: extractRecordRefFromXML(response/**/), currency: extractRecordRefFromXML(response/**/), - internalId: extractRecordInternalIdFromXMLAttribute(response/**/<'record>) + internalId: extractRecordInternalIdFromXMLAttribute(response/**/<'record>), + itemList: check mapItemListMemberRecord(response/**//*) }; return invoice; } +isolated function mapItemListMemberRecord(xml itemMembersXml) returns Item[]|error { + xmlns "urn:sales_2020_2.transactions.webservices.netsuite.com" as tranSales; + Item[] itemMembers = []; + foreach xml element in itemMembersXml { + xml elementItem = element/*; + Item itemMember = { + subscription: extractRecordRefFromXML(elementItem/**//*), + item: extractRecordRefFromXML(elementItem/**/), + quantityAvailable: extractDecimalFromXML(elementItem/**//*), + quantityOnHand: extractDecimalFromXML(elementItem/**//*), + quantity: extractDecimalFromXML(elementItem/**//*), + units: extractRecordRefFromXML(elementItem/**/), + description: extractStringFromXML(elementItem/**//*), + price: extractRecordRefFromXML(elementItem/**/), + rate: extractStringFromXML(elementItem/**//*), + amount: extractDecimalFromXML(elementItem/**//*), + location: extractRecordRefFromXML(elementItem/**//*), + line: extractDecimalFromXML(elementItem/**//*) + }; + boolean|error value = extractBooleanValueFromXMLOrText(element/**//*); + if value is boolean { + itemMember.excludeFromRateRequest = value; + } + value = extractBooleanValueFromXMLOrText(element/**//*); + if value is boolean { + itemMember.isTaxable = value; + } + itemMembers.push(itemMember); + } + return itemMembers; +} + isolated function getInvoiceResult(http:Response response) returns @tainted Invoice|error { xml xmlValue = check formatPayload(response); if (response.statusCode == http:STATUS_OK) { diff --git a/netsuite/account.bal b/netsuite/account.bal index 22d3a05..034dada 100644 --- a/netsuite/account.bal +++ b/netsuite/account.bal @@ -54,6 +54,143 @@ isolated function mapNewAccountRecordFields(NewAccount account) returns string { return finalResult; } +isolated function mapNewItemGroupRecordFields(NewItemGroup itemGroup) returns string|error { + string finalResult = EMPTY_STRING; + map|error itemGroupMap = itemGroup.cloneWithType(MapAnyData); + if itemGroupMap is map { + string[] keys = itemGroupMap.keys(); + int position = 0; + foreach var item in itemGroup { + if item is string|boolean|decimal { + finalResult += setSimpleType(keys[position], item, LIST_ACCT); + } else if item is RecordInputRef { + finalResult += getXMLRecordInputRef(item); + } else if item is RecordRef { + finalResult += getXMLRecordRef(item); + } else if item is RecordRef[] { + string recordRefList = check getRecordRefListInXML(item); + finalResult += string `<${LIST_ACCT}:${keys[position]} xsi:type="nsCore:RecordRefList" + xmlns:nsCore="urn:core_2020_2.platform.webservices.netsuite.com"> + ${recordRefList} + `; + } else if item is ItemMember[] { + string itemMemberList = EMPTY_STRING; + foreach ItemMember itemMember in item { + itemMemberList += string ` + <${LIST_ACCT}:itemMember xsi:type="${LIST_ACCT}:ItemMember"> + ${getItemMemberInXML(itemMember)} + `; + } + finalResult += string ` + <${LIST_ACCT}:memberList xsi:type="${LIST_ACCT}:ItemMemberList" replaceAll="false"> + ${itemMemberList} + `; + } else if item is Translation[] { + string translationList = EMPTY_STRING; + foreach Translation translation in item { + translationList += string ` + <${LIST_ACCT}:translation xsi:type="${LIST_ACCT}:Translation"> + ${getTranslationOrHierarchyVRecordsInXML(translation)} + `; + } + finalResult += string ` + <${LIST_ACCT}:translationsList xsi:type="${LIST_ACCT}:TranslationsList"> + ${translationList} + `; + } else if item is ItemGroupHierarchyVersions[] { + string hierarchyList = EMPTY_STRING; + foreach ItemGroupHierarchyVersions hVersion in item { + hierarchyList += string ` + <${LIST_ACCT}:itemGroupHierarchyVersions xsi:type="${LIST_ACCT}:ItemGroupHierarchyVersions"> + ${getTranslationOrHierarchyVRecordsInXML(hVersion)} + `; + } + finalResult += string ` + <${LIST_ACCT}:hierarchyVersionsList xsi:type="${LIST_ACCT}:ItemGroupHierarchyVersionsList"> + ${hierarchyList} + `; + } else if item is CustomFieldList { + finalResult += check getCustomElementList(item, LIST_ACCT); + } + position += 1; + } + } + return finalResult; +} + +isolated function getTranslationOrHierarchyVRecordsInXML(Translation translation) returns string { + string finalResult = EMPTY_STRING; + map|error translationMap = translation.cloneWithType(MapAnyData); + if translationMap is map { + string[] keys = translationMap.keys(); + int position = 0; + foreach var item in translation { + if item is string|boolean|decimal { + finalResult += setSimpleType(keys[position], item, LIST_ACCT); + } else if item is RecordInputRef { + finalResult += getXMLRecordInputRef(item); + } else if item is RecordRef { + finalResult += getXMLRecordRef(item); + } + position += 1; + } + } + return finalResult; +} + +isolated function getItemMemberInXML(ItemMember itemMember) returns string { + string finalResult = EMPTY_STRING; + map|error itemMemberMap = itemMember.cloneWithType(MapAnyData); + if (itemMemberMap is map) { + string[] keys = itemMemberMap.keys(); + int position = 0; + foreach var item in itemMember { + if item is string|boolean|decimal { + if keys[position] == "vsoeDeferral" { + finalResult += string ` + ${item}`; + } else if keys[position] == "vsoePermitDiscount" { + finalResult += string ` + + ${item} + `; + } else if keys[position]== "itemSource" { + finalResult += string ` + + ${item} + `; + } else { + finalResult += setSimpleType(keys[position], item, LIST_ACCT); + } + } else if item is RecordInputRef { + finalResult += getXMLRecordInputRef(item); + } else if item is RecordRef { + finalResult += getXMLRecordRef(item); + } + position += 1; + } + } + return finalResult; +} + +isolated function getRecordRefListInXML(RecordRef[] records) returns string|error { + string recordListInXML = EMPTY_STRING; + foreach RecordRef ref in records { + recordListInXML += string ``; + } + return recordListInXML; +} + +isolated function wrapItemGroupElements(string subElements) returns string { + return string ` + ${subElements} + `; +} + isolated function wrapAccountElements(string subElements) returns string { return string ` @@ -119,7 +256,7 @@ isolated function mapAccountFields(json accountTypeJson, Account account) return return; } -isolated function getAccountResult(http:Response response) returns @tainted Account|error { +isolated function getAccountResult(http:Response response) returns Account|error { xml xmlValue = check formatPayload(response); if (response.statusCode == http:STATUS_OK) { xml output = xmlValue/**/; @@ -134,6 +271,99 @@ isolated function getAccountResult(http:Response response) returns @tainted Acco } } +isolated function getItemGroupResult(http:Response response) returns ItemGroup|error { + xml xmlValue = check formatPayload(response); + if response.statusCode == http:STATUS_OK { + xml output = xmlValue/**/; + boolean isSuccess = check extractBooleanValueFromXMLOrText(output.isSuccess); + if isSuccess { + ItemGroup itemGroup = check mapItemGroupRecord(xmlValue); + return itemGroup; + } else { + fail error(NO_RECORD_CHECK); + } + } else { + fail error(NO_RECORD_CHECK); + } +} + +isolated function mapItemMemberRecord(xml itemMembersXml) returns ItemMember[]|error { + xmlns "urn:accounting_2020_2.lists.webservices.netsuite.com" as listAcct; + ItemMember[] itemMembers = []; + foreach xml element in itemMembersXml { + ItemMember itemMember = { + memberDescr:extractStringFromXML(element/**//*), + componentYield:extractDecimalFromXML(element/**//*), + bomQuantity:extractDecimalFromXML(element/**//*), + itemSource:extractStringFromXML(element/**//*), + quantity:extractDecimalFromXML(element/**//*), + memberUnit:extractRecordRefFromXML(element/**/), + vsoeDeferral:extractStringFromXML(element/**//*), + vsoePermitDiscount:extractStringFromXML(element/**//*), + taxSchedule:extractRecordRefFromXML(element/**/), + taxcode:extractStringFromXML(element/**//*), + item:extractRecordRefFromXML(element/**/), + taxrate:extractDecimalFromXML(element/**//*), + effectiveDate:extractStringFromXML(element/**//*), + obsoleteDate:extractStringFromXML(element/**//*), + effectiveRevision:extractRecordRefFromXML(element/**/), + obsoleteRevision:extractRecordRefFromXML(element/**/), + lineNumber:extractDecimalFromXML(element/**//*), + memberKey:extractStringFromXML(element/**//*) + }; + boolean|error value = extractBooleanValueFromXMLOrText(element/**//*); + if value is boolean { + itemMember.vsoeDelivered = value; + } + itemMembers.push(itemMember); + } + return itemMembers; +} + +isolated function mapItemGroupRecord(xml response) returns ItemGroup|error { + xmlns "urn:accounting_2020_2.lists.webservices.netsuite.com" as listAcct; + ItemGroup itemGroup = { + internalId: extractRecordInternalIdFromXMLAttribute(response/**/<'record>), + createdDate: extractStringFromXML(response/**//*), + lastModifiedDate: extractStringFromXML(response/**//*), + customForm: extractRecordRefFromXML(response/**/), + defaultItemShipMethod: extractRecordRefFromXML(response/**/), + itemId: extractStringFromXML(response/**//*), + upcCode: extractStringFromXML(response/**//*), + displayName: extractStringFromXML(response/**//*), + vendorName: extractStringFromXML(response/**//*), + issueProduct: extractRecordRefFromXML(response/**/), + parent: extractRecordRefFromXML(response/**/), + description: extractStringFromXML(response/**//*), + department: extractRecordRefFromXML(response/**/), + 'class: extractRecordRefFromXML(response/**/), + location: extractRecordRefFromXML(response/**/), + memberList: check mapItemMemberRecord(response/**//*), + customFieldList: check extractCustomFiledListFromXML(response/**//*) + }; + boolean|error value = extractBooleanValueFromXMLOrText(response/**//*); + if value is boolean { + itemGroup.includeStartEndLines = value; + } + value = extractBooleanValueFromXMLOrText(response/**//*); + if value is boolean { + itemGroup.isVsoeBundle = value; + } + value = extractBooleanValueFromXMLOrText(response/**//*); + if value is boolean { + itemGroup.availableToPartners = value; + } + value = extractBooleanValueFromXMLOrText(response/**//*); + if value is boolean { + itemGroup.isInactive = value; + } + value = extractBooleanValueFromXMLOrText(response/**//*); + if value is boolean { + itemGroup.printItems = value; + } + return itemGroup; +} + isolated function mapAccountRecord(xml response) returns Account|error { xmlns "urn:accounting_2020_2.lists.webservices.netsuite.com" as listAcct; Account account = { @@ -141,7 +371,7 @@ isolated function mapAccountRecord(xml response) returns Account|error { acctType: extractStringFromXML(response/**//*), acctNumber: extractStringFromXML(response/**//*), acctName: extractStringFromXML(response/**//*), - generalRate : extractStringFromXML(response/**//*), + generalRate: extractStringFromXML(response/**//*), cashFlowRate: extractStringFromXML(response/**//*), currency:extractRecordRefFromXML(response/**/) }; diff --git a/netsuite/accountTypes.bal b/netsuite/accountTypes.bal index 594e583..1fdf383 100644 --- a/netsuite/accountTypes.bal +++ b/netsuite/accountTypes.bal @@ -128,7 +128,7 @@ public type NewAccount record { # + openingBalance - Opening Balance of the account # + revalue - Revalue Open Balance for Foreign Currency Transactions # + subsidiary - A subsidiary of the account -type AccountCommon record { +public type AccountCommon record { @display{label: "Account Type"} AccountType|string acctType?; @display{label: "Units Type"} @@ -170,3 +170,237 @@ type AccountCommon record { @display{label: "Account Subsidiary"} Subsidiary subsidiary?; }; + +# Represents an item(member) of an item group +# +# + memberDescr - Description of the member +# + componentYield - Component yield +# + bomQuantity - BOM quantity +# + itemSource - Item source values:(_stock, _phantom, _workOrder, _purchaseOrder) +# + quantity - Quantity of the member +# + memberUnit - Units +# + vsoeDeferral - Deferral value (_deferBundleUntilDelivered, _deferUntilItemDelivered) +# + vsoePermitDiscount - Permit Discount value (_asAllowed, _never) +# + vsoeDelivered - Default as Delivered +# + taxSchedule - Tax schedule +# + taxcode - Tax Code +# + item - Item reference +# + taxrate - Tax rate +# + effectiveDate - Effective date +# + obsoleteDate - ObsoleteDate +# + effectiveRevision - Effective Revision +# + obsoleteRevision - ObsoleteRevision +# + lineNumber - Line number +# + memberKey - Member Key +@display{label: "Item member"} +public type ItemMember record { + @display{label: "Member Description"} + string memberDescr?; + @display{label: "Company Yield"} + decimal? componentYield?; + @display{label: "BOM Quantity"} + decimal? bomQuantity?; + @display{label: "Item Source"} + string itemSource?; + @display{label: "Quantity"} + decimal? quantity?; + @display{label: "Member Unit"} + RecordRef memberUnit?; + @display{label: "VSO Deferral"} + string vsoeDeferral?; + @display{label: "VSO Permit Discount"} + string vsoePermitDiscount?; + @display{label: "VSO Delivered"} + boolean vsoeDelivered?; + @display{label: "Tax Schedule"} + RecordRef taxSchedule?; + @display{label: "Tax Code"} + string taxcode?; + @display{label: "Item Reference"} + RecordRef item; + @display{label: "Tax Rate"} + decimal? taxrate?; + @display{label: "Effective Date"} + string effectiveDate?; + @display{label: "Obsolete Date"} + string obsoleteDate?; + @display{label: "Effective Revision"} + RecordRef effectiveRevision?; + @display{label: "Obsolete Revision"} + RecordRef obsoleteRevision?; + @display{label: "Line Number"} + decimal? lineNumber?; + @display{label: "Member Key"} + string memberKey?; +}; + +# Netsuite itemGroup type record +# +# + internalId - Internal Id of the group +# + externalId - External id of the group +@display{label: "Item Group"} +public type ItemGroup record { + @display{label: "Internal ID"} + string internalId?; + @display{label: "External ID"} + string externalId?; + *ItemGroupCommon; +}; + +# Represents Item Group record in record creation +# +@display{label: "New Item Group"} +public type NewItemGroup record { + *ItemGroupCommon; +}; + +# Netsuite itemGroup type record common fields +# +# + createdDate - Created date +# + lastModifiedDate - Last modified date +# + customForm - Custom form +# + includeStartEndLines - Include Start/End Lines +# + isVsoeBundle - ItemGroup Is VSOE Bundle or not +# + defaultItemShipMethod - Default item ship method +# + availableToPartners - Available to Adv. Partners +# + isInactive - ItemGroup is inactive or not +# + itemId - Item name/number +# + upcCode - UPC Code +# + displayName - Display name/code +# + vendorName - Vendor name +# + issueProduct - The product this item is associated with +# + parent - Parent item +# + description - Item description +# + subsidiaryList - Subsidiary list +# + includeChildren - Include Children field to share the item with all the sub-subsidiaries associated with each +# subsidiary selected in the Subsidiary field. +# + department - Department to associate with this item +# + 'class - Class to associate with this item. +# + location - Location to associate with this item +# + itemShipMethodList - Shipping method list +# + printItems - To display the member items with their respective display names, quantities and descriptions on sales and purchase forms +# + memberList - List of items of the group +# + translationsList - List translations of the group +# + hierarchyVersionsList - List of version hierarchies. +# + customFieldList - Custom Fields +@display{label: "Item Group Common Fields"} +public type ItemGroupCommon record { + @display{label: "Created Date"} + string createdDate?; + @display{label: "LastModified Date"} + string lastModifiedDate?; + @display{label: "Custom Form Reference"} + RecordRef customForm?; + @display{label: "Include Start-End Lines"} + boolean includeStartEndLines?; + @display{label: "Is Vsoe Bundle"} + boolean isVsoeBundle?; + @display{label: "Default Item Ship Method"} + RecordRef defaultItemShipMethod?; + @display{label: "Available To Partners"} + boolean availableToPartners?; + @display{label: "Is Inactive"} + boolean isInactive?; + @display{label: "Item ID"} + string itemId?; + @display{label: "UPC Code"} + string upcCode?; + @display{label: "Display Name"} + string displayName?; + @display{label: "Vendor Name"} + string vendorName?; + @display{label: "Issue Product"} + RecordRef issueProduct?; + @display{label: "Parent"} + RecordRef parent?; + @display{label: "Description"} + string description?; + @display{label: "Subsidiary List"} + RecordRef[] subsidiaryList?; + @display{label: "Include Children"} + boolean includeChildren?; + @display{label: "Department"} + RecordRef department?; + @display{label: "Class"} + RecordRef 'class?; + @display{label: "Location"} + RecordRef location?; + @display{label: "Item Ship Method List"} + RecordRef[] itemShipMethodList?; + @display{label: "Print Items"} + boolean printItems?; + @display{label: "Member List"} + ItemMember[] memberList?; + @display{label: "Translations List"} + Translation[] translationsList?; + @display{label: "Hierarchy Versions List"} + ItemGroupHierarchyVersions[] hierarchyVersionsList?; + @display{label: "Custom Field List"} + CustomFieldList customFieldList?; +}; + +# Netsuite Translation element for the itemGroup +# +# + locale - The location +# + language - language of the translation +# + displayName - Display Name +# + description - Description +# + salesDescription - Sales description +# + storeDisplayName - Store display name +# + storeDescription - Store description +# + storeDetailedDescription - store detail Description +# + featuredDescription - featured Description +# + specialsDescription - special translation Description +# + pageTitle - page Title +# + noPriceMessage - No price message +# + outOfStockMessage - Out of stock message +@display{label: "Netsuite Translation Record"} +public type Translation record { + @display{label: "Netsuite Locale"} + string locale?; + @display{label: "Language"} + string language?; + @display{label: "Display Name"} + string displayName?; + @display{label: "Description"} + string description?; + @display{label: "Sales Description"} + string salesDescription?; + @display{label: "Store Display Name"} + string storeDisplayName?; + @display{label: "Store Description"} + string storeDescription?; + @display{label: "Store Detailed Description"} + string storeDetailedDescription?; + @display{label: "Featured Description"} + string featuredDescription?; + @display{label: "Specials Description"} + string specialsDescription?; + @display{label: "Page Title"} + string pageTitle?; + @display{label: "No Price Message"} + string noPriceMessage?; + @display{label: "Out Of Stock Message"} + string outOfStockMessage?; +}; + +# Represents item group version hierarchies +# +# + isIncluded - Whether it is included +# + hierarchyVersion - Hierarchy version +# + startDate - Start date +# + endDate - End date +# + hierarchyNode - hierarchy node + @display{label: "Netsuite Item Group Hierarchy Versions Record"} +public type ItemGroupHierarchyVersions record { + @display{label: "Is Included"} + boolean isIncluded?; + @display{label: "Hierarchy Version"} + RecordRef hierarchyVersion?; + @display{label: "Start Date"} + string startDate?; + @display{label: "End Date"} + string endDate?; + @display{label: "Hierarchy Node"} + RecordRef hierarchyNode?; +}; diff --git a/netsuite/client.bal b/netsuite/client.bal index 7580906..03a9956 100644 --- a/netsuite/client.bal +++ b/netsuite/client.bal @@ -150,6 +150,18 @@ public isolated client class Client { return getRecordAddResponse(response); } + # Creates a new Item Group record instance in NetSuite according to given item detail. + # + # + itemGroup - ItemGroup type record with detail + # + return - RecordAddResponse type record otherwise the relevant error + @display{label: "Add New Item Group"} + isolated remote function addNewItemGroup(@display{label: "Item Group"}ItemGroupCommon itemGroup) returns @tainted + @display{label: "Response"} RecordAddResponse|error { + xml payload = check buildAddOperationPayload(itemGroup, ITEM_GROUP, self.config); + http:Response response = check sendRequest(self.basicClient, ADD_SOAP_ACTION, payload); + return getRecordAddResponse(response); + } + # Deletes a record instance from NetSuite according to the given detail if they are valid. # # + info - Details of NetSuite record instance to be deleted @@ -186,14 +198,12 @@ public isolated client class Client { return getUpdateResponse(response); } - - # Updates a NetSuite customer instance by internal ID. # # + customer - Customer record with details and internal ID # + return - RecordUpdateResponse type record otherwise the relevant error @display{label: "Update Customer"} - isolated remote function updateCustomerRecord(@display{label: "Customer"} Customer customer) returns @tainted + isolated remote function updateCustomerRecord(@display{label: "Customer"} Customer customer) returns @display{label: "Response"} RecordUpdateResponse|error { xml payload = check buildUpdateOperationPayload(customer, CUSTOMER , self.config); http:Response response = check sendRequest(self.basicClient, UPDATE_SOAP_ACTION, payload); @@ -205,7 +215,7 @@ public isolated client class Client { # + contact - Contact record with details and internal ID # + return - RecordUpdateResponse type record otherwise the relevant error @display{label: "Update Contact"} - isolated remote function updateContactRecord(@display{label: "Contact"} Contact contact) returns @tainted + isolated remote function updateContactRecord(@display{label: "Contact"} Contact contact) returns @display{label: "Response"} RecordUpdateResponse|error { xml payload = check buildUpdateOperationPayload(contact, CONTACT , self.config); http:Response response = check sendRequest(self.basicClient, UPDATE_SOAP_ACTION, payload); @@ -217,7 +227,7 @@ public isolated client class Client { # + currency - Currency record with details and internal ID # + return - RecordUpdateResponse type record otherwise the relevant error @display{label: "Update Currency"} - isolated remote function updateCurrencyRecord(@display{label: "Currency"} Currency currency) returns @tainted + isolated remote function updateCurrencyRecord(@display{label: "Currency"} Currency currency) returns @display{label: "Response"} RecordUpdateResponse|error { xml payload = check buildUpdateOperationPayload(currency, CURRENCY , self.config); http:Response response = check sendRequest(self.basicClient, UPDATE_SOAP_ACTION, payload); @@ -227,11 +237,13 @@ public isolated client class Client { # Updates a NetSuite invoice instance by internal ID. # # + invoice - Invoice record with details and internalId + # + replaceAll - if true, replaces all items with new items in the invoice # + return - RecordUpdateResponse type record otherwise the relevant error @display{label: "Update Invoice"} - isolated remote function updateInvoiceRecord(@display{label: "Invoice"} Invoice invoice) returns @tainted - @display{label: "Response"} RecordUpdateResponse|error { - xml payload = check buildUpdateOperationPayload(invoice, INVOICE , self.config); + isolated remote function updateInvoiceRecord(@display{label: "Invoice"} Invoice invoice, + @display{label: "Replace all items"} boolean replaceAll) returns + @display{label: "Response"} RecordUpdateResponse|error { + xml payload = check buildUpdateOperationPayload(invoice, INVOICE , self.config, replaceAll); http:Response response = check sendRequest(self.basicClient, UPDATE_SOAP_ACTION, payload); return getUpdateResponse(response); } @@ -266,7 +278,7 @@ public isolated client class Client { # + account - Account record with details and internal ID # + return - RecordUpdateResponse type record otherwise the relevant error @display{label: "Update Account"} - isolated remote function updateAccountRecord(@display{label: "Account"} Account account) returns @tainted @display + isolated remote function updateAccountRecord(@display{label: "Account"} Account account) returns @display {label: "Response"} RecordUpdateResponse|error { xml payload = check buildUpdateOperationPayload(account, ACCOUNT , self.config); http:Response response = check sendRequest(self.basicClient, UPDATE_SOAP_ACTION, payload); @@ -293,7 +305,7 @@ public isolated client class Client { # + searchType - Netsuite saved search types # + return - If success returns the list of saved search references otherwise the relevant error @display{label: "Get saved search IDs by record type"} - isolated remote function getSavedSearchIDs(@display{label: "Record type"} string searchType) returns @tainted + isolated remote function getSavedSearchIDs(@display{label: "Record type"} string searchType) returns @display{label: "Response"} SavedSearchResponse|error { xml payload = check BuildSavedSearchRequestPayload(self.config, searchType); http:Response response = check sendRequest(self.basicClient, GET_SAVED_SEARCH_ACTION, payload); @@ -424,6 +436,18 @@ public isolated client class Client { return getCustomerResult(response); } + # Gets a item group record from Netsuite by using internal ID. + # + # + recordInfo - Ballerina record for Netsuite record information + # + return - Customer type record otherwise the relevant error + @display{label: "Get ItemGroup"} + isolated remote function getItemGroupRecord(@display{label: "Record Detail"} RecordInfo recordInfo) returns + @display{label: "Response"} ItemGroup|error { + xml payload = check buildGetOperationPayload(recordInfo, self.config); + http:Response response = check sendRequest(self.basicClient, GET_SOAP_ACTION, payload); + return getItemGroupResult(response); + } + # Gets a contact record from Netsuite by using internal ID. # # + recordInfo - Ballerina record for Netsuite record information @@ -520,4 +544,4 @@ public isolated client class Client { http:Response response = check sendRequest(self.basicClient, soapAction, payload); return response.getXmlPayload(); } - } +} diff --git a/netsuite/commonRecords.bal b/netsuite/commonRecords.bal index e8c77de..51a7678 100644 --- a/netsuite/commonRecords.bal +++ b/netsuite/commonRecords.bal @@ -92,7 +92,7 @@ public type RecordDetail record { string deletionReasonMemo?; }; -# Ballerina record for Netsuite record delete response +# Ballerina record for Netsuite record response # # + recordType - NetSuite Record type Eg: "currency","invoice", netsuite:INVOICE etc. # + recordInternalId - Internal ID of the Netsuite record @@ -105,7 +105,8 @@ public type RecordInfo record { }; # RecordType Connector supports for creation operation for now. -public type NewRecordType NewCustomer|NewContact|NewVendor|NewVendorBill|NewCurrency|NewInvoice|NewClassification|NewAccount|NewSalesOrder; +public type NewRecordType NewCustomer|NewContact|NewVendor|NewVendorBill|NewCurrency|NewInvoice|NewClassification| + NewAccount|NewSalesOrder|NewItemGroup; # RecordType Connector supports for update operation for now. public type ExistingRecordType Customer|Contact|Currency|Invoice|Classification|Account|SalesOrder|VendorBill|Vendor; diff --git a/netsuite/commonUtils.bal b/netsuite/commonUtils.bal index 5e12ac7..2798b71 100644 --- a/netsuite/commonUtils.bal +++ b/netsuite/commonUtils.bal @@ -130,15 +130,15 @@ isolated function buildDeleteOperationPayload(RecordDetail recordType, Connectio } isolated function buildUpdateOperationPayload(ExistingRecordType recordType, RecordCoreType recordCoreType, ConnectionConfig - config) returns xml|error { + config, boolean replaceAll = false) returns xml|error { string header = check buildXMLPayloadHeader(config); - string elements = check getUpdateOperationElements(recordType, recordCoreType); + string elements = check getUpdateOperationElements(recordType, recordCoreType, replaceAll); string body = getUpdateXMLBodyWithParentElement(elements); return getSoapPayload(header, body); } -isolated function getUpdateOperationElements(ExistingRecordType recordType, RecordCoreType recordCoreType) returns - string|error { +isolated function getUpdateOperationElements(ExistingRecordType recordType, RecordCoreType recordCoreType, + boolean replaceAll= false) returns string|error { string subElements = EMPTY_STRING; match recordCoreType { CUSTOMER => { @@ -174,7 +174,7 @@ isolated function getUpdateOperationElements(ExistingRecordType recordType, Reco return wrapAccountElementsToUpdatedWithParentElement(subElements, recordType?.internalId.toString()); } INVOICE => { - subElements = check mapInvoiceRecordFields(recordType); + subElements = check mapInvoiceRecordFields(recordType, replaceAll); return wrapInvoiceElementsToBeUpdatedWithParentElement(subElements, recordType?.internalId.toString()); } _ => { @@ -222,6 +222,10 @@ isolated function getAddOperationElements(NewRecordType recordType, RecordCoreTy subElements = mapNewAccountRecordFields(recordType); return wrapAccountElements(subElements); } + ITEM_GROUP => { + subElements = check mapNewItemGroupRecordFields(recordType); + return wrapItemGroupElements(subElements); + } _ => { fail error(UNKNOWN_TYPE); } diff --git a/netsuite/coreTypes.bal b/netsuite/coreTypes.bal index a7ac07a..09e749b 100644 --- a/netsuite/coreTypes.bal +++ b/netsuite/coreTypes.bal @@ -36,7 +36,7 @@ public type RecordBaseRef record { public type RecordRef record { *RecordBaseRef; @display{label: "Record Type"} - string 'type; + string? 'type; }; # References to NetSuite Records for Input operations diff --git a/netsuite/enums.bal b/netsuite/enums.bal index b548cbf..8696c9b 100644 --- a/netsuite/enums.bal +++ b/netsuite/enums.bal @@ -250,7 +250,7 @@ public enum RecordCoreType { ITEMCUSTOMFIELD = "itemCustomField", ITEMDEMANDPLAN = "itemDemandPlan", ITEMFULFILLMENT = "itemFulfillment", - ITEMGROUP = "itemGroup", + ITEM_GROUP = "itemGroup", ITEMNUMBERCUSTOMFIELD = "itemNumberCustomField", ITEMOPTIONCUSTOMFIELD = "itemOptionCustomField", ITEMSUPPLYPLAN = "itemSupplyPlan", diff --git a/netsuite/salesTypes.bal b/netsuite/salesTypes.bal index 359ffc8..7225e9f 100644 --- a/netsuite/salesTypes.bal +++ b/netsuite/salesTypes.bal @@ -27,7 +27,9 @@ # + rate - Defines the rate for this item. # + amount - Amount of the item # + isTaxable - Shows whether item is taxable -# + location - Locations for details +# + location - Locations for details +# + line - Line number of the invoice item list +# + excludeFromRateRequest - Whether to exclude from the rate request public type Item record { RecordRef subscription?; RecordRef item; @@ -41,6 +43,8 @@ public type Item record { decimal? amount; boolean isTaxable?; RecordRef location?; + decimal? line?; + boolean excludeFromRateRequest?; }; # Netsuite Sales Order type record diff --git a/netsuite/tests/test.bal b/netsuite/tests/test.bal index 0b1e429..a0eaaf2 100644 --- a/netsuite/tests/test.bal +++ b/netsuite/tests/test.bal @@ -45,6 +45,60 @@ string customerAccountId = EMPTY_STRING; string invoiceId = EMPTY_STRING; string vendorId = EMPTY_STRING; string vendorBillId = EMPTY_STRING; +string itemGroupId = EMPTY_STRING; + +@test:Config {enable: true} +function testAddItemGroupRecordOperation() { + log:printInfo("testItemGroupRecord"); + RecordInputRef subsidiary = { + internalId: "1", + 'type: "subsidiary" + }; + ItemMember itemMember01 = { + quantity: 1, + item: { + internalId: "8", + 'type: "item" + } + }; + ItemMember itemMember02 = { + quantity: 2, + item: { + internalId: "14", + 'type: "item" + } + }; + NewItemGroup itemGroup = { + isVsoeBundle: false, + itemId: "Netsuite Test Item Group_04", + displayName: "Netsuite test item group_04", + description: "This is test item group", + subsidiaryList: [subsidiary], + memberList:[itemMember01, itemMember02] + }; + RecordAddResponse|error output = netsuiteClient->addNewItemGroup(itemGroup); + if output is RecordAddResponse { + log:printInfo(output.toString()); + itemGroupId = output.internalId; + } else { + test:assertFail(output.toString()); + } +} + +@test:Config {enable: true, dependsOn: [testAddItemGroupRecordOperation]} +function testGetItemGroupRecord() { + log:printInfo("testGetItemGroupRecord"); + RecordInfo ref = { + recordInternalId: itemGroupId, + recordType: "itemGroup" + }; + ItemGroup|error output = netsuiteClient->getItemGroupRecord(ref); + if output is ItemGroup { + log:printInfo(output.internalId.toString()); + } else { + test:assertFalse(true, output.toString()); + } +} @test:Config {enable: true} function testAddContactRecordOperation() { @@ -206,7 +260,7 @@ function testAddInvoiceRecord() { RecordAddResponse|error output = netsuiteClient->addNewInvoice(invoice); if (output is RecordAddResponse) { log:printInfo(output.toString()); - invoiceId = <@untainted>output.internalId; + invoiceId = output.internalId; } else { test:assertFail(output.toString()); } @@ -234,7 +288,7 @@ function testAddSalesOrderOperation() { RecordAddResponse|error output = netsuiteClient->addNewSalesOrder(salesOrder); if (output is RecordAddResponse) { log:printInfo(output.toString()); - salesOrderId = <@untainted>output.internalId; + salesOrderId = output.internalId; } else { test:assertFail(output.toString()); } @@ -255,7 +309,7 @@ function testAddClassificationRecord() { RecordAddResponse|error output = netsuiteClient->addNewClassification(classification); if (output is RecordAddResponse) { log:printInfo(output.toString()); - classificationId = <@untainted>output.internalId; + classificationId = output.internalId; } else { test:assertFail(output.toString()); } @@ -277,7 +331,7 @@ function testAddAccountRecord() { RecordAddResponse|error output = netsuiteClient->addNewAccount(account); if (output is RecordAddResponse) { log:printInfo(output.toString()); - customerAccountId = <@untainted>output.internalId; + customerAccountId = output.internalId; } else { test:assertFail(output.toString()); } @@ -358,7 +412,7 @@ function testSalesOrderUpdateOperation() { RecordUpdateResponse|error output = netsuiteClient->updateSalesOrderRecord(salesOrder); if (output is RecordUpdateResponse) { log:printInfo(output.toString()); - salesOrderId = <@untainted>output.internalId; + salesOrderId = output.internalId; } else { test:assertFail(output.toString()); } @@ -402,15 +456,25 @@ function testUpdateAccountRecord() { @test:Config { enable: true, - dependsOn: [testAddInvoiceRecord] + dependsOn: [testAddInvoiceRecord] } function testUpdateInvoiceRecord() { log:printInfo("testUpdateInvoiceRecord"); + Item item01 = { + item: { + internalId: "14", + 'type: "item" + }, + quantity: 20, + amount: 1040, + line: 2 + }; Invoice invoice = { internalId: invoiceId, - email: "test@ecosystem.com" + email: "test@ecosystem.com", + itemList: [item01] }; - RecordUpdateResponse|error output = netsuiteClient->updateInvoiceRecord(invoice); + RecordUpdateResponse|error output = netsuiteClient->updateInvoiceRecord(invoice, false); if (output is RecordAddResponse) { log:printInfo(output.toString()); } else { @@ -653,6 +717,24 @@ function testDeleteInvoiceRecord() { } } +@test:Config { + enable: true, + dependsOn: [testAddItemGroupRecordOperation, testGetItemGroupRecord] +} +function testDeleteItemGroupRecord() { + log:printInfo("testDeleteItemGroupRecord"); + RecordDetail recordDeletionInfo = { + recordInternalId: itemGroupId, + recordType: ITEM_GROUP + }; + RecordDeletionResponse|error output = netsuiteClient->deleteRecord(recordDeletionInfo); + if (output is RecordDeletionResponse) { + log:printInfo(output.toString()); + } else { + test:assertFail(output.toString()); + } +} + @test:Config {enable: true} function testGetAllCurrencyRecords() { log:printInfo("testGetAllCurrencyRecords"); @@ -765,7 +847,7 @@ function testGetClassificationRecordOperation() { @test:Config { enable: true, - dependsOn: [testAddInvoiceRecord] + dependsOn: [testAddInvoiceRecord] } function testInvoiceRecordGetOperation() { log:printInfo("testInvoiceRecordGetOperation");