This is more I think to do with Python than erpnext.
def execute(filters=None):
if not filters: filters = {}
float_precision = cint(frappe.db.get_default("float_precision")) or 3
expiry = frappe.db.sql("""select distinct expiry_date from `tabBatch` order by expiry_date""", as_dict=1)
iwb_map = get_item_warehouse_batch_map(filters, float_precision)
columns = [_("Item") + ":Link/Item:100"]
for e in expiry:
columns += [str(e["expiry_date"])]
data = []
for i, item in enumerate(iwb_map):
exp = []
for e in expiry:
inp =0
for b in (iwb_map[item]):
if e["expiry_date"] == b:
qty_dict = iwb_map[item][e.expiry_date]
exp.append(flt(qty_dict.bal_qty, float_precision))
inp = 1
if inp == 0:exp.append(0)
data.append([item, exp])
return columns, data
The result is as follows. As you can see, instead of falling into the right column, It collects in one column.