Skip to content

Commit

Permalink
Add VisitNoteServicer.Find and return ErrBillExist on create if one a…
Browse files Browse the repository at this point in the history
…lready exists for a visit note
  • Loading branch information
willfitze committed Dec 10, 2024
1 parent 03979df commit 6e1f049
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 26 deletions.
73 changes: 47 additions & 26 deletions bill.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ package elation

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"slices"
"time"

"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)

const billExistError = "The visit note provided already has a bill associated with it."

var ErrBillExist = errors.New("bill already exists for visit note")

type BillServicer interface {
Create(ctx context.Context, create *BillCreate) (*Bill, *http.Response, error)
}
Expand All @@ -21,32 +28,32 @@ type BillService struct {
}

type Bill struct {
ID int64 `json:"id"` //: 65099661468,
RefNumber *string `json:"ref_number"` //: null, // string(50). required for PATCH that marks bill as processed.
ServiceDate time.Time `json:"service_date"` //: "2016-10-12T12:00:00Z",
BillingDate *time.Time `json:"billing_date"` //: null, // datetime(iso8601). required for PATCH that marks bill as processed.
BillingStatus string `json:"billing_status"` //: "Unbilled",
BillingError *string `json:"billing_error"` //: null, // string(200). required for PATCH that marks bill as failed.
BillingRawError *string `json:"billing_raw_error"` //: null, // longtext. optional for PATCH that marks bill as failed.
Notes string `json:"notes"` //: "patient has not paid yet",
CPTs []*BillCPT `json:"cpts"` //: [{}],
Payment int64 `json:"payment"` //: 142502884606313,
VisitNote int64 `json:"visit_note"` //: 64409108504,
VisitNoteSignedDate time.Time `json:"visit_note_signed_date"` //: "2016-10-12T22:11:01Z",
VisitNoteDeletedDate *time.Time `json:"visit_note_deleted_date"` //: null,
ReferringProvider *BillProvider `json:"referring_provider"` //: {},
BillingProvider *int64 `json:"billing_provider"` //: 42120898,
RenderingProvider *int64 `json:"rendering_provider"` //: 68382673,
SupervisingProvider *int64 `json:"supervising_provider"` //: 52893234,
OrderingProvider *BillProvider `json:"ordering_provider"` //: {}
ServiceLocation int64 `json:"service_location"` //: 141103949480183,
Physician int64 `json:"physician"` //: 64811630594,
Practice int64 `json:"practice"` //: 65540,
Patient int64 `json:"patient"` //: 64901939201,
PriorAuthorization *string `json:"prior_authorization"` //: "1234-ABC",
Metadata any `json:"metadata"` //: null,
CreatedDate time.Time `json:"created_date"` //: "2016-05-23T17:50:50Z",
LastModifiedDate time.Time `json:"last_modified_date"` //: "2016-10-12T22:39:46Z"
ID int64 `json:"id"` //: 65099661468,
RefNumber *string `json:"ref_number"` //: null, // string(50). required for PATCH that marks bill as processed.
ServiceDate time.Time `json:"service_date"` //: "2016-10-12T12:00:00Z",
BillingDate *time.Time `json:"billing_date"` //: null, // datetime(iso8601). required for PATCH that marks bill as processed.
BillingStatus string `json:"billing_status"` //: "Unbilled",
BillingError *string `json:"billing_error"` //: null, // string(200). required for PATCH that marks bill as failed.
BillingRawError *string `json:"billing_raw_error"` //: null, // longtext. optional for PATCH that marks bill as failed.
Notes string `json:"notes"` //: "patient has not paid yet",
CPTs []*BillCPT `json:"cpts"` //: [{}],
Payment int64 `json:"payment"` //: 142502884606313,
VisitNote int64 `json:"visit_note"` //: 64409108504,
VisitNoteSignedDate time.Time `json:"visit_note_signed_date"` //: "2016-10-12T22:11:01Z",
VisitNoteDeletedDate *time.Time `json:"visit_note_deleted_date"` //: null,
ReferringProvider *BillProvider `json:"referring_provider"` //: {},
BillingProvider *int64 `json:"billing_provider"` //: 42120898,
RenderingProvider *int64 `json:"rendering_provider"` //: 68382673,
SupervisingProvider *int64 `json:"supervising_provider"` //: 52893234,
OrderingProvider *BillProvider `json:"ordering_provider"` //: {}
ServiceLocation json.RawMessage `json:"service_location"` //: 141103949480183,
Physician int64 `json:"physician"` //: 64811630594,
Practice int64 `json:"practice"` //: 65540,
Patient int64 `json:"patient"` //: 64901939201,
PriorAuthorization *string `json:"prior_authorization"` //: "1234-ABC",
Metadata any `json:"metadata"` //: null,
CreatedDate time.Time `json:"created_date"` //: "2016-05-23T17:50:50Z",
LastModifiedDate time.Time `json:"last_modified_date"` //: "2016-10-12T22:39:46Z"
}

type BillCreate struct {
Expand Down Expand Up @@ -115,6 +122,20 @@ func (b *BillService) Create(ctx context.Context, create *BillCreate) (*Bill, *h
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "error making request")

var clientErr *Error
if errors.As(err, &clientErr) && clientErr.StatusCode == http.StatusBadRequest {
errorRes := map[string][]string{}
err := json.Unmarshal([]byte(clientErr.Body), &errorRes)
if err != nil {
return nil, res, fmt.Errorf("unmarshaling response body to error response: %w", err)
}

if len(errorRes["visit_note"]) > 0 && slices.Contains(errorRes["visit_note"], billExistError) {
return nil, res, ErrBillExist
}
}

return nil, res, fmt.Errorf("making request: %w", err)
}

Expand Down
50 changes: 50 additions & 0 deletions bill_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,53 @@ func TestBillService_Create(t *testing.T) {
})
}
}

func TestBillService_Create_already_exists(t *testing.T) {
assert := assert.New(t)

billCreate := &BillCreate{
ServiceLocation: 10,
VisitNote: 64409108504,
Patient: 64901939201,
Practice: 65540,
Physician: 64811630594,
}

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tokenRequest(w, r) {
return
}

assert.Equal(http.MethodPost, r.Method)
assert.Equal("/bills", r.URL.Path)

body, err := io.ReadAll(r.Body)
assert.NoError(err)

actualBillCreate := &BillCreate{}
err = json.Unmarshal(body, actualBillCreate)
assert.NoError(err)

assert.Equal(billCreate, actualBillCreate)

errorRes := map[string][]string{
"visit_note": {billExistError},
}
b, err := json.Marshal(errorRes)
assert.NoError(err)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
//nolint
w.Write(b)
}))
defer srv.Close()

client := NewHTTPClient(srv.Client(), srv.URL+"/token", "", "", srv.URL)
svc := BillService{client}

created, res, err := svc.Create(context.Background(), billCreate)
assert.Nil(created)
assert.NotNil(res)
assert.ErrorIs(err, ErrBillExist)
}
34 changes: 34 additions & 0 deletions visit_note.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

type VisitNoteServicer interface {
Create(ctx context.Context, create *VisitNoteCreate) (*VisitNote, *http.Response, error)
Find(ctx context.Context, opts *FindVisitNotesOptions) (*Response[[]*VisitNote], *http.Response, error)
}

var _ VisitNoteServicer = (*VisitNoteService)(nil)
Expand Down Expand Up @@ -178,3 +179,36 @@ func (v *VisitNoteService) Create(ctx context.Context, create *VisitNoteCreate)

return vn, res, nil
}

type FindVisitNotesOptions struct {
*Pagination

Patient int64 `url:"patient,omitempty"`
Physician int64 `url:"physician,omitempty"`
Practice int64 `url:"practice,omitempty"`

LastModifiedGT time.Time `url:"last_modified_gt,omitempty"`
LastModifiedGTE time.Time `url:"last_modified_gte,omitempty"`
LastModifiedLT time.Time `url:"last_modified_lt,omitempty"`
LastModifiedLTE time.Time `url:"last_modified_lte,omitempty"`

FromSignedDate time.Time `url:"from_signed_date,omitempty"`
ToSignedDate time.Time `url:"to_signed_date,omitempty"`
Unsigned bool `url:"unsigned,omitempty"`
}

func (v *VisitNoteService) Find(ctx context.Context, opts *FindVisitNotesOptions) (*Response[[]*VisitNote], *http.Response, error) {
ctx, span := v.client.tracer.Start(ctx, "find visit notes", trace.WithSpanKind(trace.SpanKindClient))
defer span.End()

out := &Response[[]*VisitNote]{}

res, err := v.client.request(ctx, http.MethodGet, "/visit_notes", opts, nil, &out)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "error making request")
return nil, res, fmt.Errorf("making request: %w", err)
}

return out, res, nil
}
92 changes: 92 additions & 0 deletions visit_note_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,95 @@ func TestVisitNoteService_Create(t *testing.T) {
})
}
}

func TestVisitNoteService_Find(t *testing.T) {
assert := assert.New(t)

opts := &FindVisitNotesOptions{
Patient: 123,
Physician: 456,
Practice: 789,
LastModifiedGT: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC),
LastModifiedGTE: time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC),
LastModifiedLT: time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC),
LastModifiedLTE: time.Date(2022, 1, 4, 0, 0, 0, 0, time.UTC),
FromSignedDate: time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC),
ToSignedDate: time.Date(2022, 1, 6, 0, 0, 0, 0, time.UTC),
Unsigned: true,
}

visitNotes := []*VisitNote{
{
ID: 1,
},
{
ID: 2,
},
}

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tokenRequest(w, r) {
return
}

assert.Equal(http.MethodGet, r.Method)
assert.Equal("/visit_notes", r.URL.Path)

actualPatient := r.URL.Query().Get("patient")
assert.Equal(opts.Patient, strToInt64(actualPatient))

actualPhysician := r.URL.Query().Get("physician")
assert.Equal(opts.Physician, strToInt64(actualPhysician))

actualPractice := r.URL.Query().Get("practice")
assert.Equal(opts.Practice, strToInt64(actualPractice))

actualLastModifiedGT := r.URL.Query().Get("last_modified_gt")
assert.Equal(opts.LastModifiedGT.Format(time.RFC3339), actualLastModifiedGT)

actualLastModifiedGTE := r.URL.Query().Get("last_modified_gte")
assert.Equal(opts.LastModifiedGTE.Format(time.RFC3339), actualLastModifiedGTE)

actualLastModifiedLT := r.URL.Query().Get("last_modified_lt")
assert.Equal(opts.LastModifiedLT.Format(time.RFC3339), actualLastModifiedLT)

actualLastModifiedLTE := r.URL.Query().Get("last_modified_lte")
assert.Equal(opts.LastModifiedLTE.Format(time.RFC3339), actualLastModifiedLTE)

actualFromSignedDate := r.URL.Query().Get("from_signed_date")
assert.Equal(opts.FromSignedDate.Format(time.RFC3339), actualFromSignedDate)

actualToSignedDate := r.URL.Query().Get("to_signed_date")
assert.Equal(opts.ToSignedDate.Format(time.RFC3339), actualToSignedDate)

actualUnsigned := r.URL.Query().Get("unsigned")
assert.Equal(opts.Unsigned, strToBool(actualUnsigned))

b, err := json.Marshal(Response[[]*VisitNote]{
Results: []*VisitNote{
{
ID: 1,
},
{
ID: 2,
},
},
})
assert.NoError(err)

w.Header().Set("Content-Type", "application/json")
//nolint
w.Write(b)
}))
defer srv.Close()

client := NewHTTPClient(srv.Client(), srv.URL+"/token", "", "", srv.URL)
svc := VisitNoteService{client}

visitNotesRes, res, err := svc.Find(context.Background(), opts)
assert.NotEmpty(visitNotesRes)
assert.NotNil(res)
assert.NoError(err)

assert.Equal(visitNotes, visitNotesRes.Results)
}

0 comments on commit 6e1f049

Please sign in to comment.