diff --git a/lib/exquickbooks/api/sales_receipt.ex b/lib/exquickbooks/api/sales_receipt.ex new file mode 100644 index 0000000..0997d90 --- /dev/null +++ b/lib/exquickbooks/api/sales_receipt.ex @@ -0,0 +1,52 @@ +defmodule ExQuickBooks.API.SalesReceipt do + @moduledoc """ + Functions for interacting with the SalesReceipt API. + + This module directly implements operations from the official API: + + """ + + use ExQuickBooks.Endpoint, base_url: ExQuickBooks.accounting_api + use ExQuickBooks.Endpoint.JSON + + alias ExQuickBooks.OAuth.AccessToken + + @doc """ + Creates an Sales Receipt. + + A SalesReceipt object must have at least one line that describes an item + and an amount. + """ + @spec create_sales_receipt(AccessToken.t, json_map) :: + {:ok, json_map} | {:error, any} + def create_sales_receipt(token, sales_receipt) do + request(:post, "company/#{token.realm_id}/salesreceipt", sales_receipt) + |> sign_request(token) + |> send_json_request + end + + @doc """ + Retrieves a Sales Receipt. + """ + @spec read_sales_receipt(AccessToken.t, String.t) :: + {:ok, json_map} | {:error, any} + def read_sales_receipt(token, sales_receipt_id) do + request(:get, "company/#{token.realm_id}/salesreceipt/#{sales_receipt_id}") + |> sign_request(token) + |> send_json_request + end + + @doc """ + Void a Sales Receipt. Must include Sparse, Id, and SyncToken as + sales_receipt attributes. + """ + @spec void_sales_receipt(AccessToken.t, json_map) :: + {:ok, json_map} | {:error, any} + def void_sales_receipt(token, sales_receipt) do + request(:post, "company/#{token.realm_id}/salesreceipt", sales_receipt, nil, params: [ + {"include", "void"} + ]) + |> sign_request(token) + |> send_json_request + end +end diff --git a/test/exquickbooks/api/sales_receipt_test.exs b/test/exquickbooks/api/sales_receipt_test.exs new file mode 100644 index 0000000..3dee0bf --- /dev/null +++ b/test/exquickbooks/api/sales_receipt_test.exs @@ -0,0 +1,53 @@ +defmodule ExQuickBooks.API.SalesReceiptTest do + use ExUnit.Case, async: false + use ExQuickBooks.APICase + + alias ExQuickBooks.API.SalesReceipt + alias ExQuickBooks.OAuth.AccessToken + + doctest SalesReceipt + + @token %AccessToken{ + token: "token", + token_secret: "secret", + realm_id: "realm_id" + } + + test "create_sales_receipt/3 creates an sales_receipt" do + load_response("sales_receipt/create_sales_receipt.json") |> send_response + + assert {:ok, %{"SalesReceipt" => _}} = + SalesReceipt.create_sales_receipt(@token, %{foo: true}) + + assert %{body: body} = take_request() + assert String.contains?(to_string(body), "foo") + end + + test "create_sales_receipt/3 recovers from an error" do + load_response("sales_receipt/create_sales_receipt_error.json") + |> Map.put(:status_code, 400) + |> send_response + + assert {:error, %{"Fault" => _}} = + SalesReceipt.create_sales_receipt(@token, %{foo: true}) + end + + test "void_sales_receipt/3 voids and retrieves an sales_receipt" do + load_response("sales_receipt/void_sales_receipt.json") |> send_response + + assert {:ok, %{"SalesReceipt" => _}} = + SalesReceipt.void_sales_receipt(@token, %{foo: true}) + + assert %{body: body} = take_request() + assert String.contains?(to_string(body), "foo") + end + + test "void_sales_receipt/3 recovers from an error" do + load_response("sales_receipt/void_sales_receipt_error.json") + |> Map.put(:status_code, 400) + |> send_response + + assert {:error, %{"Fault" => _}} = + SalesReceipt.void_sales_receipt(@token, %{foo: true}) + end +end diff --git a/test/exquickbooks_test.exs b/test/exquickbooks_test.exs index fe96816..42b50c5 100644 --- a/test/exquickbooks_test.exs +++ b/test/exquickbooks_test.exs @@ -92,13 +92,17 @@ defmodule ExQuickBooksTest do Application.delete_env(:exquickbooks, key) end + defp keyword_list_equal?(env1, env2) do + List.keysort(env1, 1) == List.keysort(env2, 1) + end + defp restore_env(old_env) do new_env = get_all_env() for {k, _} <- new_env, do: delete_env(k) for {k, v} <- old_env, do: put_env(k, v) - get_all_env() == old_env || raise """ + keyword_list_equal?(get_all_env(), old_env) || raise """ Could not restore the application's environment. Check that you're not modifying it simultaneously in other tests. Those tests should specify `async: false`. diff --git a/test/fixtures/sales_receipt/create_sales_receipt.json b/test/fixtures/sales_receipt/create_sales_receipt.json new file mode 100644 index 0000000..45e1410 --- /dev/null +++ b/test/fixtures/sales_receipt/create_sales_receipt.json @@ -0,0 +1,57 @@ +{ + "time": "2017-09-20T18:32:11.363-07:00", + "SalesReceipt": { + "sparse": false, + "domain": "QBO", + "TxnDate": "2017-09-20", + "TotalAmt": 0, + "SyncToken": "0", + "PrintStatus": "NeedToPrint", + "MetaData": { + "LastUpdatedTime": "2017-09-20T18:32:11-07:00", + "CreateTime": "2017-09-20T18:32:11-07:00" + }, + "Line": [ + { + "SalesItemLineDetail": { + "UnitPrice": 0, + "TaxCodeRef": { + "value": "NON" + }, + "Qty": 1, + "ItemRef": { + "value": "3", + "name": "Oribe:Shampoo:Test Product" + } + }, + "LineNum": 1, + "Id": "1", + "DetailType": "SalesItemLineDetail", + "Amount": 0 + }, + { + "SubTotalLineDetail": { + + }, + "DetailType": "SubTotalLineDetail", + "Amount": 0 + } + ], + "Id": "6", + "EmailStatus": "NotSet", + "DocNumber": "1005", + "DepositToAccountRef": { + "value": "4", + "name": "Undeposited Funds" + }, + "CustomField": [ + + ], + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "Balance": 0, + "ApplyTaxAfterDiscount": false + } +} diff --git a/test/fixtures/sales_receipt/create_sales_receipt_error.json b/test/fixtures/sales_receipt/create_sales_receipt_error.json new file mode 100644 index 0000000..f179103 --- /dev/null +++ b/test/fixtures/sales_receipt/create_sales_receipt_error.json @@ -0,0 +1,14 @@ +{ + "time": "2017-09-20T18:40:29.968-07:00", + "Fault": { + "type": "ValidationFault", + "Error": [ + { + "element": "Line.DetailType", + "code": "2020", + "Message": "Required param missing, need to supply the required value for the API", + "Detail": "Required parameter Line.DetailType is missing in the request" + } + ] + } +} diff --git a/test/fixtures/sales_receipt/void_sales_receipt.json b/test/fixtures/sales_receipt/void_sales_receipt.json new file mode 100644 index 0000000..60c976c --- /dev/null +++ b/test/fixtures/sales_receipt/void_sales_receipt.json @@ -0,0 +1,58 @@ +{ + "time": "2017-09-20T18:33:39.066-07:00", + "SalesReceipt": { + "sparse": false, + "domain": "QBO", + "TxnDate": "2017-09-20", + "TotalAmt": 0, + "SyncToken": "1", + "PrivateNote": "Voided", + "PrintStatus": "NeedToPrint", + "MetaData": { + "LastUpdatedTime": "2017-09-20T18:33:39-07:00", + "CreateTime": "2017-09-20T18:32:11-07:00" + }, + "Line": [ + { + "SalesItemLineDetail": { + "UnitPrice": 0, + "TaxCodeRef": { + "value": "NON" + }, + "Qty": 0, + "ItemRef": { + "value": "3", + "name": "Oribe:Shampoo:Test Product" + } + }, + "LineNum": 1, + "Id": "1", + "DetailType": "SalesItemLineDetail", + "Amount": 0 + }, + { + "SubTotalLineDetail": { + + }, + "DetailType": "SubTotalLineDetail", + "Amount": 0 + } + ], + "Id": "6", + "EmailStatus": "NotSet", + "DocNumber": "1005", + "DepositToAccountRef": { + "value": "4", + "name": "Undeposited Funds" + }, + "CustomField": [ + + ], + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "Balance": 0, + "ApplyTaxAfterDiscount": false + } +} diff --git a/test/fixtures/sales_receipt/void_sales_receipt_error.json b/test/fixtures/sales_receipt/void_sales_receipt_error.json new file mode 100644 index 0000000..a9aad10 --- /dev/null +++ b/test/fixtures/sales_receipt/void_sales_receipt_error.json @@ -0,0 +1,14 @@ +{ + "time": "2017-09-20T18:43:12.415-07:00", + "Fault": { + "type": "ValidationFault", + "Error": [ + { + "element": "", + "code": "5010", + "Message": "Stale Object Error", + "Detail": "Stale Object Error : You and John Doe were working on this at the same time. John Doe finished before you did, so your work was not saved." + } + ] + } +}