Giter VIP home page Giter VIP logo

quickbooks-sync's Introduction

QuickBooks Sync Build Status

QuickBooks Sync regroups multiple NuGet packages to sync data from QuickBooks Desktop using QbXml. Make requests to QuickBooks Desktop, analyze the returned values, etc.

This project is actively maintained and is in its early alpha stage. Many breaks will be introduced until stability is reached.

QbXml

Installation

Install-Package QbSync.QbXml

Introduction

QbXml is the language used by QuickBooks desktop to exchange back and forth data between an application and the QuickBooks database.

Here is a couple of ideas how you can make some requests and parse responses.

Create a request XML with QbXml

public class CustomerRequest
{
  public CustomerRequest()
  {
    var request = new QbXmlRequest();
    var innerRequest = new CustomerQueryRqType();

    // Add some filters here
    innerRequest.MaxReturned = "100";
    innerRequest.FromModifiedDate = new DATETIMETYPE(DateTime.Now);

    request.AddToSingle(innerRequest);

    // Get the XML
    var xml = request.GetRequest();
  }
}

Receive a response from QuickBooks and parse the QbXml

public class CustomerResponse
{
  public void LoadResponse(string xml)
  {
    var response = new QbXmlResponse();
    var rs = response.GetSingleItemFromResponse<CustomerQueryRsType>(xml);

    // Receive customer objects, corresponding to the xml
    var customers = rs.CustomerRet;
  }
}

Web Connector

Installation

Install-Package QbSync.WebConnector
Install-Package QbSync.WebConnector.AspNetCore

The WebConnector.AspNetCore contains reference to SoapCore/AspNetCore. If you wish to create your steps in a library that does not have this dependency, you may install the WebConnector package.

Introduction

Version 1.0.0 supports .NET Standard 2.0. We follow the dependency injection standard to load the services. We abstracted the SOAP protocol so you only have to implement necessary services in order to make your queries to QuickBooks.

Thanks to the Web Connector, you can communicate with QuickBooks Desktop. Users must download it at the following address: Intuit Web Connector

The Web Connector uses the SOAP protocol to talk with your website, the NuGet package takes care of the heavy lifting to talk with the QuickBooks Desktop. However, you must implement some services in order to get everything working according to your needs. The Web Connector will come periodically to your website asking if you have any requests to do to its database. With the nature of SOAP in mind, there are no protocols keeping the connection state between QuickBooks and your server. For this reason, your server needs to keep track of sessions with a database.

How does it work?

Once the Web Connector downloaded, your user must get a QWC file that will connect the Web Connector to your website. To generate a QWC file, load the appropriate service then pass in your model:

public MyController(IWebConnectorQwc webConnectorQwc)
{
    this.webConnectorQwc = webConnectorQwc;
}

// ...

var data = webConnectorQwc.GetQwcFile(new QbSync.WebConnector.Models.WebConnectorQwcModel
{
    AppName = "My App",
    AppDescription = "Sync QuickBooks with My Website",
    AppSupport = $"{url}/support",
    AppURL = $"{url}/QBConnectorAsync.asmx",
    FileID = Guid.NewGuid(), // Don't generate a new guid all the time, save it somewhere
    OwnerID = Guid.NewGuid(), // Don't generate a new guid all the time, save it somewhere
    UserName = "jsgoupil",
    RunEvery = new TimeSpan(0, 30, 0),
    QBType = QbSync.WebConnector.Models.QBType.QBFS
});

Register the services

In your Startup.cs, registers the services as follow:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddWebConnector(options =>
        {
            options
                .AddAuthenticator<Authenticator>()

                //.WithMessageValidator<MyMessageValidator>()
                //.WithWebConnectorHandler<MyWebConnectorHandler>()

                // Register steps; the order matters.
                .WithStep<CustomerQuery.Request, CustomerQuery.Response>()
                .WithStep<InvoiceQuery.Request, InvoiceQuery.Response>();
        });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // Before UseMvc()
    app
        .UseWebConnector(options =>
        {
            options.SoapPath = "/QBConnectorAsync.asmx";
        });

    app.UseMvc();
}

Implement an Authenticator

An authenticator will keep the state in your database of what is happening with the Web Connector session.

public interface IAuthenticator
{
    Task<IAuthenticatedTicket> GetAuthenticationFromLoginAsync(string login, string password);
    Task<IAuthenticatedTicket> GetAuthenticationFromTicketAsync(string ticket);
    Task SaveTicketAsync(IAuthenticatedTicket ticket);
}
  1. GetAuthenticationFromLoginAsync - Authenticates a user from its login/password combination.
  2. GetAuthenticationFromTicketAsync - Authenticates a ticket previously given from a GetAuthenticationFromLogin call.
  3. SaveTicketAsync - Saves the ticket to the database.

The IAuthenticatedTicket contains 3 mandatory properties:

public interface IAuthenticatedTicket
{
  string Ticket { get; set; }
  string CurrentStep { get; set; }
  bool Authenticated { get; set; }
}
  1. Ticket - Exchanged with the Web Connector. It acts as a session identifier.
  2. CurrentStep - State indicating at which step the Web Connector is currently at.
  3. Authenticated - Simple boolean indicating if the ticket is authenticated.

If a user is not authenticated, make sure to return a ticket value, but set the Authenticated to false. You may want to attach more properties to the interface, such as your UserId, TimeZone, etc.

Implement a step

By registering a step such as CustomerQuery, you can get customers from the QuickBooks database. Since all steps will require a database manipulation on your side, you have to implement it yourself. But don't worry, it is pretty simple. The classes request and response are split because it is important to understand that they do not share fields: the HTTP requests coming from the Web Connector are made separately.

Don't forget to register them in your startup. The order matters, the steps will be executed in the order you provided.

After the step has executed, the next step in order will run.

Here is an example:

public class CustomerQuery
{
    public const string NAME = "CustomerQuery";

    public class Request : StepQueryRequestBase<CustomerQueryRqType>
    {
        public override string Name => NAME;

        protected override Task<bool> ExecuteRequestAsync(IAuthenticatedTicket authenticatedTicket, CustomerQueryRqType request)
        {
            // Do some operations on the customerRequest to get only specific ones
            request.FromModifiedDate = new DateTimeType(DateTime.Now);

            return base.ExecuteRequestAsync(authenticatedTicket, request);
        }
    }

    public class Response : StepQueryResponseBase<CustomerQueryRsType>
    {
        public override string Name => NAME;

        protected override Task ExecuteResponseAsync(IAuthenticatedTicket authenticatedTicket, CustomerQueryRsType response)
        {
            // Execute some operations with your database.

            return base.ExecuteResponseAsync(authenticatedTicket, response);
        }
    }
}

The 2 classes CustomerQueryRqType/CustomerQueryRsType are provided by the QbXml NuGet package. You associate the request and the response. They implement QbRequest and QbResponse. To find the correct request and response pair, visit https://static.developer.intuit.com/qbSDK-current/common/newosr/index.html

Implement step with an iterator

When you make a request to the QuickBooks database, you might receive hundreds of objects back. Your server or the database won't be able to handle that many; you have to break the query into batches. We have everything handled for you, but we need to save another state to the database. Instead of deriving from StepQueryResponseBase, you have to derive from StepQueryWithIterator and implement 2 methods.

  • In the request *
protected abstract Task<string> RetrieveMessageAsync(IAuthenticatedTicket ticket, string key);
  • In the response *
protected abstract void SaveMessageAsync(IAuthenticatedTicket ticket, string key, string value);

Save the message to the database based on its ticket, CurrentStep and key. Then retrieve it from the same keys.

By default, if you derive from the iterator, the query is batched with 100 objects.

The requests and responses that support an iterator implements QbIteratorRequest and QbIteratorResponse.

Implement a step with multiple requests

If you wish to send more than one request at once to QuickBooks, inherit from GroupStepQueryRequestBase and GroupStepQueryResponseBase and send as many objects you want to QuickBooks. Keep in mind that you should keep the final result under a certain size to allow your server to be able to parse it.

Look at this example which make a CustomerAdd and a CustomerQuery in one step.

public class CustomerGroupAddQuery
{
    public const string NAME = "CustomerGroupAddQuery";

    public class Request : GroupStepQueryRequestBase
    {
        public override string Name => NAME;

        private readonly ApplicationDbContext dbContext;

        public Request(
            ApplicationDbContext dbContext
        )
        {
            this.dbContext = dbContext;
        }

        protected override Task<IEnumerable<IQbRequest>> ExecuteRequestAsync(IAuthenticatedTicket authenticatedTicket)
        {
            var list = new List<IQbRequest>
            {
                new CustomerAddRqType
                {
                    CustomerAdd = new QbSync.QbXml.Objects.CustomerAdd
                    {
                        Name = "Unique Name" + Guid.NewGuid().ToString("D"),
                        FirstName = "User " + authenticatedTicket.GetUserId().ToString()
                    }
                },
                new CustomerQueryRqType
                {
                    ActiveStatus = ActiveStatus.All
                }
            };

            return Task.FromResult(list as IEnumerable<IQbRequest>);
        }

        protected override Task<QBXMLMsgsRqOnError> GetOnErrorAttributeAsync(IAuthenticatedTicket authenticatedTicket)
        {
            // This is the default behavior, use this overriden method to change it to stopOnError
            // QuickBooks does not support rollbackOnError
            return Task.FromResult(QBXMLMsgsRqOnError.continueOnError);
        }
    }

    public class Response : GroupStepQueryResponseBase
    {
        public override string Name => NAME;

        private readonly ApplicationDbContext dbContext;

        public Response(
            ApplicationDbContext dbContext
        )
        {
            this.dbContext = dbContext;
        }

        protected override Task ExecuteResponseAsync(IAuthenticatedTicket authenticatedTicket, IEnumerable<IQbResponse> responses)
        {
            foreach (var item in responses)
            {
                switch (item)
                {
                    case CustomerQueryRsType customerQueryRsType:
                        // Do something with the CustomerQuery data
                        break;
                    case CustomerAddRsType customerAddRsType:
                        // Do something with the CustomerAdd data
                        break;
                }
            }

            return base.ExecuteResponseAsync(authenticatedTicket, responses);
        }
    }
}

Changing the step order at runtime

If you want to change the step order at runtime, you may implement the following methods:

public interface IStepQueryResponse
{
    Task<string?> GotoStepAsync();
    Task<bool> GotoNextStepAsync();
}
  1. GotoStepAsync - Indicates the exact step name you would like to go. If you return null, the GotoNextStepAsync will be called.
  2. GotoNextStepAsync - Indicates if you should go to the next step or not. If you return false, the same exact step will be executed.

Implement a MessageValidator

QuickBooks supports multiple versions. However, this package supports only version 13.0 and above. In order to validate a request, you must provide a IMessageValidator. The reason this package cannot validate the version is because of the nature of the Web Connector: it takes 2 calls from the Web Connector to validate the version then warn the user.

  1. The first call sends a version to your server. You can validate the version and must save the ticket for reference in the second call.
  2. The second call, you need to tell the Web Connector the version was wrong based on the ticket saved in step 1.

Since this is done with two requests, the first request must persist that the version is wrong based on the ticket. With IsValidTicket, simply check if the ticket has been saved in your database (as invalid). If you find the ticket in your database, you can safely remove it from it as this method will not be called again with the same ticket.

This step is optional. If you don't implement a MessageValidator, we assume that the version is valid.

The MessageValidator can also be used to get the company file path that QuickBooks sends you.

Implement a WebConnectorHandler

This step is optional, the handler allows you to receive some calls from the Web Connector that you can take further actions.

If you do not wish to implement all the methods, you can override WebConnectorHandlerNoop.

public interface IWebConnectorHandler
{
    Task ProcessClientInformationAsync(IAuthenticatedTicket authenticatedTicket, string response);
    Task OnExceptionAsync(IAuthenticatedTicket authenticatedTicket, Exception exception);
    Task<int> GetWaitTimeAsync(IAuthenticatedTicket authenticatedTicket);
    Task<string> GetCompanyFileAsync(IAuthenticatedTicket authenticatedTicket);
    Task CloseConnectionAsync(IAuthenticatedTicket authenticatedTicket);
}
  1. ProcessClientInformationAsync - Returns the configuration QuickBooks is in. This method is called once per session.
  2. OnExceptionAsync - Called when any types of exception occur on the server.
  3. GetWaitTimeAsync - Tells the Web Connector to come back later after X seconds. Returning 0 means to do the work immediately.
  4. GetCompanyFileAsync- Uses the company file path. Return an empty string to use the file that is currently opened.
  5. CloseConnectionAsync - The connection is closing; the Web Connector will not come back with this ticket.

Handling Timestamps

QuickBooks does not handle Daylight Saving Time (DST) properly. The DATETIMETYPE class in this library is aware of this issue and will correct timestamps coming from QuickBooks by removing the offset values in the common use cases.

Internally, QuickBooks returns an incorrect date time offset during DST. Consequently, QuickBooks expects that you send the date time with the same incorrect offset OR a date time, without an offset, in the computer's time zone where QuickBooks is installed.

In order to get correct dates from a DATETIMETYPE, you can do the following:

var savedString = request.FromModifiedDate.ToString();
// -> 2019-03-21T11:37:00 ; this value is the local time when the object has been modified
var savedDateTime = request.FromModifiedDate.ToDateTime();
// -> An unspecified `DateTime` representing the local time when the object has been modified

To re-create a DATETIMETYPE to use in a subsequent query, you may use one of the following methods:

request.FromModifiedDate = DATETIMETYPE.Parse(savedString);

or

request.FromModifiedDate = new DATETIMETYPE(savedDateTime);

Because the request.FromModifiedDate is inclusive, a common practice is to add one second to the previous date before making the query:

request.FromModifiedDate = new DATETIMETYPE(savedDateTime.AddSeconds(1));
request.FromModifiedDate = DATETIMETYPE.Parse(savedString).Add(TimeSpan.FromSeconds(1));

The above methods are the recommended approach, which will be the least likely to give you query issues due to QuickBooks DST issues.

If you truly need the original uncorrected value returned from QuickBooks that has a potentially incorrect offset, you can use:

request.FromModifiedDate.QuickBooksRawString;
// -> 2019-03-21T11:37:00-08:00 ; the original string returned from QuickBooks
request.FromModifiedDate.UncorrectedDate;
// -> A nullable `DateTimeOffset` parsed value of the QuickBooksRawString

Note: The UncorrectedDate while nullable, will never be null if the DATETIMETYPE was generated from a QuickBooks response

To re-create a DATETIMETYPE for a query in this situation:

request.FromModifiedDate = DATETIMETYPE.Parse(rawString);
request.FromModifiedDate = DATETIMETYPE.FromUncorrectedDate(uncorrectedDate);

A use case for this method is if you are required to persist a DateTimeOffset, or any other UTC-based method, so that you can accurately use the value to do a future query.

These methods should not be used to show the value to an end-user since it may appear to be an hour off during DST.


If you need to display the date to the user, you can get the DateTimeOffset by providing the correct TimeZoneInfo as such:

var quickBooksTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var dateTime = customerRet.TimeModified.ToDateTime();
var correctedOffset = quickBooksTimeZone.GetUtcOffset(dateTime);
var correctedDateTimeOffset = new DateTimeOffset(dateTime, correctedOffset);

Internally how it works

If you are not contributing to this project, you most likely don't need to read this section.

The Web Connector executes the following tasks:

  1. Authenticate - Sends the login/password that you must verify. You also return a session ticket that will be used for the rest of messages that are exchanged back and forth.
  2. SendRequestXML - The Web Connector expects that you return an XML command that will execute on the database.
  3. ReceiveRequestXML - Response regarding the previous step.
  4. GOTO Step 2 - Until you return an empty string, indicating that you are done.
  5. CloseConnection - Connection is done.

QbManager

The QbManager can be overriden in order to handle the communication at a lower level. You most likely don't need to do this. Use the WebConnectorHandler.

  1. SaveChangesAsync - Called before returning any data to the Web Connector. It's time to save data to your database.
  2. LogMessage - Data going in or out goes through this method, you can save it to a database in order to better debug.
  3. GetWaitTimeAsync - Tells the Web Connector to come back in X seconds.
  4. AuthenticateAsync - Verifies if the login/password is correct. Returns appropriate message to the Web Connector in order to continue further communication.
  5. ServerVersion - Returns the server version.
  6. ClientVersion - Indicates which version is the Web Connector. Returns W: to return a warning; E: to return an error. Empty string if everything is fine.
  7. SendRequestXMLAsync - The Web Connector is asking what has to be done to its database. Return an QbXml command.
  8. ReceiveRequestXMLAsync - Response from the Web Connector based on the previous command sent.
  9. GetLastErrorAsync - Gets the last error that happened. This method is called only if an error is found.
  10. ConnectionErrorAsync - An error happened with the Web Connector.
  11. CloseConnectionAsync - Closing the connection. Return a string to show to the user in the Web Connector.
  12. OnExceptionAsync - Called if any of your steps throw an exception. It would be a great time to log this exception for future debugging.
  13. ProcessClientInformationAsync - Called when the Web Connector first connect to the service. It contains the information about the QuickBooks database.
  14. GetCompanyFileAsync - Indicates which company file to use on the client. By default, it uses the one currently opened.

XSD Generator

The XSD generator that Microsoft provides does not embed enough information in the resulting C#. For this reason, this project has its own code generator which enhanced a lot of types. For instance, we order properly the items in the XML. We add some length restriction. We add some interfaces.

We marked some properties as deprecated as we found out QuickBooks was emitting a warning when using them. If you find more properties, let us know.

We use a modified version of the XSD provided from QuickBooks; after working on this project, we found that the XSD are not up to date with the latest information.

Contributing

Contributions are welcome. Code or documentation!

  1. Fork this project
  2. Create a feature/bug fix branch
  3. Push your branch up to your fork
  4. Submit a pull request

License

QuickBooksSync is under the MIT license.

quickbooks-sync's People

Contributors

adamskt avatar davidcc avatar dteske25 avatar jsgoupil avatar rogerfar avatar tofer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

quickbooks-sync's Issues

Common Interfaces

It would be very helpful if common types had either common base types or common interfaces.

For Example, VendorAddress and BillAddress are both identical address types.
But since the generated code doesn't generate them from a common type or assign a common interface, mapping code requires either reflection or copy-paste duplication.

Another example would be the common properties of all Mod types or Ret types (ListId, EditSequence, etc.).

If the XSD and generator can't be updated to do this automatically, it would be possible to add them manually if the generator built all types with the partial keyword. Then common interfaces could be manually added to your codebase and associated with appropriate types.

Assocating exceptions with tickets

Just filing an issue for a future issue I will address in a PR.

Currently, when an exception occurs in SendRequestXML or ReceiveRequestXML, exceptions are caught and forwarded to an overridable method, but neglect to pass in the ticket if one was found or not. This makes it difficult to track errors by tickets when Web Connector comes knocking for the last error by ticket. I propose there should be a way to catch exceptions after the ticket is retrieved, then pass that ticket to the exception handling.

Solution 1

try
{
    var authenticatedTicket = authenticator.GetAuthenticationFromTicket(ticket);

    try
    {
        ...
    }
    catch (Exception ex)
    {
        OnException(authenticatedTicket, ex);
    }
}
catch (Exception ex)
{
    OnException(null, ex);
}

Soltution 2

public class QbSyncException : Exception
{
    public QbSyncException(AuthenticatedTicket ticket, Exception innerException)
        : base(null, innerException)
    {
        this.Ticket = ticket;
    }

    public AuthenticatedTicket Ticket { get; private set; }
}

try
{
    var authenticatedTicket = authenticator.GetAuthenticationFromTicket(ticket);

    try
    {
        ...
    }
    catch (Exception ex)
    {
        throw new QbSyncException(authenticatedTicket, ex);
    }
}
catch (QbSyncException ex)
{
    OnException(ex.Ticket, ex);
}
catch (Exception ex)
{
    OnException(null, ex);
}

Authenticator Null Reference

Hi,

I am in QbManager > AuthenticateAsync.

I receive a null reference here on line 82:

quickbooks-sync\test\WebApplication.Sample\Application\Authenticator.cs

How is your code getting the current step? I just passed the

image

Tracking iterator progress

Currently, example contains the following code:

var lastFromModifiedDate = response.CustomerRet.OrderBy(m => m.TimeModified).Select(m => m.TimeModified).LastOrDefault();
await dbContext.SaveIfNewerAsync(LAST_MODIFIED_CUSTOMER, lastFromModifiedDate);

I've noticed that entities received in response are not sorted and in case sync is interrupted next sync can skip some records.

Example. The first batch has lastFromModifiedDate = 01.01.2020, the second batch lastFromModifiedDate = 01.01.2019, third batch lastFromModifiedDate = 21.05.2020. So LAST_MODIFIED_CUSTOMER is now 21.05.2020, but there are few more batches pending (i.e. from 2018). If system crashes or connection drops - next sync will set request.FromModifiedDate = 21.05.2020... and miss all remaining items.

Is there any way to track sync in a safe manner?

QuickBooks does not handle UTC time properly, so we should have a way to handle timezone for the developer

A request as such:
2016-05-17T20:00:00
Actually means that it checks for 20:00 LOCAL time on QuickBooks machine.

If we are in PST. For 20:00 local time, it's actually 3:00am (next day)

Now, if we set
request.FromModifiedDate = new DateTime(long.Parse(lastSync), DateTimeKind.Utc); // 3:00am UTC

This will actually end up requesting a real UTC time. Then QuickBooks will actually think you are requesting a 3:00am local time.

Then I have to end up doing this:
request.FromModifiedDate = new DateTime(long.Parse(lastSync), DateTimeKind.Utc).ToLocalTime(); // Which would be the client timezone

or

request.FromModifiedDate = Instant.FromDateTimeUtc(new DateTime(long.Parse(lastSync), DateTimeKind.Utc)).InZone(localTimeZone).ToDateTimeUnspecified();

Question About Querying Items

I am looking to query items. I am interested in ItemNonInventoryRet, ItemInventoryRet, ItemInventoryAssemblyRet & ItemServiceRet.

Is there a way to only include these item types and not the other types?

Here is my current query:

var request = new QbXmlRequest();
            var innerRequest = new ItemQueryRqType {
                MaxReturned = MaxReturned,
                iterator = IteratorType.Start,
                FromModifiedDate = SyncLog.FromDate,
                ToModifiedDate = SyncLog.ToDate,
                ActiveStatus = ActiveStatus.ActiveOnly
            };

Handle StatusCode error better

Currently, in the ExecuteResponse we check if the main required object is present. But when there is an error, it ends up to be an ugly if case for each status error code.

Example with InvoiceAdd

if (response.InvoiceRet != null) { ... }
else if (response.statusCode == "3120") {

}

Not only this is ugly, but when the response is coming back with an error, there are no state saved properly with what has been sent and what is being returned.
So I end up doing either a save to the database or something ugly like:

                // If the object cannot be added, it's because the parent has been deleted.
                ////"Object &quot;80000027-1429563689&quot; specified in the request cannot be found.  QuickBooks error message: Invalid argument.  The specified record does not exist in the list."
                var startsWith = "Object \"";
                var endsWith = "\" specified in the request cannot be found.  QuickBooks error message: Invalid argument.  The specified record does not exist in the list.";
                if (response.statusMessage.StartsWith(startsWith) && response.statusMessage.EndsWith(endsWith))
                {
                    // We will remove the entity sync, we can't sync this invoice. We will also remove the site.
                    var invoiceIdStr = messageService.RetrieveMessage(authenticatedTicket.Ticket, GetName(), Key);
                    int invoiceId = 0;
                    if (invoiceIdStr != null && int.TryParse(invoiceIdStr, out invoiceId))
                    {

InvoiceAdd.InvoiceLineAdd items being reordered

Some items are getting reordered by InvoiceAdd.InvoiceLineAdd. Unable to see why this is happening

image

image

80000007-1554843372 QB Desktop Test 8000001E-1555443730 A/R 2019-05-10 0008 Owner Invoice ID 0008: momo 1 {a66c95ac-fac9-43fa-822e-3219204a45c3} 800000F4-1555593041 01-0050 ยท Plans and Specs 0.03 3.00 80000002-1553892706 800000D2-1555593040 06-1000 ยท Rough Carpentry 0.03 3.69 80000002-1553892706

[Questions] Can we call a step from an api endpoint

Hey! Just wondering if there is a way to call a step without waiting for the web connector to make the request. I'd like to be able to call an endpoint /sync and start the sync process.

I'm assuming I'd have to build up the manager inside of the endpoint, but not sure.

Rate or RatePercent not being set on SalesOrderLineRet

I was trying to write some unit tests and needed to create a sample quickbooks sales order response. However, for some reason, the Rate and RatePercent aren't being set no matter what I do, strangely. After the initialization the rate is still null and the rate percent is also zero. I also tried setting it via a property afterwards (e.g. newVal.Rate = 100M) but even then the resulting object still has newVal.Rate=null.

E.g. the rate is set to 100
image

But then after setting it, the Rate parameter on the final object is null...?
image

Locals view of newVal
image

decimal precision in QUANTYPE

Hello, we are trying to send quantities such as 0.16667 as the quantity on a CreditMemoLineAdd, and the QBXML being generated is 0.17.

I believe this is due to the use of
return _value.ToString("F", CultureInfo.InvariantCulture);

in the FLOATTYPE superclass. Since (I believe) QBXML supports 5 decimal places, would "F5" be more appropriate here?

UnitOfMeasureUnit is missing from ColDesc in reports.

QuickBooksDesktop.Services.WebConnectorHandler: Error: An exception occurred during processing a step for ticket 304100f0-3bca-495c-b692-2db0228b6bad. Step: GeneralSummaryReportQuery

QbSync.WebConnector.Core.QbSyncException: Exception of type 'QbSync.WebConnector.Core.QbSyncException' was thrown.
 ---> System.InvalidOperationException: There is an error in XML document (23, 3).
 ---> System.InvalidOperationException: Instance validation error: 'UnitOfMeasureUnit' is not a valid value for ColType.
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read904_ColType(String s)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read906_ColDesc(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read916_ReportRet(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read1063_Item(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read1348_QBXMLMsgsRs(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read1399_QBXML(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderQBXML.Read1400_QBXML()
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---

"Interactive mode" support?

Can you support "Interactive mode" in the QbManager?
This means the QbManager class need implement many methods such as โ€œgetInteractiveURLโ€, โ€œinteractiveDoneโ€, โ€œinteractiveRejectedโ€,...

Error always logged on sync complete

We get a an error logged each sync with the message

[QBError] Out - {KEY} Sync Completed

trying to find out if there actually an error occuring that we need to fix, or if this should be an informational message instead of an error.

StepQueryWithIterator could pass ticket to interested methods

I think the StepQueryWithIterator class could pass the authenticatedTicket to the SaveMessage and RetrieveMessage methods in lieu of the current ticket and step. This would be useful because (in my implementation, for example) the ticket I use inherits from the AuthenticatedTicket class and is tracked in memory while storing a state (associated with the ticket) that can be used throughout the step process. So passing the instance of the ticket would save one the trouble of looking up the authenticated ticket by the ticket ID in certain implementations.

OwnerID doesn't support "0" value for custom fields

Multiple request, response, and ret types use property OwnerID, and are of type GUIDTYPE or GUIDTYPE[], but this does not support the allowed value of "0" to retrieve or set custom fields from QuickBooks. Limiting to GUIDs seems to limit compatibility with private data only.

Note: I did try new GUIDTYPE("0") but that gets formatted to {00000000-0000-0000-0000-000000000000}

I found the following quickbooks documentation in chapter 11 (page 137) for more info on the difference between custom fields and private data, and page 146 for an example query with the "0" value: https://developer-static.intuit.com/qbSDK-current/doc/PDF/QBSDK_ProGuide.pdf

I did some very limited testing, and changing OwnerID to string or string[] I was able to get custom fields successfully via the DataExtRet.

Upgrade from old version

Hello I am upgrading an old project from version 0.0.3 to latest and it appears several of the references/class names have changed. Do you have a migration/changelog that can help with the migration? Thanks

InvoiceAdd order wrong

In the latest version the order of InvoiceAdd has changed (that's the only entity I verified, there might be others):

v0.2:

<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
	<QBXMLMsgsRq onError="continueOnError">
		<InvoiceAddRq>
			<InvoiceAdd>
				<InvoiceLineAdd>
					<Desc>TEST</Desc>
				</InvoiceLineAdd>
				<CustomerRef>
					<ListID>80000003-1547478958</ListID>
					<FullName>Arnett-Wessin Energy Services</FullName>
				</CustomerRef>
				<TemplateRef>
					<ListID>80000002-1547157537</ListID>
					<FullName>Intuit Professional Invoice</FullName>
				</TemplateRef>
				<TxnDate>2019-03-20</TxnDate>
				<RefNumber>3</RefNumber>
				<BillAddress />
				<TermsRef>
					<ListID>80000001-1547157549</ListID>
					<FullName>1% 10 Net 30</FullName>
				</TermsRef>
				<ShipDate>2019-03-20</ShipDate>
				<CustomerSalesTaxCodeRef>
					<ListID>80000001-1547157549</ListID>
					<FullName>G</FullName>
				</CustomerSalesTaxCodeRef>
			</InvoiceAdd>
		</InvoiceAddRq>
	</QBXMLMsgsRq>
</QBXML>

v0.11:

<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
	<QBXMLMsgsRq onError="continueOnError">
		<InvoiceAddRq>
			<InvoiceAdd>
				<CustomerRef>
					<ListID>80000003-1547478958</ListID>
					<FullName>Arnett-Wessin Energy Services</FullName>
				</CustomerRef>
				<TemplateRef>
					<ListID>80000002-1547157537</ListID>
					<FullName>Intuit Professional Invoice</FullName>
				</TemplateRef>
				<TxnDate>2019-03-20</TxnDate>
				<RefNumber>3</RefNumber>
				<BillAddress />
				<TermsRef>
					<ListID>80000001-1547157549</ListID>
					<FullName>1% 10 Net 30</FullName>
				</TermsRef>
				<ShipDate>2019-03-20</ShipDate>
				<CustomerSalesTaxCodeRef>
					<ListID>80000001-1547157549</ListID>
					<FullName>G</FullName>
				</CustomerSalesTaxCodeRef>
				<InvoiceLineAdd>
					<Desc>TEST</Desc>
				</InvoiceLineAdd>
			</InvoiceAdd>
		</InvoiceAddRq>
	</QBXMLMsgsRq>
</QBXML>

The former will error out in the QB validator saying that the order is wrong.

Upgrade to QbSync 2.3.0 causes MissingMethodException

Setup

Visual Studio Version: 16.8.5
Project .NET Version: 5.0.3
QbSync.QbXml Version 2.3.0
QbSync.WebConnector.AspNetCore: 2.0.2

Issue
When using QbSync.QbXml version 2.2.0, all steps complete without issue. However, when upgrading to version 2.3.0 (using NuGet package manager), the following error is observed on the first step. Please note the followoing:

  • Authentication works flawlessly and we are getting to the first step.
  • This first step is a simple AccountQuery type.
  • The returned XML observed in the debugger looks fine, aka, Quickbooks is returning the proper response.
  • The only way I could seem to get insight into this issue was wrapping the base.ReceiveXMLAsync call in a try/catch.

image

Request Code

public class Request : StepQueryRequestBase<AccountQueryRqType>
        {
            public override string Name => StepName;
            ///This only exists to ensure we are sending good XML during testing of the 2.3.0 issue
            public override async Task<string> SendXMLAsync(IAuthenticatedTicket authenticatedTicket)
            {
                var results = await base.SendXMLAsync(authenticatedTicket);
                return results;
            }

            protected override Task<bool> ExecuteRequestAsync(IAuthenticatedTicket ticket, AccountQueryRqType request)
            {
                request.ActiveStatus = ActiveStatus.All;
                request.metaData = AccountQueryRqTypeMetaData.NoMetaData;
                request.IncludeRetElement = new []
                {
                    nameof(AccountRet.ListID),
                    nameof(AccountRet.EditSequence),
                    nameof(AccountRet.Name),
                    nameof(AccountRet.FullName),
                    nameof(AccountRet.AccountType),
                    nameof(AccountRet.SpecialAccountType),
                    nameof(AccountRet.AccountNumber)
                };
                return base.ExecuteRequestAsync(ticket, request);
            }
        }

The response class' ExecuteResponseAsync is never called in this scenario.

I don't mind keeping our solution at version 2.2.0, but if I am implementing something incorrectly, I want to ensure I am not version locked here going forward.

Thanks for an awesome project

Help for integrating with my local database

Hi. I want to integrate this application for quickbook sync but not able how to use this example. Like I,m creating one console app and trying to use these methods but its not working. If you can help me that would be great. Like any other example using this quickbook_sync. Thanks in advance.

QuickBooks Sync does not work with .NET 7

I built the sample application to connect with Quick books.
i am getting "Authentication failed" back from the webConnector when it tries to connect with the application.

here's the logs from the WebConnector

20221114.18:53:19 UTC : QBWebConnector.RegistryManager.setUpdateLock() : ********************* Update session locked *********************
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.instantiateWebService() : Initiated connection to the following application.
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.instantiateWebService() : AppName: My App
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.instantiateWebService() : AppUniqueName (if available): My App
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.instantiateWebService() : AppURL: http://localhost:64642/QBConnectorAsync.asmx
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_serverVersion() : *** Calling serverVersion().
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_serverVersion() : Received from serverVersion() following parameter:<serverVersionRet="3.0.2.0">
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_clientVersion() : *** Calling clientVersion() with following parameter:<productVersion="2.3.0.215">
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_clientVersion() : Received from clientVersion() following parameter:<clientVersionRet="">
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_clientVersion() : This application agrees with the current version of QBWebConnector. Allowing update operation.
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_authenticate() : Authenticating to application 'My App', username = 'username'
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_authenticate() : *** Calling authenticate() with following parameters:<userName="username"><password=
20221114.18:53:19 UTC : QBWebConnector.SOAPWebService.do_authenticate() : QBWC1012: Authentication failed due to following error message.
Object reference not set to an instance of an object.
More info:
StackTrace = at QBWebConnector.WebService.do_authenticate(String& ticket, String& companyFileName)
Source = QBWebConnector
20221114.18:53:20 UTC : QBWebConnector.RegistryManager.setUpdateLock() : HKEY_CURRENT_USER\Software\Intuit\QBWebConnector\UpdateLock has been set to False
20221114.18:53:20 UTC : QBWebConnector.RegistryManager.setUpdateLock() : ********************* Update session unlocked *********************
20221114.18:53:20 UTC : QBWebConnector.WebServiceManager.DoUpdate() : Update completed with errors. See log (QWClog.txt) for details.
20221114.18:53:20 UTC : UpdateThisScheduledApp() : QBWC1031: Update completed with some error. Application has been notified of the error accordingly. See QWCLog for further information.

Support customizable iterator records

Currently, the StepQueryWithIterator<T, Y>.ExecuteRequest method simply sets the QbIteratorRequest.MaxReturned value to 100. We could check for an existing valid value and set one if there isn't.

int maxVal;
bool isValid = int.TryParse(request.MaxReturned, out maxVal);

// Max 200 is arbitrary--maybe there shouldn't be a max.
if (!isValid || (isValid && (0 > maxVal || maxVal > 200)))
{
    // Not a valid max returned value. Set to 100 by default.
    request.MaxReturned = "100";
}

Allow QBWC to run without QB Open

Sync Manager is currently hardcoding setting which means it will only talk with the currently opened company. Would be nice if this parameter was exposed so we can use this library with QB closed.

                        ret[1] = string.Empty; // Use the company that is opened on the client.

SONumber

Hello, thanks for the awesome package. I'm trying to send the SONumber from our system, and I only see the SONumber field in the remarks of the object.cs file. It will not let me set the SOnumber in the QbSync.QbXml.Objects.InvoiceAdd method. The property is not available here, so is there another way to send this to QB. Do we need to upgrade to version 16?

Thank you!!

SoapCore 0.9.9 is not compatible with .NET core 3.1

DigDes/SoapCore#373

TypeLoadException: Could not load type 'Microsoft.AspNetCore.Http.Internal.BufferingHelper' from assembly 'Microsoft.AspNetCore.Http, Version=3.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

Need to update SoapCore.
But they way they do not support .NET Core 2.2.

Upgrading to 1.0.0 should work.

StepQueryResponseBase should not constrain to new()

I've hit a small wall in my implementation. I now have a step that can return 1 of 2 request types, based on runtime results. But given the generic arguments of StepQueryResponseBase, I can only provide one type to be serialized for the request and one type to be deserialized for the response, and those types must be a class and must have a parameterless constructor.

I believe we can resolve this by not constraining to new() on StepQueryResponseBase. This way, the generic type can simply be a contract. I would then be able to, for example, make a wrapper class that implements both QbRequest and QbResponse, and then run logic based on the type of the returned response. Or I could just return a QbRequest when creating the request, and the serializer will still serialize properly. The onus would then be on the imeplementer to cast the returned response appropriately.

Then we could also make StepQueryResponseBase.CreateRequest either abstract to allow the implementer to return whatever is required for that call, or keep it virtual and try to invoke a parameterless constructor via reflection to get an instance of T (then maybe wrap/throw if an exception is thrown).

protected virtual T CreateRequest(AuthenticatedTicket authenticatedTicket)
{
    System.Type tType = typeof(T);

    ConstructorInfo ctor = tType.GetConstructor(System.Type.EmptyTypes);
    if (ctor == null)
        throw new MissingMethodException(tType.Name, tType.Name);

    return (T)ctor.Invoke(null);
}

The only hangup I see with the reflection approach (opposed to the abstract approach) is that I don't believe we are using reflection anywhere else in the project, and reflection can require higher permissions if the assembly is loaded with restricted permissions in a locked down environment (like a GoDaddy shared host or something).

Parsing amounts incorrectly from string to decimal

When the WebConnector's host OS is setup with a different culture (having a different decimal symbol) than the server running the QBSync.QbXml library, the values parsed in QbSync.QbXml.Objects.FLOATYPE from string to decimal are being parsed wrong. This is the case when QBSync.Qxml is running on en-US, where the decimal symbol is point (".") and the WebConnector is running on fr-CA, where the decimal symbol is comma (","). eg, an amount sent in the original QbXml by the WebConnector as "20,00", will be parsed to "2000.00".

Looking at the code, we can see that the private method Parse in QbSync.QbXml.Objects.FLOATYPE implementation, used to parse the values from string to decimal is using the Decimal.TryParse overload that receives only the string as parameter. This overload will use the current thread culture (in the case of a web app).

       private static decimal Parse(string value)
        {
            if (decimal.TryParse(value, out decimal output))
            {
                return output;
            }

            return 0m;
        }

This issue could be solved using the Decimal.TryParse overload that receives also the number style and the culture as parameter. It means that the culture should be sent a) as parameter on every call to deserialize a FLOATYPE, or b) sent as parameter in the FLOATYPE constructor and use it in the Parse method.

This is a little example to illustrate the problem and the solution:
https://dotnetfiddle.net/V20rXq

This issue is probably also impacting the parsing from decimal to string, however, after some tests I've made with the WebConnector, looks like it is able to manage properly both decimal symbols regardless the host culture.

In my opinion, it is important that the culture will be set explicitly for each usage of the serialization logic, or at the app initialization (or both), to highlight the fact that the culture used to serialize/deserialze the QbXmls actually matters, and may impact the values of some properties.

XmlCodeExporter Missing

Hi,

I am using .NET Core 6 and I upgraded from .NET Standard 2.0.

I am in this file:

\quickbooks-sync\src\XsdGenerator\Program.cs

I built the solution. I get a build error here:

Line 51

        var codeExporter = new XmlCodeExporter(codeNamespace, compileUnit, CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateOrder);

It appears this code is unavailable in .NET Core. Is there a workaround?

QuickBooks found an error when parsing the provided XML text stream.

I'm trying to create a Sales Order but receive message: QuickBooks found an error when parsing the provided XML text stream.
This is my XML:

<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
  <QBXMLMsgsRq onError="stopOnError">
    <SalesOrderAddRq>
      <SalesOrderAdd defMacro="MACROTYPE">
        <SalesOrderLineAdd>
          <Rate>1.00</Rate>
          <ItemRef>
            <ListID>160000-933272656</ListID>
          </ItemRef>
          <Quantity>2.00</Quantity>
          <Amount>200.00</Amount>
        </SalesOrderLineAdd>
        <SalesOrderLineAdd>
          <Rate>1.00</Rate>
          <ItemRef>
            <ListID>1C0000-933272656</ListID>
          </ItemRef>
          <Quantity>2.00</Quantity>
          <Amount>200.00</Amount>
        </SalesOrderLineAdd>
        <CustomerRef>
          <ListID>800000DD-1702612106</ListID>
        </CustomerRef>
        <RefNumber>SO-001</RefNumber>
        <BillAddress>
          <Addr1>110 Main Street</Addr1>
          <Addr2>Suite 2000</Addr2>
          <State>TX</State>
          <PostalCode>99875</PostalCode>
          <Country>US</Country>
        </BillAddress>
        <DueDate>2019-10-10</DueDate>
      </SalesOrderAdd>
    </SalesOrderAddRq>
  </QBXMLMsgsRq>
</QBXML>

The reason is the order of fields is not match. For example, in your request the first tag should be CustomerRef, then RefNumber, after BillAddress etc. So here is a correct version of the request:

<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
  <QBXMLMsgsRq onError="stopOnError">
    <SalesOrderAddRq>
      <SalesOrderAdd defMacro="MACROTYPE">
       <CustomerRef>
          <ListID>800000DD-1702612106</ListID>
        </CustomerRef>
        <RefNumber>SO-001</RefNumber>
        <BillAddress>
          <Addr1>110 Main Street</Addr1>
          <Addr2>Suite 2000</Addr2>
          <State>TX</State>
          <PostalCode>99875</PostalCode>
          <Country>US</Country>
        </BillAddress>
        <DueDate>2019-10-10</DueDate>
        <SalesOrderLineAdd>
          <ItemRef>
            <ListID>160000-933272656</ListID>
          </ItemRef>
          <Quantity>2.00</Quantity>
          <Rate>1.00</Rate>
          <Amount>200.00</Amount>
        </SalesOrderLineAdd>
        <SalesOrderLineAdd>
          <ItemRef>
            <ListID>1C0000-933272656</ListID>
          </ItemRef>
          <Quantity>2.00</Quantity>
          <Rate>1.00</Rate>
          <Amount>200.00</Amount>
        </SalesOrderLineAdd>
      </SalesOrderAdd>
    </SalesOrderAddRq>
  </QBXMLMsgsRq>
</QBXML>

How to fix it in the code?

.NET naming conventions

Some simple changes that I have noticed so far that I would change to follow the .NET conventions. Feel free to disregard and close the issue if you disagree. ๐Ÿ˜„

Interfaces like QbRequest, QbIteratorRequest, QbResponse, and QbIteratorResponse should begin with a capital I. (Were these autogenerated?)

This is a matter of opinion: I feel that StepQueryResponseBase<T, Y>.GetName() should be an abstract property if its only purpose is to return a string with a name. I think making it a property will convey the idea that it should be simple and nothing complicated.

How can i Iterator for JournalEntryAddRqType.

As i have to push Multi pal days entry of Night audit to QB JournalEntry.

I have scenario like i have to pass ascending order by dates Journal entry to QB, if i get error to pass any date entry, i need to stop from it, and need to solve. then again start from the error date.

Payment receipt template returns `true` for template type

I was using a sample company today and noticed that all the templates of type Payment Receipt return a TemplateType of true.

Very strange behaviour, but breaks XML deserialization because it's expecting an enum.

<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
	<QBXMLMsgsRq onError="continueOnError">
		<TemplateQueryRq requestID="10a28a0a-526d-4dac-9282-5e87db89cbb7" />
	</QBXMLMsgsRq>
</QBXML>
<QBXML>
	<QBXMLMsgsRs>
		<TemplateQueryRs requestID="10a28a0a-526d-4dac-9282-5e87db89cbb7"
               			statusCode="0"
               			statusSeverity="Info"
               			statusMessage="Status OK">
			<TemplateRet>
				<ListID>A0000-1193777333</ListID>
				<TimeCreated>2007-10-30T14:48:53-07:00</TimeCreated>
				<TimeModified>2008-01-01T15:40:55-07:00</TimeModified>
				<EditSequence>1199227255</EditSequence>
				<Name>Work Order</Name>
				<IsActive>true</IsActive>
				<TemplateType>SalesOrder</TemplateType>
			</TemplateRet>
			<TemplateRet>
				<ListID>80000028-1924956754</ListID>
				<TimeCreated>2030-12-31T07:12:34-07:00</TimeCreated>
				<TimeModified>2030-12-31T07:12:34-07:00</TimeModified>
				<EditSequence>1924956754</EditSequence>
				<Name>Intuit Standard Payment Receipt</Name>
				<IsActive>true</IsActive>
				<TemplateType>true</TemplateType>
			</TemplateRet>
			<TemplateRet>
				<ListID>8000002A-1988119998</ListID>
				<TimeCreated>2032-12-31T08:33:18-07:00</TimeCreated>
				<TimeModified>2032-12-31T08:33:18-07:00</TimeModified>
				<EditSequence>1988119998</EditSequence>
				<Name>New Payment Receipt Template</Name>
				<IsActive>true</IsActive>
				<TemplateType>true</TemplateType>
			</TemplateRet>
		</TemplateQueryRs>
	</QBXMLMsgsRs>
</QBXML>

This seems a new type of template introduced in QB 2021 (https://www.intuitiveaccountant.com/accounting-tech/general-ledger/quickbooks-desktop-2021-customize-payment-receipts/).

I'm not sure if this something we should fix in the parser, or address with Intuit, but doing the latter will 99% result in a, works as intended, or we don't really care response.

Support QBXml 14.

The new version is out and the difference can be found here:

https://static.developer.intuit.com/resources/ReleaseNotes_QBXMLSDK_14_0.pdf

The main things we should fix is to add the QBXml 14 Schema. change the VERSION to 14. Update the Objects.

And potentially fix the issue they mention here

In a VehicleMileageQuery, if you specify time values (for example, 2002-06-05T10:21:10) in any of the date range filters, you get a statuscode error 3020 statusSeverity="Error" statusMessage="There was an error when converting the date value...โ€
The reason for this is that Vehicle Mileage supports only Date values, not DateTime values, despite what the OSR says. The
workaround is to use only date values, as follows: 2002-06-05.

Check ExecuteRequest before CreateRequest

I wonder if it would make more sense to check ExecuteRequest before CreateRequest is called. This way, we can confirm that a request should even bother being made before a request object is formed.

For example: create a CustomerAdd step, call ExecuteRequest first to check if any customers need to be added (via querying the database or something); if so, return true. If true, then call CreateRequest to create the request. If the result is not null (it really shouldn't be since ExecuteRequest is true), then call qbXmlRequest.GetRequest. This essentially just reverses the order of CreateRequest and ExecuteRequest, but you gain the ability to check before forming the request for the Web Connector.

Unexpected keys in EmployeePayrollInfo causing issues deserializing

Hello! We are having some issues parsing older versions of the QBD SDK with this package.
Some of our clients is sending the following 4 keys under the <EmployeeRet><EmployeePayrollInfo> key.
When these keys are present, the entire EmployeePayrollInfo object fails to deserialize, causing unexpected behavior on our platform. Is there a way to allow these extra keys to be present and ignored, or to just be added?
If there's an easy change that I can submit, please let me know.

qbxmlops50.xml
qbxmlops70.xml

  • IsCoveredByQualifiedPensionPlan - appears on qbxmlops50.xml, qbxmlops70.xml
<IsCoveredByQualifiedPensionPlan>BOOLTYPE</IsCoveredByQualifiedPensionPlan> <!-- PRIVATE, opt, v5.0 -->
  • PayScheduleRef - appears on qbxmlops70.xml
<PayScheduleRef>                                  <!-- PRIVATE, opt, v6.0 -->
  <ListID>IDTYPE</ListID>                         <!-- PRIVATE, opt -->
  <FullName>STRTYPE</FullName>                    <!-- PRIVATE, opt, max length = 31 for QBD|QBCA|QBUK|QBAU -->
</PayScheduleRef>
  • EmployeeTaxInfo - appears on qbxmlops50.xml, qbxmlops70.xml
<EmployeeTaxInfo>                                 <!-- PRIVATE, opt, v5.0 -->
  <StateLived>STRTYPE</StateLived>                <!-- PRIVATE, opt, max length = 21 for QBD|QBCA|QBUK|QBAU -->
  <StateWorked>STRTYPE</StateWorked>              <!-- PRIVATE, opt, max length = 21 for QBD|QBCA|QBUK|QBAU -->
  <IsStandardTaxationRequired>BOOLTYPE</IsStandardTaxationRequired> <!-- PRIVATE, opt -->
  <EmployeeTax>                                   <!-- PRIVATE, opt, may rep -->
    <IsSubjectToTax>BOOLTYPE</IsSubjectToTax>     <!-- PRIVATE, opt -->
    <PayrollItemTaxRef>                           <!-- PRIVATE -->
      <ListID>IDTYPE</ListID>                     <!-- opt -->
      <FullName>STRTYPE</FullName>                <!-- opt, max length = 31 for QBD|QBCA|QBUK|QBAU -->
    </PayrollItemTaxRef>
    <TaxLawVersion>INTTYPE</TaxLawVersion>        <!-- PRIVATE, opt -->
    <TaxInfo>                                     <!-- PRIVATE, opt, may rep -->
      <TaxInfoCategory>STRTYPE</TaxInfoCategory>  <!-- PRIVATE, max length = 100 for QBD|QBCA|QBUK|QBAU -->
      <TaxInfoValue>STRTYPE</TaxInfoValue>        <!-- PRIVATE -->
    </TaxInfo>
  </EmployeeTax>
</EmployeeTaxInfo>
  • EmployeeDirectDepositAccount - appears on qbxmlops50.xml, qbxmlops70.xml
<EmployeeDirectDepositAccount>                    <!-- PRIVATE, opt, may rep, v5.0 -->
  <!-- BEGIN OR: You may optionally have Amount OR RatePercent -->
  <Amount>AMTTYPE</Amount>                        <!-- PRIVATE -->
  <!-- OR -->
  <RatePercent>PERCENTTYPE</RatePercent>          <!-- PRIVATE -->
  <!-- END OR -->
  <BankName>STRTYPE</BankName>                    <!-- PRIVATE, max length = 31 for QBD|QBCA|QBUK|QBAU -->
  <RoutingNumber>STRTYPE</RoutingNumber>          <!-- PRIVATE, max length = 9 for QBD|QBCA|QBUK|QBAU -->
  <AccountNumber>STRTYPE</AccountNumber>          <!-- PRIVATE, max length = 25 for QBD|QBCA|QBUK|QBAU -->
  <!-- BankAccountType may have one of the following values: Checking, Savings -->
  <BankAccountType>ENUMTYPE</BankAccountType>     <!-- PRIVATE -->
</EmployeeDirectDepositAccount>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.