npm package: @shipx/formula2
Description: Utilities for Shipx
Latest version: 2.5.1

# @shipx/formula2

Centralized computation library for ShipX invoicing, cost sheets, and financial calculations. Provides precise arithmetic (via `mathjs` big numbers) for multi-currency cost/revenue tracking in a shipping/logistics context.

## Architecture

```
src/
  index.ts                       # Re-exports cost + voucher modules
  types/
    types.ts                     # GraphQL-generated types (CostItem, Voucher, etc.)
    example.d.ts                 # Type declaration for sha.js
  lib/
    cost.ts                      # Cost item computation & aggregation
    voucher.ts                   # Voucher/invoice calculation & tax summary
    helpers/
      opBigNumber.ts             # Safe big number arithmetic wrapper
      grossProfit.ts             # Gross profit calculation strategies
      percentage.ts              # Profit margin percentage
      shortBill.ts               # Underbilled amount detection
```

## Exported API

### Cost Module (`cost.ts`)

#### `computeCostItems(costItems: CostItem[], vouchers: Voucher[]): CostItem[]`

Primary function. For each cost item:
- Computes sell/cost rates and totals from base rates and exchange rates
- Aggregates `accountPayable` and `accountReceivable` from approved/paid vouchers (ACCPAY/ACCREC)
- Aggregates `accountPayableDraft` and `accountReceivableDraft` from draft/submitted vouchers
- Calculates `grossProfit`, `percentage`, and `shortBill`
- Handles credit notes (subtracted) and journal vouchers (linked cost item reallocation)

#### `summarizeCostItems(costItems: CostItem[]): CostSheet`

Aggregates an array of computed cost items into a single summary:
- Sums: `estimatedProfit`, `grossProfit`, `costTotal`, `sellTotal`, `accrual`, `accountPayable`, `accountReceivable`, `accountPayableDraft`, `accountReceivableDraft`, `shortBill`
- Computes `shortBillPercentage` = shortBill / sellTotal * 100
- Skips deleted items

#### `calculateGrossProfit(ci, options?: OptionsParam): CostItem`

Standalone gross profit calculation for a single cost item. Supports named strategy overrides via `options.grossProfit`.

#### `sortCostItems(costItems): CostItem[]`

Sorts by: deleted status, charge item sequence, name, code, unit, size.

### Voucher Module (`voucher.ts`)

#### `calculateVoucher(voucher: Voucher, opts?: RoundingOption[]): Voucher`

Calculates a complete voucher:
- Runs `calculateVoucherItem` on each line item
- Aggregates subtotals, tax totals, and grand totals (base, converted, local currencies)
- Computes `dueDate` from `issueDate + term` days
- Calculates `balance` = total - sum of PAID payments

#### `calculateVoucherItem(voucherItem: VoucherItem, opts?: RoundingOption[]): VoucherItem`

Calculates a single voucher line item:
- `rate` = baseRate * exchangeRate
- `subTotal` = quantity * rate
- `localSubTotal` = subTotal * localExchangeRate
- `taxTotal` = subTotal * taxPercentage / 100
- `total` = subTotal + taxTotal
- Supports configurable rounding (up/down/round) at any calculation step

**RoundingOption:**
```typescript
{
  roundAt: 'baseRate' | 'rate' | 'subTotal' | 'localSubTotal' | 'taxTotal' | ... ,
  rounding?: 'up' | 'down' | 'round' | '',
  decimalPlaces?: number  // default: 2
}
```

#### `calculateDueDate(voucher: Voucher): Date`

Returns `issueDate + term` days (defaults to today if no issue date).

#### `computeTaxSummary(voucher: Voucher): { taxSummaryItems: TaxSummary[] }`

Groups non-deleted voucher items by tax UUID, aggregates `total` and `taxTotal` per tax code.

#### `sortVoucherItems(voucherItems): VoucherItem[]`

Sorts by: deleted status, job number, charge item sequence, name, code, unit, size.

### Helpers

| Helper | Purpose |
|---|---|
| `opBigNumber(fn, a, b)` | Wraps mathjs operations in bignumber conversion to avoid floating-point errors |
| `grossProfit.default` | GP = accountReceivable - accountPayable - cashBook - accrual - blankCheque (when costTotal=0 or both AR & AP > 0) |
| `grossProfit.sellMinusCostGreater` | GP = max(sellTotal, AR) - adjusted cost amount (alternate strategy) |
| `percentage.default` | Margin % = grossProfit / accountReceivable * 100 |
| `shortBill.default` | When accrual < 0: costTotal - accountPayable - accountPayableDraft |

## Key Concepts

- **CostItem**: A line item on a booking's cost sheet (e.g., freight charge, handling fee). Has both sell-side and cost-side pricing.
- **Voucher**: An invoice document (accounts payable = ACCPAY, accounts receivable = ACCREC). Contains voucher items linked to cost items.
- **Accrual**: Difference between expected cost and actual invoiced amount.
- **Short Bill**: Underbilled amount, shown when accrual is negative (cost exceeds what's been invoiced).
- **Gross Profit**: Revenue minus costs, computed from actual invoiced amounts (AR - AP).
- **Journal Voucher**: Reallocation entry linking one cost item to another.

## Development

### Build
```
npm run build
```

### Watch (rebuild on save)
```
npm run watch
```

### Test
```
npm test
```

### Publish
Important: build first before publishing.
```
npm run build
npm publish
```

## NPM Link

This project builds with Node 20, but when linking you need to match the target application's Node version.

```bash
cd formula
nvm use 12
npm link

cd ../api
npm link @shipx/formula2
```