Repository: moov-io/ach
Pull request #1194 (2023-03-31 15:29:11)

fix: include Offset in API docs and read from JSON


Patch:
diff --git a/batch.go b/batch.go
index 28211bd9..fbf860c3 100644
--- a/batch.go
+++ b/batch.go
@@ -154,8 +154,10 @@ func (batch *Batch) UnmarshalJSON(p []byte) error {
 	type Alias Batch
 	aux := struct {
 		*Alias
+		Offset *Offset `json:"offset"`
 	}{
 		(*Alias)(batch),
+		nil,
 	}
 	if err := json.Unmarshal(p, &aux); err != nil {
 		if e, ok := err.(*json.UnmarshalTypeError); ok {
@@ -163,6 +165,9 @@ func (batch *Batch) UnmarshalJSON(p []byte) error {
 		}
 		return err
 	}
+	if aux.Offset != nil {
+		batch.offset = aux.Offset
+	}
 	return nil
 }
 
diff --git a/openapi.yaml b/openapi.yaml
index f725a499..eee498e1 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -827,7 +827,9 @@ components:
           items:
             $ref: '#/components/schemas/ADVEntryDetail'
         advBatchControl:
-            $ref: '#/components/schemas/ADVBatchControl'
+          $ref: '#/components/schemas/ADVBatchControl'
+        offset:
+          $ref: '#/components/schemas/Offset'
       required:
         - batchHeader
         - entryDetails


Test patch:
diff --git a/batch_test.go b/batch_test.go
index 5762484e..95315ad4 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -140,6 +140,24 @@ func TestBatch__UnmarshalJSON(t *testing.T) {
 	}
 }
 
+func TestBatch_UnmarshalOffset(t *testing.T) {
+	bs, err := os.ReadFile(filepath.Join("test", "testdata", "ppd-with-offset.json"))
+	require.NoError(t, err)
+
+	file, err := FileFromJSON(bs)
+	require.NoError(t, err)
+
+	require.Len(t, file.Batches, 1)
+	b, ok := file.Batches[0].(*BatchPPD)
+	require.True(t, ok)
+	require.NotNil(t, b.offset)
+
+	require.Equal(t, "987654320", b.offset.RoutingNumber)
+	require.Equal(t, "123123123", b.offset.AccountNumber)
+	require.Equal(t, OffsetAccountType("checking"), b.offset.AccountType)
+	require.Equal(t, "OFFSET", b.offset.Description)
+}
+
 // Test cases that apply to all batch types
 // testBatchNumberMismatch validates BatchNumber mismatch
 func testBatchNumberMismatch(t testing.TB) {
diff --git a/server/files_test.go b/server/files_test.go
index 0813d8ed..3fb4ae60 100644
--- a/server/files_test.go
+++ b/server/files_test.go
@@ -140,6 +140,38 @@ func TestFiles__CreateFileNacha(t *testing.T) {
 	require.Equal(t, "121042880000001", entries[0].TraceNumber)
 }
 
+func TestFiles_CreateWithOffset(t *testing.T) {
+	repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger())
+	svc := NewService(repo)
+
+	fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-with-offset.json"))
+	require.NoError(t, err)
+	defer fd.Close()
+
+	fileID := base.ID()
+	req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s", fileID), fd)
+	req.Header.Set("content-type", "application/json")
+
+	handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger())
+
+	w := httptest.NewRecorder()
+	handler.ServeHTTP(w, req)
+
+	require.Equal(t, http.StatusOK, w.Code)
+
+	var resp createFileResponse
+	err = json.NewDecoder(w.Body).Decode(&resp)
+	require.NoError(t, err)
+	require.Len(t, resp.File.Batches, 1)
+
+	b, ok := resp.File.Batches[0].(*ach.BatchPPD)
+	require.True(t, ok)
+
+	offset := reflect.ValueOf(b).Elem().FieldByName("offset")
+	expected := `&ach.Offset{RoutingNumber:"987654320", AccountNumber:"123123123", AccountType:"checking", Description:"OFFSET"}`
+	require.Equal(t, expected, fmt.Sprintf("%#v", offset))
+}
+
 func TestFiles__CustomJsonValidation(t *testing.T) {
 	repo := NewRepositoryInMemory(testTTLDuration, nil)
 	svc := NewService(repo)
diff --git a/test/testdata/ppd-with-offset.json b/test/testdata/ppd-with-offset.json
new file mode 100644
index 00000000..36c7240f
--- /dev/null
+++ b/test/testdata/ppd-with-offset.json
@@ -0,0 +1,73 @@
+{
+    "id": "1f707c97-da19-49d0-a3c9-49eebc042e68",
+    "fileHeader": {
+        "id": "0a0b39ed-24be-4711-9a87-662d00372e90",
+        "immediateDestination": "231380104",
+        "immediateOrigin": "121042882",
+        "fileCreationDate": "181008",
+        "fileCreationTime": "",
+        "fileIDModifier": "A",
+        "immediateDestinationName": "Citadel",
+        "immediateOriginName": "Wells Fargo"
+    },
+    "batches": [
+        {
+            "batchHeader": {
+                "id": "45f0cc00-6228-4e50-bffa-aac0e1902df7",
+                "serviceClassCode": 200,
+                "companyName": "Wells Fargo",
+                "companyIdentification": "121042882",
+                "standardEntryClassCode": "PPD",
+                "companyEntryDescription": "Trans. Des",
+                "effectiveEntryDate": "2018-10-09T00:00:00Z",
+                "ODFIIdentification": "12104288",
+                "batchNumber": 1
+            },
+            "entryDetails": [
+                {
+                    "id": "2b5ab363-762c-4459-a8cf-9573fd69029c",
+                    "transactionCode": 22,
+                    "RDFIIdentification": "23138010",
+                    "checkDigit": "4",
+                    "DFIAccountNumber": "81967038518      ",
+                    "amount": 100000,
+                    "identificationNumber": "#83738AB#      ",
+                    "individualName": "Steven Tander         ",
+                    "discretionaryData": "  ",
+                    "addendaRecordIndicator": 0,
+                    "traceNumber": "121042880000001",
+                    "category": "Forward"
+                }
+            ],
+            "batchControl": {
+                "id": "a34c8873-50a2-4315-8d98-b9f84ccccc01",
+                "serviceClassCode": 200,
+                "entryAddendaCount": 0,
+                "entryHash": 23138010,
+                "totalDebit": 0,
+                "totalCredit": 100000,
+                "companyIdentification": "121042882",
+                "ODFIIdentification": "12104288",
+                "batchNumber": 1
+            },
+            "offset": {
+                "description": "OFFSET",
+                "accountType": "checking",
+                "accountNumber": "123123123",
+                "routingNumber": "987654320"
+            }
+        }
+    ],
+    "IATBatches": null,
+    "fileControl": {
+        "id": "1fc34982-0510-4a33-ba0b-9748ea72a254",
+        "batchCount": 1,
+        "blockCount": 1,
+        "entryAddendaCount": 0,
+        "entryHash": 23138010,
+        "totalDebit": 0,
+        "totalCredit": 100000
+    },
+    "NotificationOfChange": null,
+    "ReturnEntries": null
+}