# 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.