{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImplicitParams             #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE ConstraintKinds            #-}


------------------------------------------------------------------------------
-- |
-- Module      : QuickBooks.Types
-- Description :
-- Copyright   :
-- License     :
-- Maintainer  :
-- Stability   :
-- Portability :
--
--
--
------------------------------------------------------------------------------

module QuickBooks.Types where

import           Control.Applicative ((<$>), (<*>))
import           Control.Monad       (mzero)
import           Data.Aeson          (FromJSON (..), ToJSON(..), Value (Object),
                                      (.:), object, (.=))
import           Data.Aeson.TH       (Options (fieldLabelModifier, omitNothingFields),
                                      defaultOptions, deriveJSON)
import           Data.ByteString     (ByteString)
import           Data.Char           (toLower)
import           Data.Text           (Text)
import           Data.Text.Encoding  (encodeUtf8, decodeUtf8)
import           Prelude             hiding (lines)
import qualified Text.Email.Validate as E (EmailAddress)
import           System.Log.FastLogger (LoggerSet)
import           Network.HTTP.Client   (Manager)

type Logger = LoggerSet

type CallbackURL = String

newtype OAuthVerifier = OAuthVerifier { unOAuthVerifier :: ByteString }
  deriving (Show, Eq)

-- | QuickBooks Application Keys

data AppConfig = AppConfig
  { consumerToken  :: !ByteString
  , consumerSecret :: !ByteString
  } deriving (Show, Eq)

instance FromJSON AppConfig where
  parseJSON (Object o) = AppConfig <$> (parseByteString o "consumerToken")
                                   <*> (parseByteString o "consumerSecret")
    where parseByteString obj name = encodeUtf8 <$> (obj .: name)
  parseJSON _ = mzero
  
data APIConfig = APIConfig
  { companyId      :: !ByteString
  , oauthToken     :: !ByteString
  , oauthSecret    :: !ByteString
  , hostname       :: !ByteString
  , loggingEnabled :: !ByteString
  } deriving (Show, Eq)

instance FromJSON APIConfig where
  parseJSON (Object o) = APIConfig <$> (parseByteString o "companyId")
                                   <*> (parseByteString o "oauthToken")
                                   <*> (parseByteString o "oauthSecret")
                                   <*> (parseByteString o "hostname")
                                   <*> (parseByteString o "loggingEnabled")
    where parseByteString obj name = encodeUtf8 <$> (obj .: name)
  parseJSON _ = mzero

instance ToJSON APIConfig where
  toJSON (APIConfig cId oToken oSecret hName lEnabled) = object [
                                                                  "companyId" .= (decodeUtf8 cId),
                                                                  "oauthToken" .= (decodeUtf8 oToken),
                                                                  "oauthSecret" .= (decodeUtf8 oSecret),
                                                                  "hostname" .= (decodeUtf8 hName),
                                                                  "loggingEnabled" .= (decodeUtf8 lEnabled)
                                                                ]
                                                                
  
type APIEnv = ( ?apiConfig :: APIConfig
              , AppEnv               
              , NetworkEnv              
              )

type AppEnv = ( ?appConfig :: AppConfig
              , NetworkEnv   
              )
              
type NetworkEnv = ( ?manager :: Manager
                  , ?logger  :: Logger
                  )
             
-- | A request or access OAuth token.

data OAuthToken = OAuthToken
  { token       :: ByteString
  , tokenSecret :: ByteString
  } deriving (Show, Eq)

data family QuickBooksResponse a
data instance QuickBooksResponse Invoice = QuickBooksInvoiceResponse { quickBooksResponseInvoice :: Invoice }
data instance QuickBooksResponse DeletedInvoice = QuickBooksDeletedInvoiceResponse DeletedInvoice
data instance QuickBooksResponse OAuthToken = QuickBooksAuthResponse { tokens :: OAuthToken }
data instance QuickBooksResponse () = QuickBooksVoidResponse

data instance QuickBooksResponse [Customer] =
  QuickBooksCustomerResponse { quickBooksResponseCustomer :: [Customer] }

data instance QuickBooksResponse [Item] =
  QuickBooksItemResponse { quickBooksResponseItem :: [Item] }

instance FromJSON (QuickBooksResponse Invoice) where
  parseJSON (Object o) = QuickBooksInvoiceResponse `fmap` (o .: "Invoice")
  parseJSON _          = fail "Could not parse invoice response from QuickBooks"

instance FromJSON (QuickBooksResponse DeletedInvoice) where
  parseJSON (Object o) = QuickBooksDeletedInvoiceResponse `fmap` (o .: "Invoice")
  parseJSON _          = fail "Could not parse deleted invoice response from QuickBooks"

instance FromJSON (QuickBooksResponse [Customer]) where
  parseJSON (Object o) = do
    let customers =
          o .: "QueryResponse" >>= \queryResponse -> queryResponse .: "Customer"
    fmap QuickBooksCustomerResponse customers
  parseJSON _          = fail "Could not parse customer response from QuickBooks"

instance FromJSON (QuickBooksResponse [Item]) where
  parseJSON (Object o) = do
    let items =
          o .: "QueryResponse" >>= \queryResponse -> queryResponse .: "Item"
    fmap QuickBooksItemResponse items
  parseJSON _          = fail "Could not parse item response from QuickBooks"

type QuickBooksQuery a = QuickBooksRequest (QuickBooksResponse a)
type QuickBooksOAuthQuery a = QuickBooksOAuthRequest (QuickBooksResponse a) 

data QuickBooksOAuthRequest a where
  GetTempOAuthCredentials :: CallbackURL   -> QuickBooksOAuthQuery OAuthToken
  GetAccessTokens         :: OAuthVerifier -> QuickBooksOAuthQuery OAuthToken
  DisconnectQuickBooks    :: QuickBooksOAuthQuery ()

data QuickBooksRequest a where
  CreateInvoice           :: Invoice     -> QuickBooksQuery Invoice
  ReadInvoice             :: InvoiceId   -> QuickBooksQuery Invoice
  UpdateInvoice           :: Invoice     -> QuickBooksQuery Invoice
  DeleteInvoice           :: InvoiceId   -> SyncToken -> QuickBooksQuery DeletedInvoice
  SendInvoice             :: InvoiceId   -> E.EmailAddress -> QuickBooksQuery Invoice

  QueryCustomer           :: Text -> QuickBooksQuery [Customer]
  QueryItem               :: Text -> QuickBooksQuery [Item]

newtype InvoiceId = InvoiceId {unInvoiceId :: Text}
  deriving (Show, Eq, FromJSON, ToJSON)

newtype LineId    = LineId {unLineId :: Text}
  deriving (Show, Eq, FromJSON, ToJSON)

newtype SyncToken = SyncToken { unSyncToken :: Text }
  deriving (Show, Eq, FromJSON, ToJSON)

-- | Details of a description line.

data DescriptionLineDetail = DescriptionLineDetail
  { descriptionLineDetailServiceDate :: !(Maybe Text)
  , descriptionLineDetailTaxCodeRef  :: !(Maybe TaxCodeRef)
  }
  deriving (Show, Eq)

-- | Details of a discount line.

data DiscountLineDetail = DiscountLineDetail
  { discountLineDetailDiscountRef        :: !(Maybe DiscountRef)
  , discountLineDetailPercentBased       :: !(Maybe Bool)
  , discountLineDetailDiscountPercent    :: !(Maybe Double)
  , discountLineDetailDiscountAccountRef :: !(Maybe DiscountAccountRef)
  }
  deriving (Show, Eq)

-- | Details of a sales item line.
-- In order to create a sales item line detail, use 'salesItemLineDetail'.

data SalesItemLineDetail = SalesItemLineDetail
  { salesItemLineDetailItemRef         :: !(Maybe ItemRef)
  , salesItemLineDetailClassRef        :: !(Maybe ClassRef)
  , salesItemLineDetailUnitPrice       :: !(Maybe Double)
  , salesItemLineDetailRatePercent     :: !(Maybe Double)
  , salesItemLineDetailPriceLevelRef   :: !(Maybe PriceLevelRef)
  , salesItemLineDetailMarkupInfo      :: !(Maybe Text)
  , salesItemLineDetailQty             :: !(Maybe Double)
  , salesItemLineDetailTaxCodeRef      :: !(Maybe TaxCodeRef)
  , salesItemLineDetailServiceData     :: !(Maybe Text)
  , salesItemLineDetailTaxInclusiveAmt :: !(Maybe Double)
  }
  deriving (Show, Eq)

-- | Create a sales item line detail with a reference to an item.
--
-- Example:
--
-- >>> let aSalesItemLineDetail = salesItemLineDetail ((reference "1") {referenceName = Just "Services"})
-- >>> salesItemLineDetailItemRef aSalesItemLineDetail
-- Just (Reference {referenceName = Just "Services", referenceType = Nothing, referenceValue = "1"})

salesItemLineDetail :: ItemRef -> SalesItemLineDetail
salesItemLineDetail itemRef =
  SalesItemLineDetail (Just itemRef)
                      Nothing
                      Nothing
                      Nothing
                      Nothing
                      Nothing
                      Nothing
                      Nothing
                      Nothing
                      Nothing

-- | Details of a subtotal line.

data SubTotalLineDetail = SubTotalLineDetail
  { subtotalLineDetailItemRef :: !(Maybe ItemRef) }
  deriving (Show, Eq)

-- | An individual line item of a transaction.

data Line = Line
  { lineId                    :: !(Maybe LineId)
  , lineLineNum               :: !(Maybe Double)
  , lineDescription           :: !(Maybe Text)
  , lineAmount                :: !(Maybe Double)
  , lineLinkedTxn             :: !(Maybe [LinkedTxn])
  , lineDetailType            :: !Text
  , lineDescriptionLineDetail :: !(Maybe DescriptionLineDetail)
  , lineDiscountLineDetail    :: !(Maybe DiscountLineDetail)
  , lineSalesItemLineDetail   :: !(Maybe SalesItemLineDetail)
  , lineSubTotalLineDetail    :: !(Maybe SubTotalLineDetail)
  , lineCustomField           :: !(Maybe [CustomField])
  }
  deriving (Show, Eq)

-- | Create a sales item line with amount and details.
--
-- Example:
--
-- >>> let aSalesItemLineDetail = salesItemLineDetail ((reference "1") {referenceName = Just "Services"})
-- >>> let aSalesItemLine = salesItemLine 100.0 aSalesItemLineDetail

salesItemLine :: Double
              -> SalesItemLineDetail
              -> Line
salesItemLine amount detail =
  Line Nothing
       Nothing
       Nothing
       (Just amount)
       Nothing
       "SalesItemLineDetail"
       Nothing
       Nothing
       (Just detail)
       Nothing
       Nothing
-- | if you are using a salesItemLineDetail that has a Qty and a price it generates amount automatically
emptySalesItemLine :: Line
emptySalesItemLine =
  Line Nothing
       Nothing
       Nothing
       Nothing
       Nothing
       "SalesItemLineDetail"
       Nothing
       Nothing
       Nothing
       Nothing
       Nothing


newtype DeletedInvoiceId = DeletedInvoiceId { unDeletedInvoiceId :: Text }
  deriving (Show, Eq, FromJSON, ToJSON)

data DeletedInvoice = DeletedInvoice
  { deletedInvoiceId     :: !DeletedInvoiceId
  , deletedInvoicedomain :: !Text
  , deletedInvoicestatus :: !Text
  } deriving (Show, Eq)

-- | A reference.
-- In order to create a reference, use 'reference'.

data Reference = Reference
  { referenceName  :: !(Maybe Text)
  , referenceType  :: !(Maybe Text)
  , referenceValue :: !Text
  }
  deriving (Show, Eq)

-- | Create a reference with a value.
--
-- Example:
--
-- >>> reference "21"
-- Reference {referenceName = Nothing, referenceType = Nothing, referenceValue = "21"}

reference :: Text -> Reference
reference = Reference Nothing Nothing

type ClassRef            = Reference
type CurrencyRef         = Reference

-- | A reference to a customer or a job.
--
-- Example:
--
-- >>> (reference "21") {referenceName = Just "John Doe"} :: CustomerRef
-- Reference {referenceName = Just "John Doe", referenceType = Nothing, referenceValue = "21"}

type CustomerRef         = Reference

type DepartmentRef       = Reference
type DepositToAccountRef = Reference
type DiscountAccountRef  = Reference
type DiscountRef         = Reference
type ItemRef             = Reference
type PriceLevelRef       = Reference
type SalesTermRef        = Reference
type ShipMethodRef       = Reference
type TaxCodeRef          = Reference
type TxnTaxCodeRef       = Reference

data ModificationMetaData = ModificationMetaData
  { modificationMetaDataCreateTime      :: !Text
  , modificationMetaDataLastUpdatedTime :: !Text
  }
  deriving (Show, Eq)

data TelephoneNumber = TelephoneNumber
  { telephoneNumberFreeFormNumber :: !Text
  }
  deriving (Eq, Show)

data WebSiteAddress = WebAddress
  { webSiteAddressURI :: !Text
  }
  deriving (Eq, Show)

data PhysicalAddress = PhysicalAddress
  { physicalAddressId                     :: !Text
  , physicalAddressLine1                  :: !Text
  , physicalAddressLine2                  :: !(Maybe Text)
  , physicalAddressLine3                  :: !(Maybe Text)
  , physicalAddressLine4                  :: !(Maybe Text)
  , physicalAddressLine5                  :: !(Maybe Text)
  , physicalAddressCity                   :: !Text
  , physicalAddressCountry                :: !(Maybe Text)
  , physicalAddressCountrySubDivisionCode :: !Text
  , physicalAddressPostalCode             :: !Text
  , physicalAddressNote                   :: !(Maybe Text)
  , physicalAddressLat                    :: !(Maybe Text)
  , physicalAddressLong                   :: !(Maybe Text)
  }
  deriving (Show, Eq)

type BillAddr = PhysicalAddress
type ShipAddr = PhysicalAddress

data EmailAddress = EmailAddress
  { emailAddress :: !Text
  }
  deriving (Show, Eq)

data TxnTaxDetail = TxnTaxDetail
  { txnTaxDetailTxnTaxCodeRef :: !(Maybe TxnTaxCodeRef)
  , txnTaxDetailTotalTax      :: !Double
  , txnTaxDetailTaxLine       :: !(Maybe Line)
  }
  deriving (Show, Eq)

data DeliveryInfo = DeliveryInfo
  { deliveryInfoDeliveryType :: !(Maybe Text)
  , deliveryInfoDeliveryTime :: !(Maybe Text)
  }
  deriving (Show, Eq)

data LinkedTxn = LinkedTxn
  { linkedTxnId     :: !(Maybe Text)
  , linkedTxnType   :: !(Maybe Text)
  , linkedTxnLineId :: !(Maybe Text)
  }
  deriving (Show, Eq)

data CustomField = CustomField
  { customFieldDefinitionId :: !Text
  , customFieldName         :: !Text
  , customFieldType         :: !CustomFieldType
  , customFieldStringValue  :: !(Maybe Text)
  , customFieldBooleanValue :: !(Maybe Bool)
  , customFieldDateValue    :: !(Maybe Text)
  , customFieldNumberValue  :: !(Maybe Double)
  }
  deriving (Show, Eq)

data CustomFieldType
  = BooleanType
  | DateType
  | NumberType
  | StringType
  deriving (Show, Eq)

data GlobalTaxModel
  = NotApplicable
  | TaxExcluded
  | TaxInclusive
  deriving (Show, Eq)

-- | An invoice transaction entity, that is, a sales form where the customer
-- pays for a product or service later.
--
-- Business rules:
--
--   * An invoice must have at least one 'Line' that describes an item.
--   * An invoice must have a 'CustomerRef'.
--
-- In order to create an invoice, use 'defaultInvoice'.

data Invoice = Invoice
  { invoiceId                    :: !(Maybe InvoiceId)
  , invoiceSyncToken             :: !(Maybe SyncToken)
  , invoiceMetaData              :: !(Maybe ModificationMetaData)
  , invoiceCustomField           :: !(Maybe [CustomField])
  , invoiceDocNumber             :: !(Maybe Text)
  , invoiceTxnDate               :: !(Maybe Text)
  , invoiceDepartmentRef         :: !(Maybe DepartmentRef)
  , invoiceCurrencyRef           :: !(Maybe CurrencyRef) -- Non-US
  , invoiceExchangeRate          :: !(Maybe Double) -- Non-US
  , invoicePrivateNote           :: !(Maybe Text)
  , invoiceLinkedTxn             :: !(Maybe [LinkedTxn])
  , invoiceLine                  :: ![Line]
  , invoiceTxnTaxDetail          :: !(Maybe TxnTaxDetail)
  , invoiceCustomerRef           :: !CustomerRef
  , invoiceCustomerMemo          :: !(Maybe Text)
  , invoiceBillAddr              :: !(Maybe BillAddr)
  , invoiceShipAddr              :: !(Maybe ShipAddr)
  , invoiceClassRef              :: !(Maybe ClassRef)
  , invoiceSalesTermRef          :: !(Maybe SalesTermRef)
  , invoiceDueDate               :: !(Maybe Text)
  , invoiceGlobalTaxCalculation  :: !(Maybe GlobalTaxModel) -- Non-US
  , invoiceShipMethodRef         :: !(Maybe ShipMethodRef)
  , invoiceShipDate              :: !(Maybe Text)
  , invoiceTrackingNum           :: !(Maybe Text)
  , invoiceTotalAmt              :: !(Maybe Double)
  , invoiceHomeTotalAmt          :: !(Maybe Double) -- Non-US
  , invoiceApplyTaxAfterDiscount :: !(Maybe Bool)
  , invoicePrintStatus           :: !(Maybe Text)
  , invoiceEmailStatus           :: !(Maybe Text)
  , invoiceBillEmail             :: !(Maybe EmailAddress)
  , invoiceDeliveryInfo          :: !(Maybe DeliveryInfo)
  , invoiceBalance               :: !(Maybe Double)
  , invoiceDepositToAccountRef   :: !(Maybe DepositToAccountRef)
  , invoiceDeposit               :: !(Maybe Double)

  , invoiceAllowIPNPayment       :: !(Maybe Bool)
  , invoiceDomain                :: !(Maybe Text)
  , invoiceSparse                :: !(Maybe Bool)
  }
  deriving (Show, Eq)

-- | Create an 'Invoice' with the minimum elements.
--
-- Example:
--
-- >>> let customer21 = reference "21" :: CustomerRef
-- >>> let aSalesItemLineDetail = salesItemLineDetail ((reference "1") {referenceName = Just "Services"})
-- >>> let aSalesItemLine = salesItemLine 100.0 aSalesItemLineDetail
--
-- >>> let anInvoice = defaultInvoice [aSalesItemLine] customer21

defaultInvoice :: [Line]      -- ^ The line items of a transaction
               -> CustomerRef -- ^ Reference to a customer or a job
               -> Invoice
defaultInvoice [] _ = error "Bad invoice"
defaultInvoice lines customerRef =
  Invoice Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          lines
          Nothing
          customerRef
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing
          Nothing

data InvoiceResponse = InvoiceResponse
  { invoiceResponseInvoice :: Invoice }
  deriving (Show, Eq)

data CustomerResponse = CustomerResponse
  { customerResponseCustomer :: !Customer
  }
  deriving (Eq, Show)

data Customer = Customer
  { customerId                      :: !(Maybe Text)
  , customerSyncToken               :: !(Maybe SyncToken)
  , customerMetaData                :: !(Maybe ModificationMetaData)
  , customerTitle                   :: !(Maybe Text) -- def null
  , customerGivenName               :: !(Maybe Text) -- max 25 def null
  , customerMiddleName              :: !(Maybe Text) -- max 25, def null
  , customerFamilyName              :: !(Maybe Text) -- max 25, def null
  , customerSuffix                  :: !(Maybe Text) -- max 10, def null
  , customerFullyQualifiedName      :: !(Maybe Text)
  , customerCompanyName             :: !(Maybe Text) -- max 50, def null
  , customerDisplayName             :: !Text -- unique
  , customerPrintOnCheckName        :: !(Maybe Text) -- max 100
  , customerActive                  :: !(Maybe Bool) -- def true
  , customerPrimaryPhone            :: !(Maybe TelephoneNumber)
  , customerAlternatePhone          :: !(Maybe TelephoneNumber)
  , customerMobile                  :: !(Maybe TelephoneNumber)
  , customerFax                     :: !(Maybe TelephoneNumber)
  , customerPrimaryEmailAddress     :: !(Maybe EmailAddress)
  , customerWebAddr                 :: !(Maybe WebSiteAddress)
  , customerDefaultTaxCodeRef       :: !(Maybe TaxCodeRef)
  , customerTaxable                 :: !(Maybe Bool)
  , customerBillAddr                :: !(Maybe BillAddr)
  , customerShipAddr                :: !(Maybe ShipAddr)
  , customerNotes                   :: !(Maybe Text) -- max 2000
  , customerJob                     :: !(Maybe Bool) -- def false or null
  , customerBillWithParent          :: !(Maybe Bool) -- def false or null
  , customerParentRef               :: !(Maybe CustomerRef)
  , customerLevel                   :: !(Maybe Int) -- def 0, up to 5
  , customerSalesTermRef            :: !(Maybe SalesTermRef)
  , customerPaymentMethodRef        :: !(Maybe Reference)
  , customerBalance                 :: !(Maybe Double)
  , customerOpenBalanceDate         :: !(Maybe Text)
  , customerBalanceWithJobs         :: !(Maybe Double)
  , customerCurrencyRef             :: !(Maybe CurrencyRef)
  , customerPreferredDeliveryMethod :: !(Maybe Text)
  , customerResaleNum               :: !(Maybe Text) -- max 15
  }
  deriving (Eq, Show)

data ItemResponse = ItemResponse
  { itemResponseItem :: !Item
  }
  deriving (Eq, Show)

data Item = Item
  { itemId                   :: !(Maybe Text)
  , itemSyncToken            :: !(Maybe SyncToken)
  , itemMetaData             :: !(Maybe ModificationMetaData)
  , itemName                 :: !Text -- max 100
  , itemDescription          :: !(Maybe Text) -- max 4000
  , itemActive               :: !(Maybe Bool) -- def true
  , itemSubItem              :: !(Maybe Bool) -- def false or null
  , itemParentRef            :: !(Maybe ItemRef) -- def null
  , itemLevel                :: !(Maybe Int) -- def 0 up to 5
  , itemFullyQualifiedName   :: !(Maybe String) -- def null
  , itemTaxable              :: !(Maybe Bool) -- US only
  , itemSalesTaxInclusive    :: !(Maybe Bool) -- def false
  , itemUnitPrice            :: !(Maybe Double) -- max 99999999999, def 0
  , itemType                 :: !(Maybe Text) -- def Inventory (Inventory/Service)
  , itemIncomeAccountRef     :: !(Maybe Reference) -- required
  , itemPurchaseDesc         :: !(Maybe String) -- max 1000
  , itemPurchaseTaxInclusive :: !(Maybe Bool) -- def false
  , itemPurchaseCost         :: !(Maybe Double) -- max 99999999999
  , itemExpenseAccountRef    :: !(Maybe Reference) -- required
  , itemAssetAccountRef      :: !(Maybe Reference) -- req for inventory items
  , itemTrackQtyOnHand       :: !(Maybe Bool) -- def false
  , itemQtyOnHand            :: !(Maybe Double) -- req for inventory items
  , itemSalesTaxCodeRef      :: !(Maybe Reference)
  , itemPurchaseTaxCodeRef   :: !(Maybe Reference)
  , itemInvStartDate         :: !(Maybe Text) -- required for inventory items
  }
  deriving (Eq, Show)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 8
               , omitNothingFields  = True
               }
             ''Customer)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 16
               }
             ''CustomerResponse)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 4
               , omitNothingFields  = True
               }
             ''Item)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 12
               }
             ''ItemResponse)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 15
               }
             ''TelephoneNumber)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 14
               }
             ''WebSiteAddress)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 11
               , omitNothingFields  = True }
             ''CustomField)

$(deriveJSON defaultOptions
             ''CustomFieldType)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 12 }
             ''DeliveryInfo)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 21
               , omitNothingFields  = True }
             ''DescriptionLineDetail)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 18
               , omitNothingFields  = True }
             ''DiscountLineDetail)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 5 }
             ''EmailAddress)

$(deriveJSON defaultOptions
             ''GlobalTaxModel)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 7
               , omitNothingFields  = True }
             ''Invoice)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 15 }
             ''InvoiceResponse)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 4 }
             ''Line)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 6
               , omitNothingFields  = True }
             ''LinkedTxn)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 20 }
             ''ModificationMetaData)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 15 }
             ''PhysicalAddress)

$(deriveJSON defaultOptions
               { fieldLabelModifier = map toLower . drop 9
               , omitNothingFields  = True }
             ''Reference)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 19
               , omitNothingFields  = True }
             ''SalesItemLineDetail)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 18
               , omitNothingFields  = True }
             ''SubTotalLineDetail)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop 12 }
             ''TxnTaxDetail)

$(deriveJSON defaultOptions
               { fieldLabelModifier = drop (length ("deletedInvoice" :: String)) }
             ''DeletedInvoice)