Refactor timesheet automation to dynamic payload generation - #1
Open
chelstein wants to merge 10 commits into
Open
Refactor timesheet automation to dynamic payload generation#1chelstein wants to merge 10 commits into
chelstein wants to merge 10 commits into
Conversation
… structure - Rename date node to "Set Current Date and Time GMT -7" for clarity - Add new "Code in JavaScript" node to dynamically compute next-day date for non-billable 2hr entry (replacing hardcoded dates) - Fix CW Billable Post 8hrs: resolve missing comma syntax bug and use dynamic date expressions - Update CW NON-Billable Post 2hrs to consume structured body from code node via $json.body - Restructure connections: Schedule Trigger → Set Date → [8hr Billable (parallel), Code JS → 2hr Non-Billable] https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
- Replace Schedule Trigger (disabled) with Manual Trigger for on-demand execution - Refactor date node to use Intl.DateTimeFormat for cleaner Phoenix/AZ date extraction - Add SOC Roster node: full member list with roles, days, capacity, and department metadata - Add SOC Clients node: 10 billable customer accounts with company IDs and identifiers - Add schedule/shift version node: 3-shift structure (1st/2nd/3rd) with per-member day assignments - Add Aggregate node: collects date, roster, clients, and shift data into single payload - Wire all 4 data sources into Aggregate before Code in JavaScript processes them - Code in JavaScript now fans out to both CW Billable and CW NON-Billable post nodes https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
…g member
- Rewrite Code in JavaScript to read aggregated data ($json.data) from the Aggregate
node instead of broken $json.date reference
- Filter members working today by day-of-week (derived from AZ date string)
- Split each member's 8 billable hours equally across all 10 customers (0.8 hrs each),
using per-member shift times from the schedule/shift version node (AZ → UTC conversion
handles all 3 shifts including overnight crossings)
- Append one 2-hr DoNotBill admin entry per working member immediately after their shift
- All entries share a single body field and flow through CW Billable Post 8hrs;
remove the now-redundant Code → CW NON-Billable Post fan-out
- Update CW Billable Post 8hrs jsonBody from inline hardcoded template to ={{ $json.body }}
On a day when all 9 members work: 9 members × (10 billable + 1 admin) = 99 CW time entries
https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
- Use member.dailyBillable (from roster) instead of hardcoded 8hrs so each member's individual allocation drives their customer distribution - Use member.dailyAdmin (from roster) instead of hardcoded 2hrs for the internal non-billable entry, same rationale - Filter to only customers where billable=true before dividing hours, so non-billable customers are excluded from the shared pool distribution - Move hoursPerCustomer calculation inside the member loop so each member's own dailyBillable / numBillableCustomers is computed independently https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
- Replace Aggregate node with Merge chain (Merge/Merge1/Merge2/Merge3) so
all data sources fan into a single flat $json for the code node
- Add Billing Mapper node: per-member allowedBillingMap restricts which
customer companies each analyst can bill to in ConnectWise
- Rename main code node to 'CW Payload'; it now reads allowedBillingMap
and uses a deterministic date+member hash to rotate customer assignment
day-over-day without repeating the same client back-to-back
- Members with no billing map entry fall back to a DoNotBill internal entry
covering their full shift (billable + admin hours combined)
- Add passthrough 'Code in JavaScript' node to strip metadata before the
CW Billable HTTP POST
- Fix chargeToType: was hardcoded 'Company' (invalid CW value); now uses
analyst.chargeToType from roster ('ChargeCode') with chargeToId from roster
- Remove Sticky Note and old Aggregate-based Code node
https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
Reuse / duplication: - Extract makeBody() helper — eliminates three near-identical body object literals (billable, admin-after-billable, unmapped-internal); all body fields now built in one place - Extract makeEntry() helper — removes repeated memberId/name/identifier/ weekday/shiftName/type/clientCompanyId/clientName wrapper on every push - Extract CW_FLAGS constant — addToDetail/InternalAnalysis/ResolutionFlag false values no longer copy-pasted three times Quality: - Remove `continue` after else-block — restructure as clean if/else so control flow is explicit; the two admin push paths are now clearly separated within their own branches - Remove unused `shiftEnd` and `title` fields from scheduled entries - Rename buildDeterministicOffset → deterministicOffset (shorter, same meaning) Efficiency: - Replace toUtcRange (called twice per analyst, parses date string twice) with parseShiftStart + addHours + isoZ; date/time parsed once per analyst, subsequent time points derived by arithmetic - Pre-convert allowedBillingMap IDs to strings before the loop (static per run); was re-running .map(String) on every analyst iteration - Compute fullEnd lazily inside else-branch only when adminHours > 0 https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
Billable entries were incorrectly using the internal ChargeCode/chargeToId
from the roster instead of billing against the customer company directly.
- makeBody() now takes a destructured object with explicit chargeToType and
chargeToId params; chargeToId is only included in the body when non-null
(omitted entirely for 'Company'-type entries per CW API requirements)
- Billable entries: chargeToType='Company', no chargeToId — time charges
directly to the assigned customer company
- Admin entries (both post-billable and unmapped-member fallback):
chargeToType from roster ('ChargeCode'), chargeToId=35 — unchanged internal
overhead billing behaviour
- Add Content-Type: application/json header to both CW HTTP POST nodes
https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
chargeToType:'Company' requires CW member-level company access rights that are not configured, causing 400 'Member does not have rights to enter time against company' on every billable POST. Billable entries now use the same chargeToType:'ChargeCode'/chargeToId:35 as admin entries. CW determines client-billability from billableOption:'Billable' (not chargeToType), and company.id still correctly associates the entry with the customer company for reporting and invoicing. https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
Previous djb2 hash produced pseudo-random offsets that could repeat customers before covering all of them — no guarantee of full rotation within a month. New customerOffset(date, memberKey, modulo): - Uses epochDay % modulo as the base step, advancing one slot each day - Each member gets a fixed starting offset (memberId % modulo) so analysts stagger through their allowed customer lists independently - Guarantees every customer in an analyst's list is hit within N consecutive working days, where N = len(allowedBillingMap[member]) - With 2-4 customers per analyst, full rotation completes every 2-4 days, ensuring complete monthly coverage well before month-end https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
New nodes from prod version:
- Agreement IDs By Customer: maps all 10 company IDs to their CW agreement IDs
- Merge4: combines Merge3 output with agreementMap before CW Payload
CW Payload changes:
- Uses chargeToType: 'Company' on billable entries (was ChargeCode)
- Attaches agreement: { id } to billable body when agreementMap has a match
- Exposes agreementId on output item for traceability
- Replaces hash-based buildDeterministicOffset with sequential customerOffset
(epoch-day + per-member seed) — guarantees full customer rotation by month-end
https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactored the timesheet workflow from hardcoded static payloads to a dynamic, data-driven system that generates ConnectWise time entries based on roster schedules, shift assignments, and billing mappings.
Key Changes
Replaced hardcoded HTTP request bodies with dynamic payload generation via the "CW Payload" code node that:
Added comprehensive data configuration nodes:
Implemented intelligent routing logic:
Enhanced time calculation:
Improved workflow structure:
Implementation Details
The "CW Payload" code node implements sophisticated logic including:
https://claude.ai/code/session_01X4mjyuJq7XZwJ9faduKfF3