It seems a bit strange that you're aggregating a lot of stuff in your SQL query and then disaggregating it in your PHP. This is making it hard for you to match dates, I believe.
You could try this non-aggregated query:
SELECT op.products_model,
products.products_warranty_default,
orders.date_purchased,
orders.customers_name
FROM orders_products op
INNER JOIN products ON op.products_id = products.products_id
INNER JOIN orders ON op.orders_id = orders.orders_id
INNER JOIN customers ON orders.customers_id = customers.customers_id
WHERE orders.date_purchased >= DATE('2014-05-01')
AND orders.date_purchased < DATE('2014-05-31') + INTERVAL 1 DAY
ORDER BY orders.customer_name, op.orders_id,
op.products_model,
products.products_warranty_default,
orders.date_purchased,
This will give you back the detail records, the ones that match the date range.
It's quite easy, I think, to then process that result set with php. You'll need to detect the change in customer name to output the headers and footers. That's what $previous_name is about.
$previous_name = "";
while ($data = mysql_fetch_row($result)) {
$model = $data[0];
$year = $data[1];
$date = $data[2];
$name = $data[3];
if ($previous_name != $name) { /* on a new customer name ... */
if ($previous_name != "") {
/* if there is a previous customer, write the footer */
echo $footer; /* static HTML again like the header */
}
/* write the header for the new customer */
echo $header;
/* pick up the new customer name */
$previous_name = $name;
}
echo '
<tr>
<td width="149">
<center>'.$model.'</center>
</td>
<td width="149">
<center>'.date('d/m/Y', strtotime('+'.$year.' years',
strtotime($date))).'</center>
</td>
</tr>'; /* echos the model in first column and date+years in second column */
}
} /* end while ($data... */
/* wrap up the report by writing the final footer if need be */
if ( $previous_name != "" ) {
echo $footer; /* static HTML again like the header */
}
See how this goes? You request a detail resultset from SQL, and then use some programming techniques in PHP to divide that result set customer-by-customer.
That's easier and more reliable than getting a GROUPed result set and then trying to ungroup it.