# Yii2 NetSuite Extension

A comprehensive NetSuite API integration extension for Yii2 applications, providing easy access to NetSuite's REST API functionality.

## Features

- **OAuth 2.0 JWT Authentication**: Modern, secure authentication with NetSuite using OAuth 2.0 and JWT
- **Record Management**: CRUD operations for common NetSuite records (Customers, Items, Sales Orders, etc.)
- **Advanced Search**: Powerful search capabilities with query builder
- **SuiteQL Support**: Execute custom SuiteQL queries
- **Yii2 Component**: Easy integration with Yii2 applications
- **Model Classes**: Pre-built model classes for common NetSuite records
- **Error Handling**: Comprehensive error handling and logging
- **Token Management**: Automatic access token refresh and caching

## Installation

### Via Composer from Azure Repos (Recommended)

Add the Azure Repos repository to your `composer.json` and install the extension:

```json
{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://dev.azure.com/your-org/your-project/_git/yii2-netsuite"
        }
    ],
    "require": {
        "portalpeople/yii2-netsuite": "^1.0"
    }
}
```

Then install via Composer:

```bash
composer install
```

### Via Composer from Public Repository

If published to a public repository (Packagist):

```bash
composer require portalpeople/yii2-netsuite
```

### Via Azure DevOps Artifacts (Private Package Feed)

1. **Configure Azure Artifacts Feed**:
   ```bash
   # Add Azure Artifacts repository
   composer config repositories.azure-artifacts composer https://pkgs.dev.azure.com/your-org/your-project/_packaging/your-feed/composer/index.json
   
   # Set authentication (using Personal Access Token)
   composer config http-basic.pkgs.dev.azure.com your-username your-pat-token
   ```

2. **Install the package**:
   ```bash
   composer require portalpeople/yii2-netsuite
   ```

### Manual Installation

1. Clone from Azure Repos:
   ```bash
   git clone https://dev.azure.com/your-org/your-project/_git/yii2-netsuite modules/netsuite
   ```

2. Add the extension to your `composer.json` autoload section:
   ```json
   {
       "autoload": {
           "psr-4": {
               "PortalPeople\\netsuite\\": "modules/netsuite/src/"
           }
       }
   }
   ```

3. Run `composer dump-autoload` to regenerate the autoloader

## Configuration

### 1. NetSuite Setup

Before using this extension, you need to set up OAuth 2.0 JWT authentication in NetSuite:

1. **Create Integration Record**:
   - Go to Setup > Integrations > Manage Integrations
   - Click "New" to create a new integration
   - Fill in the required information
   - Check "OAuth 2.0" authentication type
   - Note down the Client ID and Client Secret

2. **Generate Certificate/Key Pair**:
   ```bash
   # Generate private key
   openssl ecparam -name prime256v1 -genkey -noout -out private.pem
   
   # Generate public key
   openssl ec -in private.pem -pubout -out public.pem
   ```

3. **Upload Public Key to NetSuite**:
   - In your integration record, upload the public key (public.pem)
   - Note down the Certificate ID (Key ID) that NetSuite generates

4. **Create User for Impersonation**:
   - Ensure you have a NetSuite user that the integration will impersonate
   - Note down the user's internal ID

5. **Place Private Key**:
   - Copy the private key file to your application's secure directory
   - Example: `modules/netsuite/config/private.pem`
   - Ensure the file is not web-accessible

### 2. Yii2 Application Configuration

Add the NetSuite component to your application configuration:

```php
// config/main.php or config/main-local.php
return [
    'components' => [
        'netsuite' => [
            'class' => 'PortalPeople\netsuite\components\NetSuiteComponent',
            'accountId' => 'YOUR_ACCOUNT_ID',
            'clientId' => 'YOUR_CLIENT_ID',
            'clientSecret' => 'YOUR_CLIENT_SECRET',
            'certificateId' => 'YOUR_CERTIFICATE_ID', // Key ID from NetSuite
            'privateKeyPath' => 'modules/netsuite/config/private.pem', // Path to private key file
            'userId' => 'YOUR_USER_INTERNAL_ID', // NetSuite user ID for impersonation
            // Optional: Custom base URL (will be auto-generated if not provided)
            // 'baseUrl' => 'https://YOUR_ACCOUNT.suitetalk.api.netsuite.com/services/rest',
        ],
    ],
];
```

**Security Note**: Store sensitive configuration in environment variables:

```php
// config/main-local.php
return [
    'components' => [
        'netsuite' => [
            'class' => 'PortalPeople\netsuite\components\NetSuiteComponent',
            'accountId' => $_ENV['NETSUITE_ACCOUNT_ID'],
            'clientId' => $_ENV['NETSUITE_CLIENT_ID'],
            'clientSecret' => $_ENV['NETSUITE_CLIENT_SECRET'],
            'certificateId' => $_ENV['NETSUITE_CERTIFICATE_ID'],
            'privateKeyPath' => $_ENV['NETSUITE_PRIVATE_KEY_PATH'],
            'userId' => $_ENV['NETSUITE_USER_ID'],
        ],
    ],
];
```

## Usage Examples

### Basic Operations

#### Create a Customer

```php
use PortalPeople\netsuite\models\Customer;

// Create a new customer using the model
$customer = new Customer();
$customer->setEntityId('CUST001');
$customer->setCompanyName('Example Company');
$customer->setEmail('contact@example.com');
$customer->setPhone('123-456-7890');

// Save to NetSuite
$response = Yii::$app->netsuite->createRecord(
    Customer::getRecordType(),
    $customer->toArray()
);

// Or create directly via component
$customerData = [
    'entityId' => 'CUST002',
    'companyName' => 'Another Company',
    'email' => 'info@anothercompany.com',
    'subsidiary' => ['internalId' => 1]
];

$response = Yii::$app->netsuite->createRecord('customer', $customerData);
```

#### Retrieve a Record

```php
// Get customer by internal ID
$customer = Yii::$app->netsuite->getRecord('customer', 123);

// Get item by internal ID
$item = Yii::$app->netsuite->getRecord('inventoryitem', 456);
```

#### Update a Record

```php
// Update customer information
$updateData = [
    'companyName' => 'Updated Company Name',
    'email' => 'newemail@example.com'
];

$response = Yii::$app->netsuite->updateRecord('customer', 123, $updateData);
```

#### Delete a Record

```php
// Delete a customer
$response = Yii::$app->netsuite->deleteRecord('customer', 123);
```

### Search Operations

#### Simple Search

```php
// Search for customers by company name
$criteria = [
    'field' => 'companyName',
    'operator' => 'contains',
    'value' => 'Example'
];

$results = Yii::$app->netsuite->searchRecords('customer', [$criteria]);
```

#### Advanced Search with Search Builder

```php
use PortalPeople\netsuite\components\SearchBuilder;

// Create a search builder
$search = new SearchBuilder();

// Build complex search criteria
$search->whereContains('companyName', 'Tech')
       ->whereIsNot('entityStatus', '7') // Not inactive
       ->whereOnOrAfter('dateCreated', '2023-01-01')
       ->select(['internalId', 'entityId', 'companyName', 'email'])
       ->orderBy('companyName', 'ASC')
       ->limit(50);

// Execute search
$criteria = $search->build();
$options = $search->getOptions();
$results = Yii::$app->netsuite->searchRecords('customer', $criteria, $options);
```

#### SuiteQL Queries

```php
use PortalPeople\netsuite\components\SearchBuilder;

// Build SuiteQL query
$search = new SearchBuilder();
$search->select(['id', 'entityid', 'companyname'])
       ->whereContains('companyname', 'Tech')
       ->limit(25);

$suiteQLQuery = $search->buildSuiteQL('customer');
$results = Yii::$app->netsuite->executeQuery($suiteQLQuery);

// Or execute custom SuiteQL directly
$customQuery = "SELECT id, entityid, companyname FROM customer WHERE companyname LIKE '%Tech%'";
$results = Yii::$app->netsuite->executeQuery($customQuery);
```

### Working with Sales Orders

```php
use PortalPeople\netsuite\models\SalesOrder;

// Create a sales order
$salesOrder = new SalesOrder();
$salesOrder->setEntity(123); // Customer internal ID
$salesOrder->setTranDate('2023-12-01');
$salesOrder->setMemo('Test order from API');

// Add items to the order
$salesOrder->addItem(456, 2, 100.00); // Item ID, Quantity, Rate
$salesOrder->addItem(789, 1, 250.00);

// Create the sales order
$response = Yii::$app->netsuite->createRecord(
    SalesOrder::getRecordType(),
    $salesOrder->toArray()
);
```

### Working with Items

```php
use PortalPeople\netsuite\models\Item;

// Create an inventory item
$item = new Item();
$item->setItemId('PROD001');
$item->setDisplayName('Example Product');
$item->setDescription('This is an example product');
$item->setBasePrice(99.99);
$item->setCostEstimate(50.00);
$item->setSubsidiary(1);

$response = Yii::$app->netsuite->createRecord(
    Item::getRecordType(),
    $item->toArray()
);
```

### Error Handling

```php
try {
    $customer = Yii::$app->netsuite->getRecord('customer', 123);
} catch (\Exception $e) {
    Yii::error('NetSuite API Error: ' . $e->getMessage());
    // Handle the error appropriately
}
```

### Testing Connection

```php
// Test if NetSuite connection is working
if (Yii::$app->netsuite->testConnection()) {
    echo "NetSuite connection successful!";
} else {
    echo "NetSuite connection failed!";
}
```

## Available Models

The extension includes pre-built model classes for common NetSuite records:

- **Customer**: Customer record operations
- **Item**: Inventory item operations  
- **SalesOrder**: Sales order operations
- **NetSuiteRecord**: Base class for all records

You can extend these models or create new ones by extending the `NetSuiteRecord` base class.

## Search Operators

The SearchBuilder supports the following operators:

- `is` / `whereIs()`: Exact match
- `isnot` / `whereIsNot()`: Not equal
- `contains` / `whereContains()`: Contains text
- `doesnotcontain` / `whereDoesNotContain()`: Does not contain text
- `startswith` / `whereStartsWith()`: Starts with text
- `greaterthan` / `whereGreaterThan()`: Greater than
- `lessthan` / `whereLessThan()`: Less than
- `onorafter` / `whereOnOrAfter()`: On or after date
- `onorbefore` / `whereOnOrBefore()`: On or before date
- `isempty` / `whereIsEmpty()`: Field is empty
- `isnotempty` / `whereIsNotEmpty()`: Field is not empty

## Configuration Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `accountId` | string | Yes | Your NetSuite account ID |
| `clientId` | string | Yes | OAuth 2.0 client ID from integration record |
| `clientSecret` | string | Yes | OAuth 2.0 client secret from integration record |
| `certificateId` | string | Yes | Certificate ID (Key ID) from NetSuite public key upload |
| `privateKeyPath` | string | Yes | Path to private key file for JWT signing |
| `userId` | string | Yes | NetSuite user internal ID for impersonation |
| `baseUrl` | string | No | Custom API base URL (auto-generated if not provided) |

## Requirements

- PHP >= 7.4
- Yii2 >= 2.0.0
- GuzzleHTTP >= 7.0
- Firebase JWT >= 6.0
- NetSuite account with REST API access
- Valid OAuth 2.0 credentials and certificate for NetSuite
- OpenSSL extension for JWT signing

## License

This extension is released under the MIT License. See LICENSE.md for details.

## Support

For issues, questions, or contributions, please use the project's issue tracker or contact the development team.

## Contributing

1. Fork the repository on Azure DevOps
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

For detailed setup instructions for Azure DevOps, see [docs/AZURE_SETUP.md](docs/AZURE_SETUP.md).

## Azure DevOps Integration

This extension is designed to work seamlessly with Azure DevOps:

- **Azure Repos**: Source code management with Git
- **Azure Artifacts**: Private package hosting and distribution
- **Azure Pipelines**: Automated CI/CD with multi-PHP testing
- **Azure Boards**: Issue tracking and project management

### Quick Setup for Azure DevOps

1. **Push to Azure Repos**:
   ```bash
   git remote add origin https://dev.azure.com/your-org/your-project/_git/yii2-netsuite
   git push -u origin main
   ```

2. **Configure Azure Artifacts**:
   ```bash
   composer config repositories.azure-artifacts composer https://pkgs.dev.azure.com/your-org/your-project/_packaging/composer-packages/composer/index.json
   composer config http-basic.pkgs.dev.azure.com your-username your-pat
   ```

3. **Install in projects**:
   ```bash
   composer require portalpeople/yii2-netsuite
   ```

For complete setup instructions, see [docs/AZURE_SETUP.md](docs/AZURE_SETUP.md).

## Changelog

### Version 1.0.0
- Initial release with OAuth 2.0 JWT authentication
- Basic CRUD operations for NetSuite records
- Advanced search functionality with SearchBuilder
- Model classes for common records (Customer, Item, SalesOrder)
- SuiteQL support for custom queries
- Automatic access token management and refresh
- ES256 JWT signing with certificate-based authentication
- Comprehensive error handling and logging