You are missing the fact that you retrieve some values first, such as
inv id, inv qty, item qty
1 10 3
1 10 5
1 10 6
Then, as you go through your result set and update your table, you will set inv qty = (10 - 7), followed by qty= (10 - 5), followed by qty = (10 - 6), but not necessarily in that particular order.
What you need to do is
$result = $db->query('SELECT id FROM item WHERE sold = 0');
# check for query errors
$stmt = $db->prepare('UPDATE inv INNER JOIN item ON inv.id = item.inv_id SET inv.qty = CAST(inv.qty - item.qty AS SIGNED INTEGER), item.sold = 1 WHERE inv.id = :id');
# check for prepare statement errors
while ($row = $result->fetch())
{
$stmt->execute(array(':id' => $row['id']));
}
And if using mysql, this assumes that you have configured your database to run in strict mode and that inv.qty is UNSIGNED, otherwise values may become negative (if using SIGNED) or may be set to 0 (if not in strict mode mysql lets the query pass with a warning and modifies the value to its end point value).
If you want to use your original approach, you'd have to keep track of how quantitites change over subsequent updates, doing something along the lines of
$quantities = array();
while (...)
{
# this will only be done once per inventory id
if (!isset($quantities[$row['inv_id']]))
{
$quantities[$row['invid']] = $row->iqty;
}
# now replace $nqty with $quantities[$row['invid']] since this is per item and also
# isn't reset to 0 on each iteration, so just update it if there are enough items left
if (($new_qty = $quantities[$row['invid']] - $row->qty) >= 0)
{
$quantities[$row['invid']] -= $new->qty
# perform update
}
}
And in particular, please note that
if($nqty<=0){
$nqty=0;
}
most likely isn't what you want. Let's say you have 2 items in stock, and I order 3. 2 - 3 = -1, -1 <= 0, so you happily say that you have 0 items left, rather than -1 item. But you also update my order to sold, and then what? You ship me 2 items instead of 3? You should only update if the quantity left is >= 0, shouldn't you?
Then when you update your inventory, you can perform another check for items not yet marked as sold (and I'd call that shipped instead) and see if you can ship them this time.