@markwharton/liquidplanner 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,11 +43,11 @@ await client.createTimesheetEntry({
43
43
  });
44
44
 
45
45
  // Query existing entries for a date
46
- const tsResult = await client.getTimesheetEntries('2026-01-29', assignmentId);
47
- if (tsResult.ok) console.log(tsResult.data); // LPTimesheetEntry[]
46
+ const tsResult = await client.getTimesheetEntries('2026-01-29');
47
+ if (tsResult.ok) console.log(tsResult.data); // LPTimesheetEntries (grouped by member)
48
48
 
49
49
  // Update an existing entry (accumulate hours)
50
- const existing = tsResult.data![0];
50
+ const existing = tsResult.data!.members[0].entries[0];
51
51
  await client.updateTimesheetEntry(existing.id, existing, {
52
52
  hours: existing.hours + 1.5,
53
53
  note: 'Additional work'
@@ -114,7 +114,7 @@ All methods return `Result<T>` — see [api-core Result Pattern](../../README.md
114
114
  | `findItems(options)` | `LPFindItemsOptions` | `Result<LPItem[]>` |
115
115
  | `getChildren(parentId, options?)` | `number, { itemType? }?` | `Result<LPItem[]>` |
116
116
  | `getWorkspaceTree()` | — | `Result<LPWorkspaceTree>` |
117
- | `getAssignments(memberId)` | `number` | `Result<{ assignments: LPAssignment[], treeItemCount: number }>` |
117
+ | `getAssignments(memberId?)` | `number?` | `Result<LPAssignments>` |
118
118
  | `clearCache()` | — | `void` |
119
119
  | `invalidateTimesheetCache()` | — | `void` |
120
120
  | `invalidateTreeCache()` | — | `void` |
@@ -123,9 +123,10 @@ All methods return `Result<T>` — see [api-core Result Pattern](../../README.md
123
123
  | `invalidateCostCodeCache()` | — | `void` |
124
124
  | `getCostCodes()` | — | `Result<LPCostCode[]>` |
125
125
  | `createTimesheetEntry(entry)` | `LPTimesheetEntry` | `LPSyncResult` |
126
- | `getTimesheetEntries(date, itemId?)` | `string \| string[], number?` | `Result<LPTimesheetEntry[]>` |
126
+ | `getTimesheetEntries(date, options?)` | `string \| string[], LPTimesheetOptions?` | `Result<LPTimesheetEntries>` |
127
127
  | `updateTimesheetEntry(entryId, existing, updates)` | `number, LPTimesheetEntry, Partial<LPTimesheetEntry>` | `LPSyncResult` |
128
128
  | `upsertTimesheetEntry(entry, options?)` | `LPTimesheetEntry, LPUpsertOptions?` | `LPSyncResult` |
129
+ | `getWeeklySummary(dates, options?)` | `string[], LPWeeklySummaryOptions?` | `Result<LPWeeklySummary>` |
129
130
 
130
131
  ### Workflow: `resolveTaskToAssignment`
131
132
 
package/dist/client.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * @see https://api-docs.liquidplanner.com/
8
8
  */
9
9
  import type { Result } from '@markwharton/api-core';
10
- import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetOptions, LPUpsertOptions, LPAncestor, LPFindItemsOptions, LPWorkspaceTree, LPAssignmentsResult } from './types.js';
10
+ import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetOptions, LPUpsertOptions, LPTimesheetEntries, LPAncestor, LPFindItemsOptions, LPWorkspaceTree, LPAssignments, LPWeeklySummaryOptions, LPWeeklySummary } from './types.js';
11
11
  /**
12
12
  * LiquidPlanner API Client
13
13
  *
@@ -178,7 +178,7 @@ export declare class LPClient {
178
178
  *
179
179
  * @param memberId - Filter to a specific member's assignments. When omitted, returns all members' assignments.
180
180
  */
181
- getAssignments(memberId?: number): Promise<Result<LPAssignmentsResult>>;
181
+ getAssignments(memberId?: number): Promise<Result<LPAssignments>>;
182
182
  /**
183
183
  * Get all cost codes in the workspace
184
184
  */
@@ -204,7 +204,7 @@ export declare class LPClient {
204
204
  * @param date - Date(s) in YYYY-MM-DD format (string or array)
205
205
  * @param options - Optional filters (itemId, memberId)
206
206
  */
207
- getTimesheetEntries(date: string | string[], options?: LPTimesheetOptions): Promise<Result<LPTimesheetEntry[]>>;
207
+ getTimesheetEntries(date: string | string[], options?: LPTimesheetOptions): Promise<Result<LPTimesheetEntries>>;
208
208
  /**
209
209
  * Update an existing timesheet entry
210
210
  *
@@ -260,4 +260,17 @@ export declare class LPClient {
260
260
  * @param options - Upsert options (accumulate defaults to true)
261
261
  */
262
262
  upsertTimesheetEntry(entry: LPTimesheetEntry, options?: LPUpsertOptions): Promise<LPSyncResult>;
263
+ /**
264
+ * Get a weekly summary with per-member time breakdowns.
265
+ *
266
+ * Aggregates timesheet entries by member and date. Optionally includes
267
+ * assignments and late items when specified via the `include` option.
268
+ *
269
+ * Fetches are parallelized — timesheet entries are always fetched, while
270
+ * assignments and late items are conditionally fetched based on `include`.
271
+ *
272
+ * @param dates - Array of date strings (YYYY-MM-DD) for the week
273
+ * @param options - Optional memberId filter and detail sections to include
274
+ */
275
+ getWeeklySummary(dates: string[], options?: LPWeeklySummaryOptions): Promise<Result<LPWeeklySummary>>;
263
276
  }
package/dist/client.js CHANGED
@@ -640,7 +640,7 @@ export class LPClient {
640
640
  if (memberId) {
641
641
  baseUrl += `&${filterIs('userId', memberId)}`;
642
642
  }
643
- return paginatedFetch({
643
+ const flatResult = await paginatedFetch({
644
644
  fetchFn: (url) => this.fetch(url),
645
645
  baseUrl,
646
646
  transform: (data) => data.map(entry => ({
@@ -653,6 +653,23 @@ export class LPClient {
653
653
  userId: entry.userId,
654
654
  })),
655
655
  });
656
+ if (!flatResult.ok)
657
+ return err(flatResult.error, flatResult.status);
658
+ // Group by member (same pattern as getAssignments)
659
+ const memberMap = new Map();
660
+ for (const entry of flatResult.data) {
661
+ const uid = entry.userId ?? 0;
662
+ let entries = memberMap.get(uid);
663
+ if (!entries) {
664
+ entries = [];
665
+ memberMap.set(uid, entries);
666
+ }
667
+ entries.push(entry);
668
+ }
669
+ const members = [...memberMap.entries()]
670
+ .sort(([a], [b]) => a - b)
671
+ .map(([memberId, entries]) => ({ memberId, entries }));
672
+ return ok({ members });
656
673
  });
657
674
  }
658
675
  /**
@@ -734,10 +751,11 @@ export class LPClient {
734
751
  if (!fetchResult.ok) {
735
752
  return { ok: false, error: fetchResult.error, status: fetchResult.status };
736
753
  }
737
- // Find matching entry
754
+ // Find matching entry from grouped result
738
755
  // If no costCodeId specified, match any entry (LP uses assignment's default)
739
756
  // If costCodeId specified, match exactly
740
- const existingEntry = fetchResult.data?.find((e) => {
757
+ const allEntries = fetchResult.data?.members.flatMap(m => m.entries) ?? [];
758
+ const existingEntry = allEntries.find((e) => {
741
759
  if (entry.costCodeId === undefined || entry.costCodeId === null) {
742
760
  return true;
743
761
  }
@@ -756,4 +774,53 @@ export class LPClient {
756
774
  // No existing entry, create new
757
775
  return this.createTimesheetEntry(entry);
758
776
  }
777
+ // ============================================================================
778
+ // Weekly Summary
779
+ // ============================================================================
780
+ /**
781
+ * Get a weekly summary with per-member time breakdowns.
782
+ *
783
+ * Aggregates timesheet entries by member and date. Optionally includes
784
+ * assignments and late items when specified via the `include` option.
785
+ *
786
+ * Fetches are parallelized — timesheet entries are always fetched, while
787
+ * assignments and late items are conditionally fetched based on `include`.
788
+ *
789
+ * @param dates - Array of date strings (YYYY-MM-DD) for the week
790
+ * @param options - Optional memberId filter and detail sections to include
791
+ */
792
+ async getWeeklySummary(dates, options) {
793
+ const { memberId, include = [] } = options || {};
794
+ const includeSet = new Set(include);
795
+ // Parallel fetch: timesheet (always) + optional sections
796
+ const [timesheetResult, assignmentsResult, lateItemsResult] = await Promise.all([
797
+ this.getTimesheetEntries(dates, memberId ? { memberId } : undefined),
798
+ includeSet.has('assignments') ? this.getAssignments(memberId) : Promise.resolve(null),
799
+ includeSet.has('lateItems')
800
+ ? this.findItems({ itemType: 'tasks', taskStatusGroupNot: 'done', late: true })
801
+ : Promise.resolve(null),
802
+ ]);
803
+ if (!timesheetResult.ok)
804
+ return err(timesheetResult.error, timesheetResult.status);
805
+ if (assignmentsResult && !assignmentsResult.ok)
806
+ return err(assignmentsResult.error, assignmentsResult.status);
807
+ if (lateItemsResult && !lateItemsResult.ok)
808
+ return err(lateItemsResult.error, lateItemsResult.status);
809
+ // Build per-member weekly summaries from grouped timesheet entries
810
+ const members = timesheetResult.data.members.map(({ memberId, entries }) => {
811
+ const timeSummary = {};
812
+ let totalHours = 0;
813
+ for (const entry of entries) {
814
+ timeSummary[entry.date] = (timeSummary[entry.date] || 0) + entry.hours;
815
+ totalHours += entry.hours;
816
+ }
817
+ return { memberId, timeSummary, totalHours, entries };
818
+ });
819
+ const result = { members };
820
+ if (assignmentsResult)
821
+ result.assignments = assignmentsResult.data;
822
+ if (lateItemsResult)
823
+ result.lateItems = lateItemsResult.data;
824
+ return ok(result);
825
+ }
759
826
  }
package/dist/index.d.ts CHANGED
@@ -30,7 +30,7 @@
30
30
  */
31
31
  export { LPClient } from './client.js';
32
32
  export { resolveTaskToAssignment } from './workflows.js';
33
- export type { LPConfig, LPCacheConfig, LPRetryConfig, LPItemType, LPUserType, LPHierarchyItem, LPItem, LPAncestor, LPWorkspace, LPMember, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTaskResolution, LPTimesheetOptions, LPUpsertOptions, LPAssignment, LPItemRef, LPNormalizedAssignment, LPMemberAssignments, LPAssignmentsResult, LPFindItemsOptions, LPTreeNode, LPWorkspaceTree, } from './types.js';
33
+ export type { LPConfig, LPCacheConfig, LPRetryConfig, LPItemType, LPUserType, LPHierarchyItem, LPItem, LPAncestor, LPWorkspace, LPMember, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTaskResolution, LPTimesheetOptions, LPUpsertOptions, LPMemberTimesheetEntries, LPTimesheetEntries, LPAssignment, LPItemRef, LPNormalizedAssignment, LPMemberAssignments, LPAssignments, LPWeeklySummaryOptions, LPMemberWeeklySummary, LPWeeklySummary, LPFindItemsOptions, LPTreeNode, LPWorkspaceTree, } from './types.js';
34
34
  export type { AccessTier } from './types.js';
35
35
  export { METHOD_TIERS, ENTITIES } from './types.js';
36
36
  export { ok, err, getErrorMessage, normalizeEnum, TTLCache, MemoryCacheStore, LayeredCache } from '@markwharton/api-core';
package/dist/types.d.ts CHANGED
@@ -265,6 +265,20 @@ export interface LPUpsertOptions {
265
265
  */
266
266
  accumulate?: boolean;
267
267
  }
268
+ /**
269
+ * One member's timesheet entries in the grouped result
270
+ */
271
+ export interface LPMemberTimesheetEntries {
272
+ memberId: number;
273
+ entries: LPTimesheetEntry[];
274
+ }
275
+ /**
276
+ * Timesheet entries grouped by member.
277
+ * One member entry when memberId specified, all members when omitted.
278
+ */
279
+ export interface LPTimesheetEntries {
280
+ members: LPMemberTimesheetEntries[];
281
+ }
268
282
  /**
269
283
  * Assignment with resolved parent context
270
284
  *
@@ -350,7 +364,7 @@ export interface LPMemberAssignments {
350
364
  * Normalized assignments result with shared lookup tables.
351
365
  * Deduplicates ancestor items and custom field values to reduce payload size.
352
366
  */
353
- export interface LPAssignmentsResult {
367
+ export interface LPAssignments {
354
368
  /** Assignments grouped by member. One entry when memberId specified, all members when omitted. */
355
369
  members: LPMemberAssignments[];
356
370
  /** Shared items lookup — walk parentId chains to reconstruct ancestor paths */
@@ -360,6 +374,39 @@ export interface LPAssignmentsResult {
360
374
  /** Total items in the workspace tree snapshot */
361
375
  treeItemCount: number;
362
376
  }
377
+ /**
378
+ * Options for getWeeklySummary
379
+ */
380
+ export interface LPWeeklySummaryOptions {
381
+ /** Filter to a specific member. When omitted, returns all members. */
382
+ memberId?: number;
383
+ /** Optional detail sections to include alongside the time summary */
384
+ include?: ('assignments' | 'lateItems')[];
385
+ }
386
+ /**
387
+ * One member's weekly summary with time breakdown by date
388
+ */
389
+ export interface LPMemberWeeklySummary {
390
+ memberId: number;
391
+ /** Total hours per date (date string → hours) */
392
+ timeSummary: Record<string, number>;
393
+ /** Sum of all hours across all dates */
394
+ totalHours: number;
395
+ /** Raw timesheet entries for this member */
396
+ entries: LPTimesheetEntry[];
397
+ }
398
+ /**
399
+ * Weekly summary with per-member time breakdowns and optional detail sections.
400
+ * Aggregates timesheet entries, assignments, and late items into a single result.
401
+ */
402
+ export interface LPWeeklySummary {
403
+ /** Per-member weekly summaries */
404
+ members: LPMemberWeeklySummary[];
405
+ /** Assignments data (when 'assignments' included) */
406
+ assignments?: LPAssignments;
407
+ /** Late items (when 'lateItems' included) */
408
+ lateItems?: LPItem[];
409
+ }
363
410
  /**
364
411
  * Options for querying items with LP API filters
365
412
  *
package/dist/types.js CHANGED
@@ -26,6 +26,7 @@ export const METHOD_TIERS = {
26
26
  getTimesheetEntries: 'standard',
27
27
  updateTimesheetEntry: 'standard',
28
28
  upsertTimesheetEntry: 'standard',
29
+ getWeeklySummary: 'standard',
29
30
  };
30
31
  // ============================================================================
31
32
  // Entity Registry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markwharton/liquidplanner",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
4
4
  "description": "LiquidPlanner API client for timesheet integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",