calcinai / xero-php

A php library for the Xero API, with a cleaner OAuth interface and ORM-like abstraction.
MIT License
359 stars 262 forks source link

PurchaseOrder $lineItem->setLineItemID() does not work #867

Closed ok200paul closed 2 years ago

ok200paul commented 2 years ago

We need to generate our own LineItemID so that we can reference them down the line, but $lineItem->setLineItemID() doesn't seem to work. Example:

// Assume that we're all good connecting to the API and $xeroApp is set
$purchaseOrder = new PurchaseOrder($xeroApp);
$lineItem = new LineItem($xeroApp);

//Add the line item to the PO
$lineItem->setDescription('PO Line item description')
    ->setQuantity(1)
    ->setUnitAmount(1)
    ->setLineItemID($guid); // $guid is a valid GUID generated by our system
$lineItem->save();     // Just in case?

$purchaseOrder->addLineItem($lineItem);
$purchaseOrder->save();

Then to test, we re-retrieve the PurchaseOrder

$purchaseOrderFromXero = $xeroApp->loadByGUID(PurchaseOrder::class, $purchaseOrder->PurchaseOrderID);

dd($purchaseOrderFromXero->getLineItems()->first()->LineItemID, $guid); // $guid is never the same as LineItemID

Is it something I'm doing, or is setLineItemID problematic as the Xero API api.xro/2.0 doesn't seem to allow individual PO line item management?

Thanks for this package, it's solid! Let me know if I can provide any more details here.

rodjsta commented 2 years ago

I don't think you can set the line item IDs. You'll have to get the line item IDs from the response and handle on your end. That's at least how I manage it in my apps.

ok200paul commented 2 years ago

Thanks @rodjsta yeah the more I test the more it's looking like this. I'll keep banging my head off it. While I have you, how do you locate the local DB items when iterating back through the items in the response?

rodjsta commented 2 years ago

@ok200paul I asked a similar question to your original question a couple of years ago here.

My use case is for Invoices, but it would be the same for purchase orders.

The way I handle allocating the remote LineItemIDs to my local line items is not 100% fool proof, but I'm not sure how it could be done otherwise.

Basically, when I have the response, I iterate through all response elements and then use the invoice ContactID + total to match it with the local invoice I had in the exportable batch. The first problem here is if I have more than one invoice for the same ContactID with the exact same total (but, this is extremely unlikely in our app for the same exportable batch).

Once I know which local invoice the response element is for, I then do a similar matching for the line items (scoped the that exact local invoice. I look for a local line item on that invoice that matches the same description, unit_qty and unit_amount. Again, we wouldn't have two line items exactly the same on a single invoice.

We then use webhooks to keep everything in sync, but Xero is our source of truth.

I hope this helps.

My app is a Laravel app. Shout out if you have any other questions.

I've included a screenshot of my export job.

image

ok200paul commented 2 years ago

Nice. Thanks for taking the time on a Saturday the comprehensive response. I see how you're updating external_id_xero locally for the LineItemIDs coming back from Xero, I've implemented this on our side to track the LineItems.

Our use case is trying to "send" local line_items data to a draft Purchase Order on Xero, then when the sale details for a line item (a sale item) is updated locally, try to update the PO on Xero.

However, it all seems a little futile as it looks like Xero's API doesn't allow individual LineItem PUT, even if we know the LineItemId (or: I haven't discovered how to do this yet). All attempts to use loadByGUID(LineItem::class, $knownLineItemId) return Resource Not Found

Aha! Turns out we can push new data to line items within a foreach loop, but we can't individually PUT to the LineItem API (man, Xero's API sucks..):

$lineItems = $purchaseOrderFromXero->getLineItems();

foreach ($lineItems as $xeroPOLineItem) {

    $xeroPOLineItem->setQuantity($updatedQuantity)
        ->setUnitAmount($updateUnitAmount);

    $xeroApp->save($xeroPOLineItem, true); // Save the LineItem, true means update data. LineItemID is an attribute of the object so it updates the correct Xero resource.

}

Hope this helps someone - thanks for your response here @rodjsta, it helped sort me out. Have a great weekend!

rodjsta commented 2 years ago

Nice work Paul. Yep, I agree the API is not always the best. I'd love to see the ability to add metadata to objects.

ok200paul commented 2 years ago

I was looking at my screen earlier saying this aloud!! Metadata would solve so much here.