README.md in netsuite_rails-0.2.2 vs README.md in netsuite_rails-0.3.1
- old
+ new
@@ -1,62 +1,238 @@
-[![Build Status](https://travis-ci.org/NetSweet/netsuite_rails.svg?branch=master)](https://travis-ci.org/NetSweet/netsuite_rails)
+[![Circle CI](https://circleci.com/gh/NetSweet/netsuite_rails.svg?style=svg)](https://circleci.com/gh/NetSweet/netsuite_rails)
+[![Slack Status](https://opensuite-slackin.herokuapp.com/badge.svg)](http://opensuite-slackin.herokuapp.com)
# NetSuite Rails
-**Note:** Documentation is horrible... look at the code for details.
+**<span style="color: red">Note:</span>** Documentation is horrible: PRs welcome. Look at the code for details.
-Build custom Ruby on Rails applications that sync to NetSuite.
+Build Ruby on Rails applications that sync ActiveRecord (ActiveModel and plain old ruby objects too) in real-time to NetSuite. Here's an example:
+```ruby
+class Item < ActiveRecord::Base
+ include NetSuiteRails::RecordSync
+
+ # specify the NS record that your rails model maps to
+ netsuite_record_class NetSuite::Records::InventoryItem
+
+ netsuite_sync :read_write,
+ # specify the frequency that your app should poll NetSuite for updates
+ frequency: 1.day,
+ # it's possible to base syncing off of a saved search. Be sure that "Internal ID" is one of your search result columns
+ saved_search_id: 123,
+ # limit pushing to NetSuite based on conditional
+ if: -> { self.a_condition? },
+ # limit pulling from NetSuite based on conditional. This is only
+ # considered when handling a single pull
+ pull_if: -> { self.another_condition? },
+
+ # accepted values are :async and :sync. Default is :async
+ mode: :sync
+
+
+ # local => remote field mapping
+ netsuite_field_map({
+ :item_number => :item_id,
+ :name => :display_name,
+
+ # the corresponding NetSuite field must be manually specified in before_netsuite_push
+ :user => Proc.new do |local_rails_record, netsuite_record, sync_direction|
+ if direction == :pull
+
+ elsif direction == :push
+
+ end
+ end,
+
+ :custom_field_list => {
+ :a_local_field => :custrecord_remote_field
+ :a_special_local_field => Proc.new do |local, ns_record, direction|
+ if direction == :push
+ # if proc is used with a field mapping, the field must be specified in `netsuite_manual_fields`
+ ns_record.custom_field_list.custentity_special_long = 1
+ ns_record.custom_field_list.custentity_special_long.type = 'platformCore:LongCustomFieldRef'
+ end
+ end
+ }
+ })
+
+ # sanitizes input from rails to ensure NS doesn't throw a fatal error
+ netsuite_field_hints({
+ :phone => :phone,
+ :email => :email
+ })
+
+ before_netsuite_push do |netsuite_record|
+ self.netsuite_manual_fields = [ :entity, :custom_field_list ]
+ end
+end
+```
+
+Your ruby model:
+
+* Needs to have a `netsuite_id` and `netsuite_id=` method
+* Does not need to be an `ActiveRecord` model. If you don't use ActiveRecord it is your responsibility
+ to trigger `Model#netsuite_push`.
+
+Notes:
+
+* If `sync_mode == :async` `model.save` will be run if a record is created referencing an existing NetSuite object: `model.create! netsuite_id: 123`
+* If you are using `update`, a `update` call will not be run if no changed fields are detected. If you are manually using fields specify them with `netsuite_manual_fields`
+
+## Using Upsert
+
+TODO generating external ID tag
+
+TODO configuring upsert
+
+TODO add vs upsert consideration
+
## Installation
```ruby
gem 'netsuite_rails'
```
-Install the database migration for poll timestamps
+Install the database migration to persist poll timestamps:
```bash
rails g netsuite_rails:install
```
-### Date & Time
+This helps netsuite_rails to know when the last time your rails DB was synced with the NS.
+## Date
+
+
+## Time
+
+"Time of Day" fields in NetSuite are especially tricky. To ensure that times don't shift when you push them to NetSuite here are some tips:
+
+1. Take a look at the company time zone setup. This is in Setup
+2. Ensure your WebService's Employee record has either:
+ * No time zone set
+ * The same time zone as the company
+3. Ensure that the WebService's GUI preferences have the same time zone settings as the company. This effects how times are translated via SuiteTalk.
+4. Set the `netsuite_instance_time_zone_offset` setting to your company's time zone
+
```ruby
# set your timezone offset
NetSuiteRails::Configuration.netsuite_instance_time_zone_offset(-6)
```
+### Changing WebService User's TimeZone Preferences
+
+It might take a couple hours for time zone changes to take effect. [From my experience](http://mikebian.co/netsuite-suitetalk-user-role-edits-are-delayed/), either the time zone changes have some delay associated with them or the time zone implementation is extremely buggy.
+
## Usage
-modes: :read, :read_write, :aggressive
+### Syncing Options
+```
+netsuite_record_class NetSuite::Records::Customer
+netsuite_record_class NetSuite::Records::CustomRecord, 123
+
+netsuite_sync: :read
+netsuite_sync: :read_write
+# TODO not after_netsuite_push replacement for aggressive sync
+
+netsuite_sync: :read, frequency: :never
+netsuite_sync: :read, frequency: 5.minutes
+netsuite_sync: :read, if: -> { self.condition_met? }
+
+```
+
When using a proc in a NS mapping, you are responsible for setting local and remote values
+The default sync frequency is [one day](https://github.com/NetSweet/netsuite_rails/blob/c453326a4190e68a2fd9d7690b2b1f2f105ec8b9/lib/netsuite_rails/poll_trigger.rb#L27).
+
for pushing tasks to DJ https://github.com/collectiveidea/delayed_job/wiki/Rake-Task-as-a-Delayed-Job
`:if` for controlling when syncing occurs
-TODO hooks for before/after push/pull
+Easily disable/enable syncing via env vars:
-### Syncing
+```ruby
+NetSuiteRails.configure do
+ netsuite_pull_disabled ENV['NETSUITE_PULL_DISABLED'].present? && ENV['NETSUITE_PULL_DISABLED'] == "true"
+ netsuite_push_disabled ENV['NETSUITE_PUSH_DISABLED'].present? && ENV['NETSUITE_PUSH_DISABLED'] == "true"
+ if ENV['NETSUITE_DISABLE_SYNC'].present? && ENV['NETSUITE_DISABLE_SYNC'] == "true"
+ netsuite_pull_disabled true
+ netsuite_push_disabled true
+ end
+end
+
+```
+
+### Hooks
+
+```ruby
+# the netsuite record is passed a single argument to this block (or method reference)
+# this provides the opportunity to set custom fields or run custom logic to prepare
+# the record for the NetSuite envoirnment
+before_netsuite_push
+after_netsuite_push
+
+# netsuite_pulling? is true when this callback is executed
+after_netsuite_pull
+```
+
+### Rake Tasks for Syncing
+
```bash
+# update & create local records modified in netsuite sync the last sync time
rake netsuite:sync
+# pull all records in NetSuite and update/create local records
rake netsuite:fresh_sync
+
+# only update records that have already been synced
+rake netsuite:sync_local RECORD_MODELS=YourModel LIST_MODELS=YourListModel
```
Caveats:
* If you have date time fields, or custom fields that will trigger `changed_attributes` this might cause issues when pulling an existing record
-* `changed_attributes` doesn't work well with store
+* `changed_attributes` doesn't work well with `store`s
+### Delayed Job
+
+The more records that use netsuite_rails, the longer you'll need your job timeout to be:
+
+```ruby
+# config/initializers/delayed_job.rb
+Delayed::Worker.max_run_time = 80.minutes
+```
+
+## Non-AR Backed Model
+
+Implement `changed_attributes` in your non-AR backed model
+
## Testing
```ruby
# in spec_helper.rb
require 'netsuite_rails/spec/spec_helper'
```
+# Syncing Using Rake Tasks
+
+```ruby
+# clockwork.rb
+every(1.minutes, 'netsuite sync') {
+ # prevent multiple netsuite:sync DJ commands from being added; only one is needed in the queue at a time
+ unless Delayed::Job.where(failed_at: nil, locked_by: nil).detect { |j| j.payload_object.class == DelayedRake && j.payload_object.task == 'netsuite:sync'}
+ Delayed::Job.enqueue DelayedRake.new("netsuite:sync")
+ end
+}
+
+# schedule.rb
+# DelayedRake: https://github.com/collectiveidea/delayed_job/wiki/Rake-Task-as-a-Delayed-Job
+every 2.minutes do
+ runner 'Delayed::Job.enqueue(DelayedRake.new("netsuite:sync"),priority:1,run_at: Time.now);'
+end
+```
+
## Author
-* Michael Bianco @iloveitaly
\ No newline at end of file
+* Michael Bianco @iloveitaly