{"version":3,"sources":["../../../src/common/instance/operations.ts","../../../src/accounting/attachments/requests.ts","../../../src/accounting/contacts/links.ts","../../../src/accounting/invoices/lineItems.ts","../../../src/accounting/journals/links.ts","../../../src/projects/timeEntries.ts","../../../src/projects/__tests__/timeEntries.test.ts"],"sourcesContent":["import { isObject } from 'deep-cuts';\r\n\r\nexport const deepClone = <T extends object>(instance: T): T => {\r\n  if (instance && isObject(instance)) {\r\n    const { constructor } = instance;\r\n    // @ts-expect-error - This is not passing for TypeScript, bit will for any Xero class.\r\n    const clone = new constructor();\r\n    /* eslint-disable guard-for-in, functional/immutable-data */\r\n    for (const key in instance) {\r\n      clone[key] = deepClone(instance[key] as object);\r\n    }\r\n    /* eslint-enable guard-for-in, functional/immutable-data */\r\n\r\n    return clone;\r\n  }\r\n\r\n  return instance;\r\n};\r\n","import type { ReadStream } from 'node:fs';\r\nimport type http from 'node:http';\r\n\r\nimport { bufferToStream } from 'tranquil-stream';\r\nimport type { Attachments, XeroClient } from 'xero-node';\r\n\r\ntype ICreateInvoiceAttachmentParameters = {\r\n  contents: Buffer;\r\n  filename: string;\r\n  invoiceId: string;\r\n};\r\n\r\nexport const createInvoiceAttachment = async (\r\n  client: XeroClient,\r\n  tenantId: string,\r\n  { invoiceId, filename, contents }: ICreateInvoiceAttachmentParameters,\r\n): Promise<{\r\n  body: Attachments;\r\n  response: http.IncomingMessage;\r\n}> => {\r\n  return client.accountingApi.createInvoiceAttachmentByFileName(\r\n    tenantId,\r\n    invoiceId,\r\n    filename,\r\n    bufferToStream(contents) as unknown as ReadStream,\r\n  );\r\n};\r\n","import qs from 'qs';\r\nimport type { Contact } from 'xero-node';\r\n\r\nimport { hasProperty } from '../../utils/properties';\r\n\r\nexport const getContactLink = (contact: Contact | string): string => {\r\n  return `https://go.xero.com/Contacts/View.aspx?${qs.stringify({\r\n    contactID:\r\n      (hasProperty(contact, 'contactID')\r\n        ? (contact as Contact).contactID\r\n        : contact) || 'null-or-empty-contact-id',\r\n  })}`;\r\n};\r\n","import { isNil } from 'deep-cuts';\r\nimport type { LineItem } from 'xero-node';\r\n\r\nimport type { DecisionFunction } from '../../types';\r\n\r\nexport const filterInvoiceLineItems = (\r\n  lineItems: LineItem[],\r\n  minCode: DecisionFunction<LineItem> | string | number,\r\n  maxCode?: string | number,\r\n): LineItem[] => {\r\n  if (typeof minCode === 'function') {\r\n    return (lineItems || []).filter(minCode);\r\n  }\r\n\r\n  const parsedMinCode = isNil(minCode)\r\n    ? minCode\r\n    : Number.parseInt(minCode as string, 10);\r\n  const parsedMaxCode = isNil(maxCode)\r\n    ? maxCode\r\n    : Number.parseInt(maxCode as string, 10);\r\n  if (parsedMinCode || parsedMaxCode) {\r\n    return (lineItems || []).filter(({ itemCode }) => {\r\n      const parsedItemCode = isNil(itemCode)\r\n        ? itemCode\r\n        : Number.parseInt(itemCode as string, 10);\r\n      if (parsedItemCode) {\r\n        const greaterThanOrEqualToMinCode = isNil(parsedMinCode)\r\n          ? true\r\n          : parsedItemCode >= parsedMinCode;\r\n        const lessThanOrEqualToMaxCode = isNil(parsedMaxCode)\r\n          ? true\r\n          : parsedItemCode <= (parsedMaxCode || 0);\r\n        return greaterThanOrEqualToMinCode && lessThanOrEqualToMaxCode;\r\n      }\r\n\r\n      return false;\r\n    });\r\n  }\r\n\r\n  return lineItems || [];\r\n};\r\n","import qs from 'qs';\r\nimport type { ManualJournal } from 'xero-node';\r\n\r\nimport { hasProperty } from '../../utils/properties';\r\n\r\nexport const getManualJournalLink = (\r\n  manualJournal: ManualJournal | string,\r\n): string => {\r\n  return `https://go.xero.com/Journal/View.aspx?${qs.stringify({\r\n    invoiceID:\r\n      (hasProperty(manualJournal, 'manualJournalID')\r\n        ? (manualJournal as ManualJournal).manualJournalID\r\n        : manualJournal) || 'null-or-empty-manual-journal-id',\r\n  })}`;\r\n};\r\n","import { roundToNearestFraction } from 'deep-cuts';\r\n\r\nimport type { TimeEntry } from './shimTypes';\r\n\r\nexport const hoursFromTimeEntries = (\r\n  timeEntries: TimeEntry[],\r\n  denominator: number = 4,\r\n  maxDecimalPlaces: number = 2,\r\n): number | undefined => {\r\n  const totalMinutes = timeEntries.reduce((totalMinutes, timeEntry) => {\r\n    const duration = timeEntry.duration || 0;\r\n    return totalMinutes + duration;\r\n  }, 0);\r\n  return roundToNearestFraction(\r\n    totalMinutes / 60,\r\n    denominator,\r\n    maxDecimalPlaces,\r\n  );\r\n};\r\n","import { hoursFromTimeEntries } from '../../';\r\nimport type { TimeEntry } from '../shimTypes';\r\n\r\ndescribe('projects/timeEntries', () => {\r\n  describe('hoursFromTimeEntries()', () => {\r\n    it('should throw an error if passed undefined', () => {\r\n      expect(() => {\r\n        // @ts-expect-error - This is an invalid type for the function.\r\n        return hoursFromTimeEntries();\r\n      }).toThrowError();\r\n    });\r\n\r\n    it('should throw an error if passed null', () => {\r\n      expect(() => {\r\n        // @ts-expect-error - This is an invalid type for the function.\r\n        return hoursFromTimeEntries(null);\r\n      }).toThrowError();\r\n    });\r\n\r\n    it('should return 0 if passed an empty array', () => {\r\n      expect(hoursFromTimeEntries([])).toBe(0);\r\n    });\r\n\r\n    it('should return the total hours from the given TimeEntries, defaulting to the nearest 15 minutes', () => {\r\n      const timeEntries = [\r\n        {\r\n          duration: 60,\r\n        },\r\n        {\r\n          duration: 30,\r\n        },\r\n        {\r\n          duration: 18,\r\n        },\r\n      ];\r\n      expect(hoursFromTimeEntries(timeEntries)).toBe(1.75);\r\n    });\r\n\r\n    it('should play nice with a non-default denominator for rounding', () => {\r\n      /* eslint-disable functional/immutable-data */\r\n      const timeEntryA = {} as unknown as TimeEntry;\r\n      timeEntryA.duration = 60;\r\n      const timeEntryB = {} as unknown as TimeEntry;\r\n      timeEntryB.duration = 23;\r\n      /* eslint-enable functional/immutable-data */\r\n      const timeEntries = [timeEntryA, timeEntryB];\r\n      expect(hoursFromTimeEntries(timeEntries, 10)).toBe(1.4);\r\n    });\r\n\r\n    it('should play nice with non-standard decimal places for rounding / fixed numbers', () => {\r\n      const timeEntries = [\r\n        {\r\n          duration: 60,\r\n        },\r\n        {\r\n          duration: 30,\r\n        },\r\n        {\r\n          duration: 18,\r\n        },\r\n      ];\r\n      expect(hoursFromTimeEntries(timeEntries, 2, 0)).toBe(2);\r\n    });\r\n\r\n    it('should sub in 0 when a Time Entry is missing a duration', () => {\r\n      /* eslint-disable functional/immutable-data */\r\n      const timeEntryA = {} as unknown as TimeEntry;\r\n      const timeEntryB = {} as unknown as TimeEntry;\r\n      timeEntryB.duration = 23;\r\n      /* eslint-enable functional/immutable-data */\r\n      const timeEntries = [timeEntryA, timeEntryB];\r\n      expect(hoursFromTimeEntries(timeEntries, 10)).toBe(0.4);\r\n    });\r\n  });\r\n});\r\n"],"mappings":";AAAA,SAAS,gBAAgB;;;ACGzB,SAAS,sBAAsB;;;ACH/B,OAAO,QAAQ;;;ACAf,SAAS,aAAa;;;ACAtB,OAAOA,SAAQ;;;ACAf,SAAS,8BAA8B;AAIhC,IAAM,uBAAuB,CAClC,aACA,cAAsB,GACtB,mBAA2B,MACJ;AACvB,QAAM,eAAe,YAAY,OAAO,CAACC,eAAc,cAAc;AACnE,UAAM,WAAW,UAAU,YAAY;AACvC,WAAOA,gBAAe;AAAA,EACxB,GAAG,CAAC;AACJ,SAAO;AAAA,IACL,eAAe;AAAA,IACf;AAAA,IACA;AAAA,EACF;AACF;;;ACfA,SAAS,wBAAwB,MAAM;AACrC,WAAS,0BAA0B,MAAM;AACvC,OAAG,6CAA6C,MAAM;AACpD,aAAO,MAAM;AAEX,eAAO,qBAAqB;AAAA,MAC9B,CAAC,EAAE,aAAa;AAAA,IAClB,CAAC;AAED,OAAG,wCAAwC,MAAM;AAC/C,aAAO,MAAM;AAEX,eAAO,qBAAqB,IAAI;AAAA,MAClC,CAAC,EAAE,aAAa;AAAA,IAClB,CAAC;AAED,OAAG,4CAA4C,MAAM;AACnD,aAAO,qBAAqB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;AAAA,IACzC,CAAC;AAED,OAAG,kGAAkG,MAAM;AACzG,YAAM,cAAc;AAAA,QAClB;AAAA,UACE,UAAU;AAAA,QACZ;AAAA,QACA;AAAA,UACE,UAAU;AAAA,QACZ;AAAA,QACA;AAAA,UACE,UAAU;AAAA,QACZ;AAAA,MACF;AACA,aAAO,qBAAqB,WAAW,CAAC,EAAE,KAAK,IAAI;AAAA,IACrD,CAAC;AAED,OAAG,gEAAgE,MAAM;AAEvE,YAAM,aAAa,CAAC;AACpB,iBAAW,WAAW;AACtB,YAAM,aAAa,CAAC;AACpB,iBAAW,WAAW;AAEtB,YAAM,cAAc,CAAC,YAAY,UAAU;AAC3C,aAAO,qBAAqB,aAAa,EAAE,CAAC,EAAE,KAAK,GAAG;AAAA,IACxD,CAAC;AAED,OAAG,kFAAkF,MAAM;AACzF,YAAM,cAAc;AAAA,QAClB;AAAA,UACE,UAAU;AAAA,QACZ;AAAA,QACA;AAAA,UACE,UAAU;AAAA,QACZ;AAAA,QACA;AAAA,UACE,UAAU;AAAA,QACZ;AAAA,MACF;AACA,aAAO,qBAAqB,aAAa,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC;AAAA,IACxD,CAAC;AAED,OAAG,2DAA2D,MAAM;AAElE,YAAM,aAAa,CAAC;AACpB,YAAM,aAAa,CAAC;AACpB,iBAAW,WAAW;AAEtB,YAAM,cAAc,CAAC,YAAY,UAAU;AAC3C,aAAO,qBAAqB,aAAa,EAAE,CAAC,EAAE,KAAK,GAAG;AAAA,IACxD,CAAC;AAAA,EACH,CAAC;AACH,CAAC;","names":["qs","totalMinutes"]}