diff --git a/docs/Attachments.md b/docs/Attachments.md index d29d6e91..c6f49f2b 100644 --- a/docs/Attachments.md +++ b/docs/Attachments.md @@ -24,7 +24,7 @@ This helper contains the following functions: The `Attachment` object also provides the following method: * `getContent(writestream)` -* `save(ownerpath, data[, stream])` +* `save(ownerpath, data[, stream, options])` ### Creating a new attachment using a file stream (recommended) @@ -59,6 +59,20 @@ Some points to note with the code snippet above: * The promise that is returned by the `.save()` function contains a response object. This has a bunch of information about the state of the response, but also contains an `entities` array. This array is what actually contains the object that was just created. * For single object saving, this `entities` array should only ever contain one element, but some objects support a multiple object save and in this case the `entities` array will contain all of the objects that were created. +### Options / IncludeOnline + +The options parameter currently only supports one field `IncludeOnline`. This can be used on accounts receivable invoices to choose whether the attachment should or should not be available on the Online Invoice. + +This parameter defaults to `false` if not provided. + +```javascript +attachmentPlaceholder.save("Invoices/" + invoice.InvoiceID, filePath, false, {IncludeOnline: true}) + .then(function(attachments){ + //Attachment has been created + var myAttachment = attachments.entities[0]; + }) +``` + ### Creating a new attachment using a file reference This method works, but it is not recommended as it requires the entire attachment to be loaded into memory and passed around. The streaming method is far more efficient. diff --git a/lib/application.js b/lib/application.js index d3ad1dd3..d0b15044 100644 --- a/lib/application.js +++ b/lib/application.js @@ -123,6 +123,11 @@ Object.assign(Application.prototype, { if (options.unitdp) params.unitdp = options.unitdp; + //Added to support attachments POST/PUT for invoices + //being included on the online invoice + if (options.IncludeOnline === true) + params.IncludeOnline = 'true'; + var endPointUrl = options.api === 'payroll' ? self.options.payrollAPIEndPointUrl : self.options.coreAPIEndPointUrl; var url = self.options.baseUrl + endPointUrl + path; if (!_.isEmpty(params)) @@ -527,7 +532,11 @@ Object.assign(Application.prototype, { return new Batch(application); }, xml2js: function(xml) { - var parser = new xml2js.Parser({ explicitArray: false }); + var parser = new xml2js.Parser({ + explicitArray: false, + valueProcessors: [xml2js.processors.parseBooleans] + }); + return new Promise(function(resolve, reject) { parser.parseString(xml, function(err, result) { if (err) return reject(err); diff --git a/lib/entities/accounting/attachment.js b/lib/entities/accounting/attachment.js index 4340d6c4..a31d76d4 100644 --- a/lib/entities/accounting/attachment.js +++ b/lib/entities/accounting/attachment.js @@ -8,7 +8,8 @@ var AttachmentSchema = new Entity.SchemaObject({ FileName: { type: String, toObject: 'always' }, Url: { type: String, toObject: 'always' }, MimeType: { type: String, toObject: 'always' }, - ContentLength: { type: Number, toObject: 'always' } + ContentLength: { type: Number, toObject: 'always' }, + OnlineInvoice: { type: Boolean, toObject: 'hasValue' } }); @@ -21,10 +22,12 @@ var Attachment = Entity.extend(AttachmentSchema, { getContent: function(writeStream) { return this.application.core.attachments.getContent(this, writeStream); }, - save: function(ownerPath, data, stream) { + save: function(ownerPath, data, stream, options) { var self = this; var path = ownerPath + '/Attachments/' + this.FileName; + options = options || {}; + if (!stream) { //we'll assume data is not a file stream, so we'll create one data = fs.createReadStream(data); @@ -34,12 +37,11 @@ var Attachment = Entity.extend(AttachmentSchema, { return Promise.reject(new Error("Data must be a valid read stream or file handle.")); } - var options = { - contentType: this.MimeType, - entityPath: 'Attachments.Attachment', - entityConstructor: function(data) { - return self.application.core.attachments.newAttachment(data); - } + //Adding other options for saving purposes + options.contentType = this.MimeType; + options.entityPath = 'Attachments.Attachment'; + options.entityConstructor = function(data) { + return self.application.core.attachments.newAttachment(data); }; return this.application.putOrPostEntity('post', path, data, options); diff --git a/lib/entities/accounting/taxrate.js b/lib/entities/accounting/taxrate.js index 9d81a8e3..999046c3 100644 --- a/lib/entities/accounting/taxrate.js +++ b/lib/entities/accounting/taxrate.js @@ -21,7 +21,7 @@ var TaxComponentSchema = new Entity.SchemaObject({ Name: { type: String, toObject: 'always' }, Rate: { type: String, toObject: 'always' }, //Hacked to string as the framework doesn't recursively translate nested objects - IsCompound: { type: String } + IsCompound: { type: String, toObject: 'hasValue' } }); var TaxRate = Entity.extend(TaxRateSchema, { diff --git a/test/accountingtests.js b/test/accountingtests.js index 9d2e9bef..aafc951b 100644 --- a/test/accountingtests.js +++ b/test/accountingtests.js @@ -1281,8 +1281,7 @@ describe('regression tests', function() { expect(taxComponent.Name).to.not.equal(""); expect(taxComponent.Name).to.not.equal(undefined); expect(taxComponent.Rate).to.be.a('String'); - //Hacked to a string as the framework doesn't recursively translate nested objects - expect(taxComponent.IsCompound).to.be.oneOf(["true", "false"]); + expect(taxComponent.IsCompound).to.be.oneOf([true, false]); }); }); done(); @@ -1332,7 +1331,7 @@ describe('regression tests', function() { //This is hacked toString() because of: https://github.com/jordanwalsh23/xero-node/issues/13 expect(taxComponent.Rate).to.equal(taxrate.TaxComponents[0].Rate.toString()); - expect(taxComponent.IsCompound).to.equal(taxrate.TaxComponents[0].IsCompound.toString()); + expect(taxComponent.IsCompound).to.equal(taxrate.TaxComponents[0].IsCompound); }); done(); }) @@ -2566,6 +2565,46 @@ describe('regression tests', function() { }); }); + it('creates an attachment on an invoice using a file reference and online invoice set to true', function(done) { + var attachmentTemplate = { + FileName: "1-test-attachment.pdf", + MimeType: "application/pdf" + }; + + var sampleDataReference = __dirname + "/testdata/test-attachment.pdf"; + + var attachmentPlaceholder = currentApp.core.attachments.newAttachment(attachmentTemplate); + + //Add attachment to an Invoice + var filter = 'Type == "ACCREC"'; + currentApp.core.invoices.getInvoices({ where: filter }) + .then(function(invoices) { + var sampleInvoice = invoices[0]; + attachmentPlaceholder.save("Invoices/" + sampleInvoice.InvoiceID, sampleDataReference, false, { IncludeOnline: true }) + .then(function(response) { + expect(response.entities.length).to.equal(1); + var thisFile = response.entities[0]; + expect(thisFile.AttachmentID).to.not.equal(""); + expect(thisFile.AttachmentID).to.not.equal(undefined); + expect(thisFile.FileName).to.equal(attachmentTemplate.FileName); + expect(thisFile.MimeType).to.equal(attachmentTemplate.MimeType); + expect(thisFile.ContentLength).to.be.greaterThan(0); + expect(thisFile.Url).to.not.equal(""); + expect(thisFile.Url).to.not.equal(undefined); + expect(thisFile.IncludeOnline).to.equal(true); + done(); + }) + .catch(function(err) { + console.log(util.inspect(err, null, null)); + done(wrapError(err)); + }) + }) + .catch(function(err) { + console.log(util.inspect(err, null, null)); + done(wrapError(err)); + }); + }); + it('gets the content of an attachment as stream', function(done) { //Add attachment to an Invoice currentApp.core.invoices.getInvoice(invoiceID)