diff --git a/db/dbinfo/comms.phone.bob.go b/db/dbinfo/comms.phone.bob.go index 402cd6aa..757ba1f1 100644 --- a/db/dbinfo/comms.phone.bob.go +++ b/db/dbinfo/comms.phone.bob.go @@ -42,6 +42,15 @@ var CommsPhones = Table[ Generated: false, AutoIncr: false, }, + CanSMS: column{ + Name: "can_sms", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, }, Indexes: commsPhoneIndexes{ PhonePkey: index{ @@ -75,11 +84,12 @@ type commsPhoneColumns struct { E164 column IsSubscribed column Status column + CanSMS column } func (c commsPhoneColumns) AsSlice() []column { return []column{ - c.E164, c.IsSubscribed, c.Status, + c.E164, c.IsSubscribed, c.Status, c.CanSMS, } } diff --git a/db/dbinfo/publicreport.report.bob.go b/db/dbinfo/publicreport.report.bob.go index 05a4860c..c7d7e7e8 100644 --- a/db/dbinfo/publicreport.report.bob.go +++ b/db/dbinfo/publicreport.report.bob.go @@ -222,6 +222,15 @@ var PublicreportReports = Table[ Generated: false, AutoIncr: false, }, + ReporterPhoneCanSMS: column{ + Name: "reporter_phone_can_sms", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, }, Indexes: publicreportReportIndexes{ ReportPkey: index{ @@ -337,11 +346,12 @@ type publicreportReportColumns struct { LocationLongitude column AddressGid column ClientUUID column + ReporterPhoneCanSMS column } func (c publicreportReportColumns) AsSlice() []column { return []column{ - c.AddressRaw, c.AddressID, c.Created, c.Location, c.H3cell, c.ID, c.LatlngAccuracyType, c.LatlngAccuracyValue, c.MapZoom, c.OrganizationID, c.PublicID, c.ReporterName, c.ReporterEmail, c.ReporterPhone, c.ReporterContactConsent, c.ReportType, c.Reviewed, c.ReviewerID, c.Status, c.LocationLatitude, c.LocationLongitude, c.AddressGid, c.ClientUUID, + c.AddressRaw, c.AddressID, c.Created, c.Location, c.H3cell, c.ID, c.LatlngAccuracyType, c.LatlngAccuracyValue, c.MapZoom, c.OrganizationID, c.PublicID, c.ReporterName, c.ReporterEmail, c.ReporterPhone, c.ReporterContactConsent, c.ReportType, c.Reviewed, c.ReviewerID, c.Status, c.LocationLatitude, c.LocationLongitude, c.AddressGid, c.ClientUUID, c.ReporterPhoneCanSMS, } } diff --git a/db/migrations/00133_comms_phone_can_text.sql b/db/migrations/00133_comms_phone_can_text.sql new file mode 100644 index 00000000..717a539c --- /dev/null +++ b/db/migrations/00133_comms_phone_can_text.sql @@ -0,0 +1,10 @@ +-- +goose Up +ALTER TABLE comms.phone ADD COLUMN can_sms BOOLEAN; +UPDATE comms.phone SET can_sms = TRUE; +ALTER TABLE comms.phone ALTER COLUMN can_sms SET NOT NULL; +ALTER TABLE publicreport.report ADD COLUMN reporter_phone_can_sms BOOLEAN; +UPDATE publicreport.report SET reporter_phone_can_sms = TRUE; +ALTER TABLE publicreport.report ALTER COLUMN reporter_phone_can_sms SET NOT NULL; +-- +goose Down +ALTER TABLE comms.phone DROP COLUMN can_sms; +ALTER TABLE publicreport.report DROAP COLUMN reporter_phone_can_sms; diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index a4142341..a958fd29 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -28,6 +28,7 @@ type CommsPhone struct { E164 string `db:"e164,pk" ` IsSubscribed bool `db:"is_subscribed" ` Status enums.CommsPhonestatustype `db:"status" ` + CanSMS bool `db:"can_sms" ` R commsPhoneR `db:"-" ` } @@ -60,12 +61,13 @@ type commsPhoneR struct { func buildCommsPhoneColumns(alias string) commsPhoneColumns { return commsPhoneColumns{ ColumnsExpr: expr.NewColumnsExpr( - "e164", "is_subscribed", "status", + "e164", "is_subscribed", "status", "can_sms", ).WithParent("comms.phone"), tableAlias: alias, E164: psql.Quote(alias, "e164"), IsSubscribed: psql.Quote(alias, "is_subscribed"), Status: psql.Quote(alias, "status"), + CanSMS: psql.Quote(alias, "can_sms"), } } @@ -75,6 +77,7 @@ type commsPhoneColumns struct { E164 psql.Expression IsSubscribed psql.Expression Status psql.Expression + CanSMS psql.Expression } func (c commsPhoneColumns) Alias() string { @@ -92,10 +95,11 @@ type CommsPhoneSetter struct { E164 omit.Val[string] `db:"e164,pk" ` IsSubscribed omit.Val[bool] `db:"is_subscribed" ` Status omit.Val[enums.CommsPhonestatustype] `db:"status" ` + CanSMS omit.Val[bool] `db:"can_sms" ` } func (s CommsPhoneSetter) SetColumns() []string { - vals := make([]string, 0, 3) + vals := make([]string, 0, 4) if s.E164.IsValue() { vals = append(vals, "e164") } @@ -105,6 +109,9 @@ func (s CommsPhoneSetter) SetColumns() []string { if s.Status.IsValue() { vals = append(vals, "status") } + if s.CanSMS.IsValue() { + vals = append(vals, "can_sms") + } return vals } @@ -118,6 +125,9 @@ func (s CommsPhoneSetter) Overwrite(t *CommsPhone) { if s.Status.IsValue() { t.Status = s.Status.MustGet() } + if s.CanSMS.IsValue() { + t.CanSMS = s.CanSMS.MustGet() + } } func (s *CommsPhoneSetter) Apply(q *dialect.InsertQuery) { @@ -126,7 +136,7 @@ func (s *CommsPhoneSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 3) + vals := make([]bob.Expression, 4) if s.E164.IsValue() { vals[0] = psql.Arg(s.E164.MustGet()) } else { @@ -145,6 +155,12 @@ func (s *CommsPhoneSetter) Apply(q *dialect.InsertQuery) { vals[2] = psql.Raw("DEFAULT") } + if s.CanSMS.IsValue() { + vals[3] = psql.Arg(s.CanSMS.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -154,7 +170,7 @@ func (s CommsPhoneSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsPhoneSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 3) + exprs := make([]bob.Expression, 0, 4) if s.E164.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -177,6 +193,13 @@ func (s CommsPhoneSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.CanSMS.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "can_sms")...), + psql.Arg(s.CanSMS), + }}) + } + return exprs } @@ -1421,6 +1444,7 @@ type commsPhoneWhere[Q psql.Filterable] struct { E164 psql.WhereMod[Q, string] IsSubscribed psql.WhereMod[Q, bool] Status psql.WhereMod[Q, enums.CommsPhonestatustype] + CanSMS psql.WhereMod[Q, bool] } func (commsPhoneWhere[Q]) AliasedAs(alias string) commsPhoneWhere[Q] { @@ -1432,6 +1456,7 @@ func buildCommsPhoneWhere[Q psql.Filterable](cols commsPhoneColumns) commsPhoneW E164: psql.Where[Q, string](cols.E164), IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), Status: psql.Where[Q, enums.CommsPhonestatustype](cols.Status), + CanSMS: psql.Where[Q, bool](cols.CanSMS), } } diff --git a/db/models/publicreport.report.bob.go b/db/models/publicreport.report.bob.go index bb9b6aa0..1481a06c 100644 --- a/db/models/publicreport.report.bob.go +++ b/db/models/publicreport.report.bob.go @@ -51,6 +51,7 @@ type PublicreportReport struct { LocationLongitude null.Val[float64] `db:"location_longitude,generated" ` AddressGid string `db:"address_gid" ` ClientUUID null.Val[uuid.UUID] `db:"client_uuid" ` + ReporterPhoneCanSMS bool `db:"reporter_phone_can_sms" ` R publicreportReportR `db:"-" ` } @@ -86,7 +87,7 @@ type publicreportReportR struct { func buildPublicreportReportColumns(alias string) publicreportReportColumns { return publicreportReportColumns{ ColumnsExpr: expr.NewColumnsExpr( - "address_raw", "address_id", "created", "location", "h3cell", "id", "latlng_accuracy_type", "latlng_accuracy_value", "map_zoom", "organization_id", "public_id", "reporter_name", "reporter_email", "reporter_phone", "reporter_contact_consent", "report_type", "reviewed", "reviewer_id", "status", "location_latitude", "location_longitude", "address_gid", "client_uuid", + "address_raw", "address_id", "created", "location", "h3cell", "id", "latlng_accuracy_type", "latlng_accuracy_value", "map_zoom", "organization_id", "public_id", "reporter_name", "reporter_email", "reporter_phone", "reporter_contact_consent", "report_type", "reviewed", "reviewer_id", "status", "location_latitude", "location_longitude", "address_gid", "client_uuid", "reporter_phone_can_sms", ).WithParent("publicreport.report"), tableAlias: alias, AddressRaw: psql.Quote(alias, "address_raw"), @@ -112,6 +113,7 @@ func buildPublicreportReportColumns(alias string) publicreportReportColumns { LocationLongitude: psql.Quote(alias, "location_longitude"), AddressGid: psql.Quote(alias, "address_gid"), ClientUUID: psql.Quote(alias, "client_uuid"), + ReporterPhoneCanSMS: psql.Quote(alias, "reporter_phone_can_sms"), } } @@ -141,6 +143,7 @@ type publicreportReportColumns struct { LocationLongitude psql.Expression AddressGid psql.Expression ClientUUID psql.Expression + ReporterPhoneCanSMS psql.Expression } func (c publicreportReportColumns) Alias() string { @@ -176,10 +179,11 @@ type PublicreportReportSetter struct { Status omit.Val[enums.PublicreportReportstatustype] `db:"status" ` AddressGid omit.Val[string] `db:"address_gid" ` ClientUUID omitnull.Val[uuid.UUID] `db:"client_uuid" ` + ReporterPhoneCanSMS omit.Val[bool] `db:"reporter_phone_can_sms" ` } func (s PublicreportReportSetter) SetColumns() []string { - vals := make([]string, 0, 21) + vals := make([]string, 0, 22) if s.AddressRaw.IsValue() { vals = append(vals, "address_raw") } @@ -243,6 +247,9 @@ func (s PublicreportReportSetter) SetColumns() []string { if !s.ClientUUID.IsUnset() { vals = append(vals, "client_uuid") } + if s.ReporterPhoneCanSMS.IsValue() { + vals = append(vals, "reporter_phone_can_sms") + } return vals } @@ -310,6 +317,9 @@ func (s PublicreportReportSetter) Overwrite(t *PublicreportReport) { if !s.ClientUUID.IsUnset() { t.ClientUUID = s.ClientUUID.MustGetNull() } + if s.ReporterPhoneCanSMS.IsValue() { + t.ReporterPhoneCanSMS = s.ReporterPhoneCanSMS.MustGet() + } } func (s *PublicreportReportSetter) Apply(q *dialect.InsertQuery) { @@ -318,7 +328,7 @@ func (s *PublicreportReportSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 21) + vals := make([]bob.Expression, 22) if s.AddressRaw.IsValue() { vals[0] = psql.Arg(s.AddressRaw.MustGet()) } else { @@ -445,6 +455,12 @@ func (s *PublicreportReportSetter) Apply(q *dialect.InsertQuery) { vals[20] = psql.Raw("DEFAULT") } + if s.ReporterPhoneCanSMS.IsValue() { + vals[21] = psql.Arg(s.ReporterPhoneCanSMS.MustGet()) + } else { + vals[21] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -454,7 +470,7 @@ func (s PublicreportReportSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s PublicreportReportSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 21) + exprs := make([]bob.Expression, 0, 22) if s.AddressRaw.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -603,6 +619,13 @@ func (s PublicreportReportSetter) Expressions(prefix ...string) []bob.Expression }}) } + if s.ReporterPhoneCanSMS.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "reporter_phone_can_sms")...), + psql.Arg(s.ReporterPhoneCanSMS), + }}) + } + return exprs } @@ -2021,6 +2044,7 @@ type publicreportReportWhere[Q psql.Filterable] struct { LocationLongitude psql.WhereNullMod[Q, float64] AddressGid psql.WhereMod[Q, string] ClientUUID psql.WhereNullMod[Q, uuid.UUID] + ReporterPhoneCanSMS psql.WhereMod[Q, bool] } func (publicreportReportWhere[Q]) AliasedAs(alias string) publicreportReportWhere[Q] { @@ -2052,6 +2076,7 @@ func buildPublicreportReportWhere[Q psql.Filterable](cols publicreportReportColu LocationLongitude: psql.WhereNull[Q, float64](cols.LocationLongitude), AddressGid: psql.Where[Q, string](cols.AddressGid), ClientUUID: psql.WhereNull[Q, uuid.UUID](cols.ClientUUID), + ReporterPhoneCanSMS: psql.Where[Q, bool](cols.ReporterPhoneCanSMS), } } diff --git a/platform/publicreport/report.go b/platform/publicreport/report.go index 6dfabe79..ec73afc7 100644 --- a/platform/publicreport/report.go +++ b/platform/publicreport/report.go @@ -172,6 +172,7 @@ func reportQuery() bob.BaseQuery[*dialect.SelectQuery] { "r.reporter_email AS \"reporter.email\"", "r.reporter_name AS \"reporter.name\"", "r.reporter_phone AS \"reporter.phone\"", + "r.reporter_phone_can_sms AS \"reporter.can_sms\"", "r.status", ), sm.From("publicreport.report").As("r"), diff --git a/platform/text/phone_number.go b/platform/text/phone_number.go index c75a92f0..104536db 100644 --- a/platform/text/phone_number.go +++ b/platform/text/phone_number.go @@ -20,8 +20,9 @@ func EnsureInDB(ctx context.Context, txn bob.Executor, dst types.E164) (err erro } func ensureInDB(ctx context.Context, txn bob.Executor, destination string) (err error) { _, err = psql.Insert( - im.Into("comms.phone", "e164", "is_subscribed", "status"), + im.Into("comms.phone", "can_sms", "e164", "is_subscribed", "status"), im.Values( + psql.Arg(true), psql.Arg(destination), psql.Arg(false), psql.Arg("unconfirmed"), diff --git a/platform/types/contact.go b/platform/types/contact.go index 5aa1a5d3..c25c63af 100644 --- a/platform/types/contact.go +++ b/platform/types/contact.go @@ -6,15 +6,19 @@ import ( ) type Contact struct { - Email *string `db:"email" json:"-"` + CanSMS *bool `db:"can_sms" json:"can_sms"` + Email *string `db:"email" json:"email"` HasEmail bool `json:"has_email"` HasPhone bool `json:"has_phone"` Name *string `db:"name" json:"name"` - Phone *string `db:"phone" json:"-"` + Phone *string `db:"phone" json:"phone"` } func (c Contact) MarshalJSON() ([]byte, error) { to_marshal := make(map[string]interface{}, 0) + if c.CanSMS != nil { + to_marshal["can_sms"] = *c.CanSMS + } to_marshal["name"] = c.Name to_marshal["has_email"] = (c.Email != nil && *c.Email != "") to_marshal["has_phone"] = (c.Phone != nil && *c.Phone != "") diff --git a/resource/publicreport_compliance.go b/resource/publicreport_compliance.go index c33782a0..c3930b60 100644 --- a/resource/publicreport_compliance.go +++ b/resource/publicreport_compliance.go @@ -60,11 +60,12 @@ func (res *complianceR) Create(ctx context.Context, r *http.Request, n publicrep MapZoom: omit.From(float32(0.0)), //OrganizationID: omitnull.FromPtr(organization_id), //PublicID: omit.From(public_id), - ReporterEmail: omit.From(""), - ReporterName: omit.From(""), - ReporterPhone: omit.From(""), - ReportType: omit.From(enums.PublicreportReporttypeCompliance), - Status: omit.From(enums.PublicreportReportstatustypeReported), + ReporterEmail: omit.From(""), + ReporterName: omit.From(""), + ReporterPhone: omit.From(""), + ReporterPhoneCanSMS: omit.FromPtr[bool](nil), + ReportType: omit.From(enums.PublicreportReporttypeCompliance), + Status: omit.From(enums.PublicreportReportstatustypeReported), } setter_compliance := models.PublicreportComplianceSetter{ AccessInstructions: omit.From(""), @@ -74,8 +75,7 @@ func (res *complianceR) Create(ctx context.Context, r *http.Request, n publicrep HasDog: omitnull.FromPtr[bool](nil), PermissionType: omit.From(enums.PermissionaccesstypeUnselected), //ReportID omit.Val[int32] - ReportPhoneCanText: omitnull.FromPtr[bool](nil), - WantsScheduled: omitnull.FromPtr[bool](nil), + WantsScheduled: omitnull.FromPtr[bool](nil), } report, err := platform.PublicReportComplianceCreate(ctx, setter_report, setter_compliance) if err != nil { @@ -107,7 +107,7 @@ type publicreportComplianceForm struct { Location omit.Val[types.Location] `schema:"location" json:"location"` PermissionType omit.Val[enums.Permissionaccesstype] `schema:"permission_type" json:"permission_type"` Reporter omit.Val[types.Contact] `schema:"reporter" json:"reporter"` - ReportPhoneCanText omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"` + ReportPhoneCanSMS omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"` WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"` } @@ -118,6 +118,7 @@ func (res *complianceR) Update(ctx context.Context, r *http.Request, prf publicr return nil, nhttp.NewBadRequest("You must provide an ID") } report_setter := models.PublicreportReportSetter{} + compliance_setter := models.PublicreportComplianceSetter{} var location *types.Location if prf.Location.IsValue() { l := prf.Location.MustGet() @@ -137,13 +138,15 @@ func (res *complianceR) Update(ctx context.Context, r *http.Request, prf publicr if reporter.Phone != nil { report_setter.ReporterPhone = omit.From(*reporter.Phone) } + if reporter.CanSMS != nil { + report_setter.ReporterPhoneCanSMS = omit.FromPtr(reporter.CanSMS) + } } var address *types.Address if prf.Address.IsValue() { a := prf.Address.MustGet() address = &a } - compliance_setter := models.PublicreportComplianceSetter{} if prf.AccessInstructions.IsValue() { compliance_setter.AccessInstructions = prf.AccessInstructions } @@ -162,9 +165,6 @@ func (res *complianceR) Update(ctx context.Context, r *http.Request, prf publicr if prf.PermissionType.IsValue() { compliance_setter.PermissionType = prf.PermissionType } - if prf.ReportPhoneCanText.IsValue() { - compliance_setter.ReportPhoneCanText = prf.ReportPhoneCanText - } if prf.WantsScheduled.IsValue() { compliance_setter.WantsScheduled = prf.WantsScheduled } diff --git a/ts/components/ReviewPoolColumnDetail.vue b/ts/components/ReviewPoolColumnDetail.vue index 55789181..585b4aa7 100644 --- a/ts/components/ReviewPoolColumnDetail.vue +++ b/ts/components/ReviewPoolColumnDetail.vue @@ -167,16 +167,8 @@ const poolLocation = ref({ latitude: 0, longitude: 0, }); -const siteOwner = ref({ - has_email: false, - has_phone: false, - name: "", -}); -const siteResident = ref({ - has_email: false, - has_phone: false, - name: "", -}); +const siteOwner = ref(new Contact()); +const siteResident = ref(new Contact()); const session = useSessionStore(); function doPoolLocation(event: MapClickEvent) { console.log("pool location", event); diff --git a/ts/rmo/content/compliance/Contact.vue b/ts/rmo/content/compliance/Contact.vue index 6f5a3423..6aab9f61 100644 --- a/ts/rmo/content/compliance/Contact.vue +++ b/ts/rmo/content/compliance/Contact.vue @@ -72,7 +72,7 @@ class="form-check-input" type="checkbox" id="can-text" - v-model="modelValue.reporter.can_text" + v-model="modelValue.reporter.can_sms" /> + Phone
{{ modelValue.reporter.phone }} - (texting OK)
diff --git a/ts/rmo/view/Compliance.vue b/ts/rmo/view/Compliance.vue index 2fb1f7f1..8993d5b1 100644 --- a/ts/rmo/view/Compliance.vue +++ b/ts/rmo/view/Compliance.vue @@ -56,8 +56,7 @@ import { PublicReportCompliance, PublicReportComplianceOptions, } from "@/type/api"; -import { Address, Location, PermissionType } from "@/type/api"; -import { type Contact } from "@/rmo/content/compliance/Contact.vue"; +import { Contact, Address, Location, PermissionType } from "@/type/api"; interface Props { slug: string; @@ -129,6 +128,9 @@ function doContact() { return; } console.log("contact", report.value.reporter); + updateReport({ + reporter: report.value.reporter, + }); } function doPermission() { if (!report.value) { diff --git a/ts/type/api.ts b/ts/type/api.ts index 18db25d3..6c6b4ffa 100644 --- a/ts/type/api.ts +++ b/ts/type/api.ts @@ -36,7 +36,7 @@ export interface Bounds { max: Location; } export interface ContactOptions { - can_text?: boolean; + can_sms: boolean; email?: string; has_email: boolean; has_phone: boolean; @@ -44,14 +44,14 @@ export interface ContactOptions { phone?: string; } export class Contact { - can_text?: boolean; - email?: string; + can_sms: boolean; + email: string; has_email: boolean; has_phone: boolean; - name?: string; - phone?: string; + name: string; + phone: string; constructor(options?: ContactOptions) { - this.can_text = options?.can_text ?? false; + this.can_sms = options?.can_sms ?? false; this.email = options?.email ?? ""; this.has_email = options?.has_email ?? false; this.has_phone = options?.has_phone ?? false; @@ -159,7 +159,6 @@ export interface ComplianceUpdate { permission_type?: string; reporter?: Contact; //uri: string; - report_phone_can_text?: boolean; wants_scheduled?: boolean; } export interface PublicReportDTO { @@ -370,7 +369,7 @@ export class PublicReportWater extends PublicReport { contact: { name: "", phone: "", - can_text: true, + can_sms: true, email: "", }, id: "",