From 4730ec129179bddadb697f7221ea2861730aa81f Mon Sep 17 00:00:00 2001 From: Mia Chillgood Date: Mon, 1 Jun 2026 00:29:08 +1200 Subject: [PATCH] Format line item tracking values --- .../__tests__/format-line-item.test.ts | 40 +++++++++++++++++++ src/helpers/format-line-item.ts | 32 ++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/helpers/__tests__/format-line-item.test.ts diff --git a/src/helpers/__tests__/format-line-item.test.ts b/src/helpers/__tests__/format-line-item.test.ts new file mode 100644 index 00000000..03873926 --- /dev/null +++ b/src/helpers/__tests__/format-line-item.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { LineItem } from "xero-node"; +import { formatLineItem } from "../format-line-item.js"; + +describe("formatLineItem", () => { + it("formats tracking categories without object stringification", () => { + const lineItem = { + itemCode: "CONSULT", + description: "Consulting services", + tracking: [ + { + name: "Project", + option: "Website Redesign", + trackingCategoryID: "category-1", + trackingOptionID: "option-1", + }, + { + name: "Cost Centre", + option: "Marketing", + trackingCategoryID: "category-2", + trackingOptionID: "option-2", + }, + ], + lineAmount: 120, + } as LineItem; + + const result = formatLineItem(lineItem); + + expect(result).toContain( + "Tracking: Category: Project, Category ID: category-1, Option: Website Redesign, Option ID: option-1; Category: Cost Centre, Category ID: category-2, Option: Marketing, Option ID: option-2", + ); + expect(result).not.toContain("[object Object]"); + }); + + it("omits tracking when there are no tracking categories", () => { + expect( + formatLineItem({ description: "No tracking" } as LineItem), + ).not.toContain("Tracking:"); + }); +}); diff --git a/src/helpers/format-line-item.ts b/src/helpers/format-line-item.ts index 66394203..d4e46917 100644 --- a/src/helpers/format-line-item.ts +++ b/src/helpers/format-line-item.ts @@ -1,6 +1,32 @@ import { LineItem } from "xero-node"; +const formatTracking = (tracking: LineItem["tracking"]): string | undefined => { + if (!tracking?.length) { + return undefined; + } + + return tracking + .map((trackingItem) => + [ + trackingItem.name ? `Category: ${trackingItem.name}` : undefined, + trackingItem.trackingCategoryID + ? `Category ID: ${trackingItem.trackingCategoryID}` + : undefined, + trackingItem.option ? `Option: ${trackingItem.option}` : undefined, + trackingItem.trackingOptionID + ? `Option ID: ${trackingItem.trackingOptionID}` + : undefined, + ] + .filter(Boolean) + .join(", "), + ) + .filter(Boolean) + .join("; "); +}; + export const formatLineItem = (lineItem: LineItem): string => { + const tracking = formatTracking(lineItem.tracking); + return [ `Item ID: ${lineItem.item}`, `Item Code: ${lineItem.itemCode}`, @@ -9,7 +35,9 @@ export const formatLineItem = (lineItem: LineItem): string => { `Unit Amount: ${lineItem.unitAmount}`, `Account Code: ${lineItem.accountCode}`, `Tax Type: ${lineItem.taxType}`, - `Tracking: ${lineItem.tracking}`, + tracking ? `Tracking: ${tracking}` : undefined, `Line Amount: ${lineItem.lineAmount}`, - ].join("\n"); + ] + .filter(Boolean) + .join("\n"); };