|
| 1 | +import { declarativeRestConnector } from './declarative-rest.js' |
| 2 | + |
| 3 | +/** |
| 4 | + * ADP (Workforce Now / ADP Marketplace) — HR/payroll reads over workers, worker |
| 5 | + * demographics, pay statements, and pay distributions. |
| 6 | + * |
| 7 | + * ┌─────────────────────────────────────────────────────────────────────────┐ |
| 8 | + * │ RUNTIME GAP — MANDATORY mTLS. NOT EXECUTABLE THROUGH THE SHARED RUNTIME. │ |
| 9 | + * │ │ |
| 10 | + * │ ADP requires MUTUAL TLS (a client X.509 certificate, CSR-signed by ADP) │ |
| 11 | + * │ presented at the TLS handshake on EVERY call — both the token endpoint │ |
| 12 | + * │ (accounts.adp.com) and the data gateway (api.adp.com). The shared │ |
| 13 | + * │ declarative-REST fetch path cannot attach a client certificate to its │ |
| 14 | + * │ outbound TLS connection, so every request here will fail the handshake │ |
| 15 | + * │ until per-connection client-cert plumbing is added to the runtime. │ |
| 16 | + * │ │ |
| 17 | + * │ This adapter therefore ships the MANIFEST + ISOLATION TESTS only. The │ |
| 18 | + * │ capability set, paths, and auth shape are correct and validated against │ |
| 19 | + * │ stubbed fetch; live execution is gated on a follow-up that teaches the │ |
| 20 | + * │ runtime to present client certs (tracked separately — see the PR). │ |
| 21 | + * └─────────────────────────────────────────────────────────────────────────┘ |
| 22 | + * |
| 23 | + * Auth is OAuth2; ADP supports both client_credentials (server-to-server Data |
| 24 | + * Connector apps — the natural fit for bulk reads) and authorization_code |
| 25 | + * (delegated End-User apps). We declare the authorization_code shape here so |
| 26 | + * the manifest carries both the authorize and token URLs; the access token is |
| 27 | + * still injected by the platform and sent as `Authorization: Bearer`. |
| 28 | + * |
| 29 | + * Most HR/payroll endpoints also require a `roleCode` header |
| 30 | + * (practitioner|employee|manager|administrator|supervisor); we default it to |
| 31 | + * `practitioner`. SSN/birth-date are masked unless the caller adds |
| 32 | + * `Accept: application/json;masked=false` with a practitioner role + scopes — |
| 33 | + * we leave masking ON by default. The `associateOID` (AOID) is the primary key |
| 34 | + * resolved from worker.list and threaded into every per-worker endpoint. |
| 35 | + * |
| 36 | + * The pay-statement IMAGE endpoint is intentionally excluded — it returns a |
| 37 | + * binary PDF, which the JSON runtime cannot parse. |
| 38 | + */ |
| 39 | +export const adpConnector = declarativeRestConnector({ |
| 40 | + kind: 'adp', |
| 41 | + displayName: 'ADP', |
| 42 | + description: |
| 43 | + 'Read HR and payroll data from ADP Workforce Now: workers, worker demographics, pay statements, and pay distributions. Requires mutual-TLS client certificates (see adapter notes) in addition to OAuth2.', |
| 44 | + auth: { |
| 45 | + kind: 'oauth2', |
| 46 | + authorizationUrl: 'https://accounts.adp.com/auth/oauth/v2/authorize', |
| 47 | + tokenUrl: 'https://accounts.adp.com/auth/oauth/v2/token', |
| 48 | + scopes: [ |
| 49 | + 'hr/workerInformationManagement/read', |
| 50 | + 'payroll/payStatementManagement/read', |
| 51 | + ], |
| 52 | + clientIdEnv: 'ADP_OAUTH_CLIENT_ID', |
| 53 | + clientSecretEnv: 'ADP_OAUTH_CLIENT_SECRET', |
| 54 | + }, |
| 55 | + category: 'hr', |
| 56 | + defaultConsistencyModel: 'authoritative', |
| 57 | + baseUrl: 'https://api.adp.com', |
| 58 | + // ADP requires a roleCode on most HR/payroll endpoints; practitioner is the |
| 59 | + // broadest read role. (PII stays masked unless the caller opts into |
| 60 | + // `Accept: application/json;masked=false`.) |
| 61 | + defaultHeaders: { roleCode: 'practitioner' }, |
| 62 | + test: { method: 'GET', path: '/hr/v2/workers/meta' }, |
| 63 | + capabilities: [ |
| 64 | + { |
| 65 | + name: 'worker.list', |
| 66 | + class: 'read', |
| 67 | + description: |
| 68 | + 'Collection of all workers for the authenticated client. OData paging via $top/$skip (default 50, max 100); total at meta.totalNumber. The associateOID on each worker is the key for per-worker endpoints. SSN/birth date are masked.', |
| 69 | + parameters: { |
| 70 | + type: 'object', |
| 71 | + properties: { |
| 72 | + top: { type: 'integer', description: 'OData $top page size (default 50, max 100).' }, |
| 73 | + skip: { type: 'integer', description: 'OData $skip offset for pagination.' }, |
| 74 | + filter: { type: 'string', description: 'OData $filter expression.' }, |
| 75 | + select: { type: 'string', description: 'OData $select field projection.' }, |
| 76 | + }, |
| 77 | + }, |
| 78 | + request: { |
| 79 | + method: 'GET', |
| 80 | + path: '/hr/v2/workers', |
| 81 | + query: { $top: '{top}', $skip: '{skip}', $filter: '{filter}', $select: '{select}' }, |
| 82 | + }, |
| 83 | + }, |
| 84 | + { |
| 85 | + name: 'worker.get', |
| 86 | + class: 'read', |
| 87 | + description: 'A single worker by Associate OID (aoid). SSN/birth date are masked unless unmasking is requested.', |
| 88 | + parameters: { |
| 89 | + type: 'object', |
| 90 | + properties: { |
| 91 | + aoid: { type: 'string', description: 'Associate OID from worker.list.' }, |
| 92 | + select: { type: 'string', description: 'OData $select field projection.' }, |
| 93 | + }, |
| 94 | + required: ['aoid'], |
| 95 | + }, |
| 96 | + request: { |
| 97 | + method: 'GET', |
| 98 | + path: '/hr/v2/workers/{aoid}', |
| 99 | + query: { $select: '{select}' }, |
| 100 | + }, |
| 101 | + }, |
| 102 | + { |
| 103 | + name: 'worker.demographics.list', |
| 104 | + class: 'read', |
| 105 | + description: |
| 106 | + 'Worker collection WITHOUT PII/SPI (no SSN, no full birth date) — a lighter payload than worker.list. OData paging supported.', |
| 107 | + parameters: { |
| 108 | + type: 'object', |
| 109 | + properties: { |
| 110 | + top: { type: 'integer' }, |
| 111 | + skip: { type: 'integer' }, |
| 112 | + filter: { type: 'string' }, |
| 113 | + select: { type: 'string' }, |
| 114 | + }, |
| 115 | + }, |
| 116 | + request: { |
| 117 | + method: 'GET', |
| 118 | + path: '/hr/v2/worker-demographics', |
| 119 | + query: { $top: '{top}', $skip: '{skip}', $filter: '{filter}', $select: '{select}' }, |
| 120 | + }, |
| 121 | + }, |
| 122 | + { |
| 123 | + name: 'worker.demographics.get', |
| 124 | + class: 'read', |
| 125 | + description: 'Demographic data for one worker by Associate OID, without PII/SPI.', |
| 126 | + parameters: { |
| 127 | + type: 'object', |
| 128 | + properties: { |
| 129 | + aoid: { type: 'string' }, |
| 130 | + select: { type: 'string' }, |
| 131 | + }, |
| 132 | + required: ['aoid'], |
| 133 | + }, |
| 134 | + request: { |
| 135 | + method: 'GET', |
| 136 | + path: '/hr/v2/worker-demographics/{aoid}', |
| 137 | + query: { $select: '{select}' }, |
| 138 | + }, |
| 139 | + }, |
| 140 | + { |
| 141 | + name: 'paystatements.list', |
| 142 | + class: 'read', |
| 143 | + description: |
| 144 | + 'Pay statements for one worker: payDate, net/gross pay amounts, total hours, and a statement image URI. Use numberoflastpaydates to cap results.', |
| 145 | + parameters: { |
| 146 | + type: 'object', |
| 147 | + properties: { |
| 148 | + aoid: { type: 'string', description: 'Associate OID from worker.list.' }, |
| 149 | + numberoflastpaydates: { type: 'integer', description: 'Limit to the N most recent pay dates.' }, |
| 150 | + }, |
| 151 | + required: ['aoid'], |
| 152 | + }, |
| 153 | + request: { |
| 154 | + method: 'GET', |
| 155 | + path: '/payroll/v1/workers/{aoid}/pay-statements', |
| 156 | + query: { numberoflastpaydates: '{numberoflastpaydates}' }, |
| 157 | + }, |
| 158 | + }, |
| 159 | + { |
| 160 | + name: 'paystatements.get', |
| 161 | + class: 'read', |
| 162 | + description: |
| 163 | + 'Full detail of one pay statement: pay period, net/gross/YTD amounts, earnings, deductions, and memos. The pay-statement id is resolved from paystatements.list.', |
| 164 | + parameters: { |
| 165 | + type: 'object', |
| 166 | + properties: { |
| 167 | + aoid: { type: 'string' }, |
| 168 | + payStatementId: { type: 'string', description: 'Pay statement id from paystatements.list.' }, |
| 169 | + }, |
| 170 | + required: ['aoid', 'payStatementId'], |
| 171 | + }, |
| 172 | + request: { method: 'GET', path: '/payroll/v1/workers/{aoid}/pay-statements/{payStatementId}' }, |
| 173 | + }, |
| 174 | + { |
| 175 | + name: 'paydistributions.get', |
| 176 | + class: 'read', |
| 177 | + description: |
| 178 | + 'Direct-deposit distribution setup for one worker: accounts, routing numbers, distribution amounts, and remaining-balance flags.', |
| 179 | + parameters: { |
| 180 | + type: 'object', |
| 181 | + properties: { aoid: { type: 'string' } }, |
| 182 | + required: ['aoid'], |
| 183 | + }, |
| 184 | + request: { method: 'GET', path: '/payroll/v2/workers/{aoid}/pay-distributions' }, |
| 185 | + }, |
| 186 | + ], |
| 187 | +}) |
0 commit comments