Files
bodyshop/server/rr/lib/README.md

658 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rr-rome-client
A minimal Node.js wrapper for Reynolds & Reynolds (Rome/RIH) STAR `ProcessMessage` over SOAP, with WS-Security UsernameToken.
## Contents
- Overview
- Install
- Supported Node Version
- Peer / External Dependencies
- Quick Start
- Configuration & Environment Variables
- Client API Methods
- RRResult Structure
- Types & IntelliSense
- Errors & Validation
- Retry Strategy
- Debug / Dump Flags
- Live Test Runner (`scripts/run-live.mjs`)
- Bundling & Upload Helper (`scripts/bundle-for-upload.mjs`)
- XML Templates & XSDs
- Logging
- Build & Development
- Design Notes / Non-goals
- License
## Overview
`rr-rome-client` builds and sends STAR XML payloads (Customer, Service Vehicle, Combined Search, Advisors, Parts, BSM Repair Orders) inside a SOAP envelope using the STAR Transport `ProcessMessage` operation. It applies a WS-Security UsernameToken header and provides:
- High-level `RRClient` methods for each supported Rome operation.
- JSDoc typedefs for all payloads and response shapes (usable in JS and TS).
- Structured parsing of response status blocks and operation-specific data.
- Automatic BODId & CreationDateTime generation, with ability to override.
- Basic input validation and error classification.
- Exponential backoff + jitter for retryable transport/vendor lock scenarios.
## Install
```bash
npm i rr-rome-client
```
(You may also need peer dependencies; see below.)
## Supported Node Version
`package.json` declares `"engines": { "node": ">=22.0.0" }`. The build targets modern Node 22 features (native ESM, improved performance). Earlier Node versions are not officially supported/tested.
## Peer / External Dependencies
Rollup externalizes runtime libraries (they are listed in `dependencies` but not bundled). Ensure these are available in your environment:
Required:
- axios HTTP transport
- fast-xml-parser STAR XML parsing
- mustache XML templating
- uuid BODId generation
Optional (only if you use env loader helpers or live scripts):
- dotenv
- dotenv-expand
Install (versions per `package.json`):
```bash
npm install axios@^1.7.7 fast-xml-parser@^4.5.0 mustache@^4.2.0 uuid@^9.0.1
# Optional env loader
npm install dotenv@^17.2.3 dotenv-expand@^12.0.3
```
TypeScript users (optional):
```bash
npm install -D @types/node
```
Browser bundling is not officially supported; you would need polyfills for core modules if attempting.
## Quick Start
```js
import { RRClient } from 'rr-rome-client';
import { loadEnv } from 'rr-rome-client/src/util/config.js'; // optional helper
// Load routing & credentials from environment variables (see below)
const { baseUrl, username, password, routing } = loadEnv();
const client = new RRClient({ baseUrl, username, password });
// Minimal createRepairOrder example
const result = await client.createRepairOrder({
customerNo: '12345',
departmentType: 'S', // DeptType
vin: '1ABCDEF2GHIJ34567',
outsdRoNo: 'EXT-RO-99'
}, { routing });
if (result.success) {
console.log('RO status:', result.data); // {status, date, time, outsdRoNo, dmsRoNo, errorMessage}
} else {
console.error('Failure:', result.statusBlocks?.transaction);
}
```
## Configuration & Environment Variables
You can manually provide configuration or use the helper `loadEnv(env)` in `src/util/config.js`.
Recognized environment variables:
- RR_BASE_URL SOAP endpoint URL (required)
- RR_USERNAME WS-Security UsernameToken username (required)
- RR_PASSWORD WS-Security UsernameToken password (required)
- RR_DEALER_NUMBER DealerNumber for Destination (required per call)
- RR_STORE_NUMBER StoreNumber (optional)
- RR_AREANUMBER AreaNumber (optional)
Example `.env`:
```
RR_BASE_URL=https://rome.example.com/soap
RR_USERNAME=integratorUser
RR_PASSWORD=superSecret
RR_DEALER_NUMBER=1234
RR_STORE_NUMBER=01
RR_AREANUMBER=1
```
`loadEnv()` returns `{ baseUrl, username, password, routing: { dealerNumber, storeNumber, areaNumber } }`.
Per-call options object shape (`CallOptions`):
```js
{
routing: { dealerNumber: '1234', storeNumber?: '01', areaNumber?: '1' },
envelope?: { bodId?, creationDateTime?, sender?: { component?, task?, referenceId? } }
}
```
If omitted, `RRClient` auto-generates `bodId` (UUID) and `creationDateTime` when sending.
## Client API Methods
Each method returns a `Promise<RRResult<T>>` where `T` is operation-specific data (or array). `success` is true for vendor SUCCESS and NO_MATCH results; FAIL triggers an `RRVendorStatusError` exception before a `RRResult` is returned.
All methods require `opts.routing.dealerNumber`.
### combinedSearch(payload: CombinedSearchQuery, opts)
Search customer + service vehicle combinations by exactly one criterion: `phone | license | vin | name | nameRecId | stkNo` plus optional `make`, `model`, `year`, `maxResults` (capped at 50).
Minimal:
```js
const res = await client.combinedSearch({ kind: 'vin', vin: '12345' }, { routing });
res.data; // Array<CombinedSearchBlock>
```
Errors: Throws `RRValidationError` if missing or multiple criteria.
### insertCustomer(payload: InsertCustomerPayload, opts)
Insert a customer record. Required: `lastName` (or `customerName`). If individual (`ibFlag='I'` or inferred by presence of `firstName`), then `firstName` required.
```js
const res = await client.insertCustomer({ firstName: 'Jane', lastName: 'Doe', phones:[{number:'5551234567'}] }, { routing });
res.data; // { dmsRecKey, status, statusCode }
```
### updateCustomer(payload: UpdateCustomerPayload, opts)
Update existing customer by `nameRecId` plus required `ibFlag`. Other fields optional.
```js
const res = await client.updateCustomer({ nameRecId:'998877', ibFlag:'I', lastName:'Doe' }, { routing });
```
### insertServiceVehicle(payload: InsertServiceVehiclePayload, opts)
Insert a service vehicle linked to a customer. Required: `vin`, `vehicleServInfo.customerNo`.
```js
const res = await client.insertServiceVehicle({
vin:'1HGCM82633A004352',
vehicleServInfo:{ customerNo:'12345' }
}, { routing });
res.data; // { status, statusCode }
```
### getAdvisors(payload: GetAdvisorsParams, opts)
Fetch advisors for a department. Department values normalized: S/P/B or long names.
```js
const res = await client.getAdvisors({ department:'SERVICE' }, { routing });
res.data; // AdvisorRow[]
```
### createRepairOrder(payload: CreateRepairOrderPayload, opts)
Required: `customerNo`, `departmentType`, `vin`, `outsdRoNo`. Advisor optional. Complex nested labor/parts/misc blocks supported via payload.
```js
const res = await client.createRepairOrder({
customerNo:'12345', departmentType:'S', vin:'1ABCDEF2GHIJ34567', outsdRoNo:'EXT-RO-99'
}, { routing });
res.data; // { status, date, time, outsdRoNo, dmsRoNo, errorMessage }
```
### updateRepairOrder(payload: UpdateRepairOrderPayload, opts)
Required: `finalUpdate ('Y'|'N')`, `outsdRoNo`. May include `roNo` and nested sections.
```js
const res = await client.updateRepairOrder({ finalUpdate:'N', outsdRoNo:'EXT-RO-99' }, { routing });
```
### getParts(payload: GetPartsParams, opts)
Required: `roNumber` (internal ERA RO number).
```js
const res = await client.getParts({ roNumber:'938275' }, { routing });
res.data; // PartRow[]
```
## Payload Schema Reference
Comprehensive field-level summary sourced from `src/types.js`, operation builders, and validation logic. Types reflect accepted JS types (string|number where applicable). Constraints list enumerations, inference rules, and validation notes. Required = must be supplied by caller (or inferred automatically). Optional fields omitted become absent in generated XML.
### CombinedSearchQuery
Only one criterion permitted; `maxResults` capped at 50.
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| kind | 'phone'|'license'|'vin'|'name'|'nameRecId'|'stkNo' | Yes | Determines which single criterion block is emitted |
| phone | string\|number\|{phone:string} | Conditionally (if kind==='phone') | Value mapped to `<Phone Num="..."/>` |
| license | string\|number\|{license:string} | Conditionally (kind==='license') | Value mapped to `<LicenseNum LicNo="..."/>` |
| vin | string\|number\|{vin:string} | Conditionally (kind==='vin') | Value mapped to `<PartVIN Vin="..."/>` (partial VIN allowed) |
| name | {fname,lname,mname} or {name} | Conditionally (kind==='name') | Either FullName triple or LName only; must supply all three for FullName |
| nameRecId | string\|number\|{custId:string}|{nameRecId:string} | Conditionally (kind==='nameRecId') | Emits `<NameRecId CustIdStart="..."/>` |
| stkNo | string\|number\|{stkNo:string} | Conditionally (kind==='stkNo') | Emits `<StkNo VehId="..."/>` |
| maxResults | number | No | Capped at 50 (default 50) -> `MaxRecs` attribute |
| make | string | No | Defaults 'ANY' -> `VehData MakePfx` |
| model | string\|number | No | Defaults 'ANY' -> `VehData Model` |
| year | string\|number | No | Defaults 'ANY' -> `VehData Year` |
### InsertCustomerPayload
`ibFlag` inferred as 'I' if `firstName` present, else 'B'. Business requires `lastName` / `customerName`; individual requires `firstName` + last name.
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| ibFlag | 'I'|'B' | Auto / For update must supply | Inferred if omitted (firstName present => 'I') |
| customerType | 'R'|'W'|'I'|'Retail'|'Wholesale'|'Internal' | No | Normalized to 'R','W','I'; must be one of listed |
| createdBy | string | No | Optional CreatedBy attribute |
| customerName | string | Conditional | Alias for `lastName` when business |
| lastName | string | Yes (unless `customerName` provided) | Required base name; sanitized to A-Z0-9 space |
| firstName | string | Required when ibFlag='I' | Sanitized; required for individuals |
| midName | string | No | Sanitized |
| salut | string | No | Sanitized |
| suffix | string | No | Sanitized |
| addresses | CustomerAddress[] | No | Each entry requires `line1`; Type defaults 'P' |
| phones | CustomerPhone[] | No | Each entry requires `number`; Type defaults 'H' |
| emails | CustomerEmail[] | No | First entry used -> `<Email MailTo="..."/>` |
| personal.gender | 'M'|'F'|'U' | No | Optional |
| personal.otherName | string | No | Sanitized alnum/space |
| personal.anniversaryDate | string | No | Included if non-empty |
| personal.employerName | string | No | Sanitized |
| personal.employerPhone | string | No | Raw string |
| personal.occupation | string | No | Sanitized |
| personal.optOut | string | No | Pass-through |
| personal.optOutUse | string | No | Pass-through |
| personal.birthDates[].type | 'P'|'S' | No | Defaults 'P'; entry must have `date` |
| personal.birthDates[].date | string | Conditional | Included only if non-empty |
| personal.ssns[].type | 'P'|'S' | No | Defaults 'P'; entry must have `ssn` |
| personal.ssns[].ssn | string | Conditional | Included only if non-empty |
| personal.driver.type | 'P'|'S' | No | Defaults 'P' |
| personal.driver.licenseNumber | string | Conditional | Required to emit DriverInfo |
| personal.driver.licenseState | string | No | Optional |
| personal.driver.licenseExpDate | string | No | Optional |
| personal.children[].name | string | No | Sanitized; optional list |
| dms.taxExemptNum | string | No | Optional |
| dms.salesTerritory | string | No | Optional |
| dms.deliveryRoute | string | No | Optional |
| dms.salesmanNum | string | No | Optional |
| dms.lastContactMethod | string | No | Optional |
| dms.followups[].type | string | Conditional | Must have both type & value |
| dms.followups[].value | string | Conditional | Must have both type & value |
### UpdateCustomerPayload
Extends InsertCustomerPayload plus:
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| nameRecId | string\|number | Yes | Required identifier |
| ibFlag | 'I'|'B' | Yes | Must be explicitly provided on update |
### InsertServiceVehiclePayload
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| vin | string | Yes | Must be provided |
| modelDesc | string | No | Optional attribute |
| carline | string | No | Optional attribute |
| extClrDesc | string | No | Optional attribute |
| intClrDesc | string | No | Optional attribute |
| trimDesc | string | No | Optional attribute |
| bodyStyle | string | No | Optional attribute |
| engineDesc | string | No | Optional attribute |
| transDesc | string | No | Optional attribute |
| year | string\|number | No | Emits `<Year>` element if present |
| odometer | string\|number | No | Emits `<Odometer>` |
| odometerUnits | string | No | Emits `<OdometerUnits>` |
| vehicleDetail.licNo | string | No | Emits `<VehicleDetail LicNo="..."/>` |
| vehicleServInfo.customerNo | string\|number | Yes | Required; becomes `CustomerNo` attribute |
| vehicleServInfo.salesmanNo | string\|number | No | Optional element |
| vehicleServInfo.inServiceDate | string\|number | No | Optional element |
| vehicleServInfo.mileage | string\|number | No | Optional element |
| vehicleServInfo.teamCode | string | No | Optional element |
| vehicleServInfo.vehExtWarranty.contractNumber | string | Conditional | Included if any warranty field present |
| vehicleServInfo.vehExtWarranty.expirationDate | string | Conditional | "" excluded |
| vehicleServInfo.vehExtWarranty.expirationMileage | string\|number | Conditional | "" excluded |
| vehicleServInfo.advisor.contactInfo.nameRecId | string\|number | No | Advisor block included only if provided |
### CreateRepairOrderPayload
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| customerNo | string\|number | Yes | CustNo |
| departmentType | string\|number | Yes | DeptType |
| vin | string | Yes | Vin |
| outsdRoNo | string\|number | Yes | External RO identifier |
| advisorNo | string\|number | No | AdvNo |
| tagNo | string\|number | No | TagNo |
| mileageIn | string\|number | No | MileageIn |
| roComment | string | No | `<RoCommentInfo RoComment="..."/>` |
| estimate.parts | string\|number | No | EstPartsAmt |
| estimate.labor | string\|number | No | EstLaborAmt |
| estimate.total | string\|number | No | EstTotalAmt |
| tax.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration validated |
| tax.taxCode | string | No | TaxCode |
| tax.txblGrossAmt | string\|number | No | TxblGrossAmt |
| tax.grossTaxAmt | string\|number | No | GrossTaxAmt |
| rolabor.ops[].opCode | string | No | Optional |
| rolabor.ops[].jobNo | string\|number | No | Optional |
| rolabor.ops[].custPayTypeFlag | string | No | Freeform (not validated) |
| rolabor.ops[].warrPayTypeFlag | string | No | Freeform |
| rolabor.ops[].intrPayTypeFlag | string | No | Freeform |
| rolabor.ops[].custTxblNtxblFlag | 'T'|'N' | No | Enumerated & validated |
| rolabor.ops[].warrTxblNtxblFlag | 'T'|'N' | No | Enumerated & validated |
| rolabor.ops[].intrTxblNtxblFlag | 'T'|'N' | No | Enumerated & validated |
| rolabor.ops[].vlrCode | string | No | Optional |
| rolabor.ops[].bill.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration |
| rolabor.ops[].bill.jobTotalHrs | string\|number | No | Optional |
| rolabor.ops[].bill.billTime | string\|number | No | Optional |
| rolabor.ops[].bill.billRate | string\|number | No | Optional |
| rolabor.ops[].ccc.cause | string | No | Optional |
| rolabor.ops[].ccc.complaint | string | No | Optional |
| rolabor.ops[].ccc.correction | string | No | Optional |
| rolabor.ops[].amount.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration |
| rolabor.ops[].amount.amtType | string | No | Optional |
| rolabor.ops[].amount.custPrice | string\|number | No | Optional |
| rolabor.ops[].amount.totalAmt | string\|number | No | Optional |
| ropart.jobs[].opCode | string | No | Optional |
| ropart.jobs[].jobNo | string\|number | No | Optional |
| ropart.jobs[].lines[].partNo | string | No | Optional |
| ropart.jobs[].lines[].partNoDesc | string | No | Optional |
| ropart.jobs[].lines[].partQty | string\|number | No | Emits QtyOrd |
| ropart.jobs[].lines[].sale | string\|number | No | Sale |
| ropart.jobs[].lines[].cost | string\|number | No | Cost |
| ropart.jobs[].lines[].addDeleteFlag | string | No | AddDeleteFlag |
| rogg.ops[].lines[].breakOut | string | No | Optional |
| rogg.ops[].lines[].itemType | 'G'|'P'|'S'|'F' | No | Enumerated & validated |
| rogg.ops[].lines[].itemDesc | string | No | Optional |
| rogg.ops[].lines[].custQty | string\|number | No | Optional |
| rogg.ops[].lines[].warrQty | string\|number | No | Optional |
| rogg.ops[].lines[].intrQty | string\|number | No | Optional |
| rogg.ops[].lines[].custPayTypeFlag | string | No | Optional |
| rogg.ops[].lines[].warrPayTypeFlag | string | No | Optional |
| rogg.ops[].lines[].intrPayTypeFlag | string | No | Optional |
| rogg.ops[].lines[].custTxblNtxblFlag | 'T'|'N' | No | Enumerated |
| rogg.ops[].lines[].warrTxblNtxblFlag | 'T'|'N' | No | Enumerated |
| rogg.ops[].lines[].intrTxblNtxblFlag | 'T'|'N' | No | Enumerated |
| rogg.ops[].lines[].amount.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration |
| rogg.ops[].lines[].amount.amtType | string | No | Optional |
| rogg.ops[].lines[].amount.custPrice | string\|number | No | Optional |
| rogg.ops[].lines[].amount.dlrCost | string\|number | No | Optional |
| romisc.ops[].lines[].miscCode | string | No | Optional |
| romisc.ops[].lines[].custPayTypeFlag | string | No | Optional |
| romisc.ops[].lines[].warrPayTypeFlag | string | No | Optional |
| romisc.ops[].lines[].intrPayTypeFlag | string | No | Optional |
| romisc.ops[].lines[].custTxblNtxblFlag | 'T'|'N' | No | Enumerated |
| romisc.ops[].lines[].warrTxblNtxblFlag | 'T'|'N' | No | Enumerated |
| romisc.ops[].lines[].intrTxblNtxblFlag | 'T'|'N' | No | Enumerated |
| romisc.ops[].lines[].codeAmt | string\|number | No | Optional |
### UpdateRepairOrderPayload
Adds required `finalUpdate` and supports most Create fields + mileageOut.
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| finalUpdate | 'Y'|'N' | Yes | Must be 'Y' or 'N' (validated) |
| outsdRoNo | string\|number | Yes | External RO identifier (validation requires present) |
| roNo | string\|number | No | Optional internal RoNo |
| mileageOut | string\|number | No | MileageOut |
| (other fields) | see CreateRepairOrderPayload | Conditional | Same validations apply |
### GetAdvisorsParams
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| department | 'S'|'P'|'B'|'SERVICE'|'PARTS'|'BODY'|'BODYSHOP'|'BODY SHOP' | Yes | Normalized to S/P/B |
| advisorNumber | string\|number | No | Optional filter |
| maxResults | number | Ignored | Not in XSD (builder ignores) |
### GetPartsParams
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| roNumber | string\|number | Yes | Required; becomes `<RoInfo RoNumber="..."/>` |
### Common Routing & Envelope Fields
| Field | Type | Required | Constraints / Notes |
|-------|------|----------|----------------------|
| routing.dealerNumber | string | Yes | Required for all requests (Destination) |
| routing.storeNumber | string | No | Optional Destination StoreNumber |
| routing.areaNumber | string | No | Optional Destination AreaNumber |
| envelope.bodId | string | Auto | UUID generated if omitted |
| envelope.creationDateTime | Date\|string | Auto | Uses current time; formatted without milliseconds |
| envelope.sender.component | string | No | Defaults 'Rome' if omitted |
| envelope.sender.task | string | No | Op-specific defaults (e.g. 'CU','SV','BSMRO','CVC','RCT') |
| envelope.sender.referenceId | string | No | Op-specific defaults ('Insert','Update','Query') |
### AdvisorRow (response convenience)
| Field | Type | Notes |
|-------|------|-------|
| advisorId | string\|number\|undefined | AdvisorNumber attribute |
| firstName | string\|undefined | FirstName attribute |
| lastName | string\|undefined | LastName attribute |
| department | 'S'|'P'|'B'|undefined | Normalized department passed through |
### PartRow (response convenience)
| Field | Type | Notes |
|-------|------|-------|
| partNumber | string\|undefined | PartNumber |
| partDescription | string\|undefined | PartDescription |
| quantityOrdered | string\|number\|undefined | QuantityOrdered |
| quantityShipped | string\|number\|undefined | QuantityShipped |
| price | string\|number\|undefined | Price |
| cost | string\|number\|undefined | Cost |
| processedFlag | string\|undefined | ProcessedFlag |
| addOrDelete | string\|undefined | AddOrDelete |
### CombinedSearch Response Shapes
Hierarchical breakdown of the parsed array returned under `RRResult.data` for `combinedSearch` (each entry is a `CombinedSearchBlock`). Attribute-centric; absent fields are simply omitted.
#### CombinedSearchBlock
| Field | Type | Notes |
|-------|------|-------|
| NameContactId | CombinedSearchNameContactId | Customer name/contact composite |
| ServVehicle | CombinedSearchServVehicle[] | Zero or more service vehicle blocks |
| Message | CombinedSearchMessage[] | Optional informational messages |
#### NameContactId
| Field | Type | Notes |
|-------|------|-------|
| NameId | CombinedSearchNameId | Includes identifiers & individual/business name choice |
| Address | Object[] | Each has address-related attributes (lines, city, state, zip, etc.) |
| ContactOptions | Object[] | Arbitrary contact option attributes as present |
| Phone | Object[] | Each phone entry carries attributes like Type, Num, Ext |
| Email | Object[] | Each email entry carries attributes (MailTo, etc.) |
#### NameId
| Field | Type | Notes |
|-------|------|-------|
| NameRecId | string\|number | Identifier used for subsequent operations |
| IBFlag | 'I'|'B' | Individual or Business flag |
| IndName | Object | Present for individual: attributes like FName, LName, MName |
| BusName | Object | Present for business: attributes like Name (business name) |
#### ServVehicle (CombinedSearchServVehicle)
| Field | Type | Notes |
|-------|------|-------|
| Vehicle | CombinedSearchVehicle | VIN + descriptive attributes |
| VehicleServInfo | CombinedSearchVehicleServInfo | Service info attributes & nested warranty/advisor/comments |
#### Vehicle (CombinedSearchVehicle)
| Attribute | Type | Notes |
|-----------|------|-------|
| Vin | string | Vehicle identification number (may be partial in search results) |
| VehicleMake | string | Make code / description |
| VehicleYr | string\|number | Year |
| MdlNo | string | Model code |
| ModelDesc | string | Model description |
| Carline | string | Carline description |
| ExtClrDesc | string | Exterior color |
| IntClrDesc | string | Interior color |
| MakeName | string | Full make name |
| VehicleDetail.LicNo | string | License plate (if present) |
#### VehicleServInfo (CombinedSearchVehicleServInfo)
| Attribute | Type | Notes |
|-----------|------|-------|
| CustomerNo | string\|number | Linked customer number |
| SalesmanNo | string\|number | Optional salesman number |
| InServiceDate | string\|number | Date vehicle placed in service |
| Mileage | string\|number | Current mileage |
| TeamCode | string | Team/department code |
| VehExtWarranty.ContractNumber | string | Extended warranty contract number |
| VehExtWarranty.ExpirationDate | string | Warranty expiration date |
| VehExtWarranty.ExpirationMileage | string\|number | Warranty mileage limit |
| Advisor.ContactInfo.NameRecId | string\|number | Advisor reference (if present) |
| VehServComments[] | string[] | Freeform service comments (array of raw text) |
#### Message (CombinedSearchMessage)
| Field | Type | Notes |
|-------|------|-------|
| MessageNo | string\|number | Optional message number (if provided) |
| Text | string | Message text content |
### CustomerResponseData
Returned as `result.data` for `insertCustomer` / `updateCustomer`.
| Field | Type | Notes |
|-------|------|-------|
| dmsRecKey | string\|undefined | DMS record key identifier (if provided in TransStatus) |
| status | string\|undefined | Vendor status string |
| statusCode | string\|undefined | Vendor status code |
### ServiceVehicleResponseData
Returned as `result.data` for `insertServiceVehicle`.
| Field | Type | Notes |
|-------|------|-------|
| status | string\|undefined | GenTransStatus Status |
| statusCode | string\|undefined | GenTransStatus StatusCode |
### RepairOrderData
Returned as `result.data` for `createRepairOrder` / `updateRepairOrder` (parsed from RoRecordStatus).
| Field | Type | Notes |
|-------|------|-------|
| status | string\|undefined | RoRecordStatus Status |
| date | string\|undefined | Date attribute/text |
| time | string\|undefined | Time attribute/text |
| outsdRoNo | string\|undefined | External RO number (OutsdRoNo) |
| dmsRoNo | string\|undefined | Internal DMS RO number (DMSRoNo) |
| errorMessage | string\|undefined | ErrorMessage if provided |
## RRResult Structure
Each successful call resolves to:
```ts
interface RRResult<T> {
success: boolean; // SUCCESS or NO_MATCH
data?: T; // op-specific parsed convenience data
parsed: any; // entire parsed STAR payload root
xml: { request: string; response: string }; // raw SOAP envelopes
statusBlocks?: { transaction?: {status,statusCode,message}; roRecord?: {status,date,time,outsdRoNo,dmsRoNo,errorMessage} };
applicationArea?: any; // raw ApplicationArea node
}
```
Use `statusBlocks.transaction` for generic status; `data` for normalized op output.
`Convenience data` refers to the distilled, operation-specific subset placed on `RRResult.data` by a dedicated `postParse` function (e.g., extracting only RoRecordStatus identifiers or customer DMS keys). It is intentionally smaller and flatter than `RRResult.parsed`, which contains the entire parsed STAR payload tree. Use `data` for common identifiers/status checks; fall back to `parsed` when you need full raw XML-derived detail.
## Types & IntelliSense
Rich JSDoc typedefs ship with the package.
- ESM: `import { RRClient } from 'rr-rome-client';` optionally import `'rr-rome-client/types'` to prompt editor indexing.
- CJS: `const { RRClient } = require('rr-rome-client'); require('rr-rome-client/types');`
Selected typedef categories (see `src/types.js`):
- Routing / Envelope / CallOptions
- Customer / Service Vehicle / Repair Order payload blocks
- Combined Search structures
- Advisor and Parts row shapes
- `RRResult<T>` generic helper
The build also emits a TypeScript declaration bundle (`dist/types/index.d.ts`) generated via `tsconfig.types.json` (processing only `src/types.js`).
## Errors & Validation
Three custom error classes (`src/errors.js`):
- `RRTransportError` Non-2xx HTTP status or network failure; `meta.status` / `meta.body` may be attached.
- `RRVendorStatusError` Vendor FAIL status (non-success & not NO_MATCH). Includes `meta.status` (raw status object) and full response XML. `retryable` may be set (currently determined by vendor message lock wording or explicit flag).
- `RRValidationError` Input validation failures (missing required fields, invalid enumeration values, etc.).
Enumerated validations (examples):
- Customer: `ibFlag`, required `firstName` if individual, allowed `customerType` values.
- Service Vehicle: mandatory `vin` and `vehicleServInfo.customerNo`.
- Repair Orders: required header fields; enumerations for tax pay type (`All|Cust|Intr|Warr`), taxable flags (`T|N`), item types (`G|P|S|F`).
Error Handling Example:
```js
try {
await client.createRepairOrder(/* ... */);
} catch (e) {
if (e instanceof RRVendorStatusError) {
console.error('Vendor fail', e.meta.status);
} else if (e instanceof RRValidationError) {
console.error('Bad input', e.message);
} else if (e instanceof RRTransportError) {
console.error('HTTP/Network', e.message, e.meta.status);
} else {
console.error('Unexpected', e);
}
}
```
## Retry Strategy
`withBackoff(fn, {max, logger})` (used internally) retries on:
- Transport/network errors (`RRTransportError`).
- Vendor status errors indicating record lock/in use (message matching `/lock|in use|record.*busy/i`) or explicit `retryable` flag.
Backoff: exponential starting at 400ms, capped at 10s, plus up to 250ms jitter.
Configure via `new RRClient({ retries: { max: 5 }, ... })`.
## Debug / Dump Flags
Set these environment variables to inspect request/response internals:
- `RR_DEBUG` Enables debug logging (`defaultLogger.debug`).
- `RR_DUMP_ENVELOPE=1` Prints outgoing SOAP envelope.
- `RR_DUMP_XML=1` Prints full response XML.
- `RR_DUMP_STATUS=1` Logs parsed status blocks.
- `RR_DUMP_APPLICATION=1` Logs raw ApplicationArea block.
- `RR_DUMP_DATA=1` Logs `result.data` (postParse convenience output).
## Live Test Runner (`scripts/run-live.mjs`)
Provides curated integration tests against a live Rome system using Vitest. Loads `.env` then `.env.local` (override). Usage:
```
node scripts/run-live.mjs <test> [flags] [-- ...vitestArgs]
```
Available tests (see script for full list & flags):
- `combinedSearch` VIN or phone search.
- `insertCustomer`, `updateCustomer` Customer record operations (use `--write` to enable live writes).
- `insertServiceVehicle` Add vehicle (requires or discovers customer).
- `getAdvisors` Advisor listing by department.
- `createRepairOrder`, `updateRepairOrder` BSM RO operations.
- `getParts` Retrieve RO parts lines.
- `all` Run all sequentially.
Common flags:
- `--dump``RR_DUMP_ENVELOPE`
- `--write``RR_LIVE_WRITES` (enables writes)
- Operation-specific flags map to `RR_TEST_*` env variables (see script comments for details).
- Arbitrary env: `--set=KEY=VALUE`.
Example:
```
node scripts/run-live.mjs createRepairOrder --write --dump --customerNo=1134485 --vin=1ABCDEF2GHIJ34567 --ro=BSM123
```
## Bundling & Upload Helper (`scripts/bundle-for-upload.mjs`)
Creates text bundles of project files for transport/support purposes.
- Includes key directories (`src/`, `test/`, `schemas/`) and specific root files.
- Excludes large/irrelevant directories (`node_modules`, `dist`, etc.).
- Options: `--max-bytes`, `--pattern=globish`, `--with-env` (include `.env` caution), `--list-only`.
Generates `bundles/bundle-<uuid>-N.txt` with file boundary markers.
## XML Templates & XSDs
Templates reside in `src/templates/templateMap.js` using Mustache. Each operation builder renders a STAR root element plus `ApplicationArea`. XSD files (under `schemas/`) accompany operations:
- Customer Insert/Update: `rey_RomeCustomerInsertReq.xsd`, `rey_RomeCustomerUpdateReq.xsd`
- Service Vehicle Insert: `rey_RomeServVehicleInsertReq.xsd`
- Combined Search: `rey_RomeCustServVehCombReq.xsd`
- Advisors: `rey_RomeGetAdvisorsReq.xsd`
- Parts: `rey_RomeGetPartsReq.xsd`
- Repair Orders Create/Update: `rey_RomeCreateBSMRepairOrderReq.xsd`, `rey_RomeUpdateBSMRepairOrderReq.xsd`
Response XSDs also present for repair orders and others.
Builders attach an `xsdFilename` hint and `elementName`; no runtime XSD validation is performed (the project does not contain a validation module beyond these references).
## Logging
Default logger (`src/logger.js`): logs to console via `info`, `warn`, `error`; `debug` gated by `RR_DEBUG`.
Provide a custom logger with matching method names in `RRClientConfig`:
```js
const logger = { info:()=>{}, warn:()=>{}, error:console.error, debug:()=>{} };
const client = new RRClient({ baseUrl, username, password, logger });
```
## Build & Development
Scripts:
- `npm run build` Rollup builds `dist/index.cjs` & `dist/index.mjs`, copies JSDoc types (`scripts/postbuild-copy-types.mjs`), emits `.d.ts` (via `tsc -p tsconfig.types.json`).
- `npm test` Runs unit tests (`vitest` with `vitest.config.unit.mjs`).
- `npm run bundle` Invoke bundle creation (see above).
- `npm run live:<op>` Convenience commands mapping to `scripts/run-live.mjs` (e.g. `npm run live:getParts`).
Rollup configuration (`rollup.config.mjs`):
- Externalizes Node core modules and listed dependencies.
- Applies terser minification (2 passes, hoisting) for compact output.
- Generates both CJS and ESM entrypoints.
Tree-shaking is enabled (`treeshake.moduleSideEffects = false`). `sideEffects: false` in `package.json` allows downstream bundlers to drop unused exports.
## Design Notes / Non-goals
- WS-Security: Implements only UsernameToken with password type Text or Digest (configurable via `wssePasswordType`). No nonce or timestamp included.
- Idempotency: `bodId` generated per request unless provided; `ensureBodAndDates` centralizes creation.
- Parsing: Focused on extracting status and op-specific identifiers; raw parsed STAR tree still accessible via `result.parsed`.
- Status Handling: Treats `NO_MATCH` (codes like 2 or 213) as non-error with `success=true` so callers can differentiate empty results from failures.
- Validation: Enforces only practical minimum & enumerations; does not attempt full schema compliance.
- No automatic `fetch` transport fallback yet (axios chosen for reliability). You can replace `postSoap` if providing a compatible function returning raw response text and error semantics.
- No built-in XSD validator; XSDs present for reference only.