|
| 1 | +package models |
| 2 | + |
| 3 | +import ( |
| 4 | + "time" |
| 5 | + |
| 6 | + "go.mongodb.org/mongo-driver/bson" |
| 7 | + "go.mongodb.org/mongo-driver/bson/bsontype" |
| 8 | +) |
| 9 | + |
| 10 | +// ============================================================ |
| 11 | +// Shared task fields (used by both companies and speakers) |
| 12 | +// ============================================================ |
| 13 | + |
| 14 | +// TaskLogos tracks logo delivery status. |
| 15 | +type TaskLogos struct { |
| 16 | + Received bool `json:"received" bson:"received"` |
| 17 | + NeedsReviewing bool `json:"needsReviewing" bson:"needsReviewing"` |
| 18 | +} |
| 19 | + |
| 20 | +// ============================================================ |
| 21 | +// Company-specific task types |
| 22 | +// ============================================================ |
| 23 | + |
| 24 | +// CompanyTaskConfirmation tracks confirmation-related progress for a company. |
| 25 | +type CompanyTaskConfirmation struct { |
| 26 | + AskedForInfo bool `json:"askedForInfo" bson:"askedForInfo"` |
| 27 | +} |
| 28 | + |
| 29 | +// CompanyTaskContract tracks contract lifecycle. |
| 30 | +type CompanyTaskContract struct { |
| 31 | + Sent bool `json:"sent" bson:"sent"` |
| 32 | + Created bool `json:"created" bson:"created"` |
| 33 | + Signed bool `json:"signed" bson:"signed"` |
| 34 | + ReceiptSent bool `json:"receiptSent" bson:"receiptSent"` |
| 35 | + Paid bool `json:"paid" bson:"paid"` |
| 36 | +} |
| 37 | + |
| 38 | +// CompanyTaskSessionTitles tracks session / workshop titles. |
| 39 | +type CompanyTaskSessionTitles struct { |
| 40 | + PresentationTitle string `json:"presentationTitle" bson:"presentationTitle"` |
| 41 | + WorkshopTitle string `json:"workshopTitle" bson:"workshopTitle"` |
| 42 | +} |
| 43 | + |
| 44 | +// CompanyTaskCorlief tracks Corlief-related steps. |
| 45 | +type CompanyTaskCorlief struct { |
| 46 | + PreNotice bool `json:"preNotice" bson:"preNotice"` |
| 47 | + Scheduled bool `json:"scheduled" bson:"scheduled"` |
| 48 | + Reserved bool `json:"reserved" bson:"reserved"` |
| 49 | +} |
| 50 | + |
| 51 | +// CompanyTaskLogistics tracks logistics info. |
| 52 | +type CompanyTaskLogistics struct { |
| 53 | + RequestedInfo bool `json:"requestedInfo" bson:"requestedInfo"` |
| 54 | + CarStatus string `json:"carStatus" bson:"carStatus"` // "not_responded", "wants", "not_wants" |
| 55 | + LicensePlate string `json:"licensePlate" bson:"licensePlate"` |
| 56 | +} |
| 57 | + |
| 58 | +// CompanyTasks is the top-level task object embedded in CompanyParticipation. |
| 59 | +type CompanyTasks struct { |
| 60 | + Confirmation CompanyTaskConfirmation `json:"confirmation" bson:"confirmation"` |
| 61 | + Logos TaskLogos `json:"logos" bson:"logos"` |
| 62 | + Contract CompanyTaskContract `json:"contract" bson:"contract"` |
| 63 | + SessionTitles CompanyTaskSessionTitles `json:"sessionTitles" bson:"sessionTitles"` |
| 64 | + Corlief CompanyTaskCorlief `json:"corlief" bson:"corlief"` |
| 65 | + Logistics CompanyTaskLogistics `json:"logistics" bson:"logistics"` |
| 66 | + PO string `json:"po" bson:"po"` |
| 67 | +} |
| 68 | + |
| 69 | +// ============================================================ |
| 70 | +// Speaker-specific task types |
| 71 | +// ============================================================ |
| 72 | + |
| 73 | +// SpeakerTaskConfirmation tracks confirmation-related progress for a speaker. |
| 74 | +type SpeakerTaskConfirmation struct { |
| 75 | + Phone string `json:"phone" bson:"phone"` |
| 76 | + LinkedIn string `json:"linkedin" bson:"linkedin"` |
| 77 | + WantsLinkedIn string `json:"wantsLinkedinTag" bson:"wantsLinkedinTag"` // "not_responded", "yes", "no" |
| 78 | + Observations string `json:"observations" bson:"observations"` |
| 79 | +} |
| 80 | + |
| 81 | +// UnmarshalBSON handles legacy bool values stored for WantsLinkedIn. |
| 82 | +func (s *SpeakerTaskConfirmation) UnmarshalBSON(data []byte) error { |
| 83 | + var raw bson.Raw |
| 84 | + if err := bson.Unmarshal(data, &raw); err != nil { |
| 85 | + return err |
| 86 | + } |
| 87 | + |
| 88 | + decodeString := func(key string) string { |
| 89 | + val, err := raw.LookupErr(key) |
| 90 | + if err != nil { |
| 91 | + return "" |
| 92 | + } |
| 93 | + if val.Type == bsontype.String { |
| 94 | + v, _ := val.StringValueOK() |
| 95 | + return v |
| 96 | + } |
| 97 | + return "" |
| 98 | + } |
| 99 | + |
| 100 | + decodeLinkedIn := func(key string) string { |
| 101 | + val, err := raw.LookupErr(key) |
| 102 | + if err != nil { |
| 103 | + return "not_responded" |
| 104 | + } |
| 105 | + switch val.Type { |
| 106 | + case bsontype.Boolean: |
| 107 | + b, _ := val.BooleanOK() |
| 108 | + return boolToString(b) |
| 109 | + case bsontype.String: |
| 110 | + v, _ := val.StringValueOK() |
| 111 | + return v |
| 112 | + default: |
| 113 | + return "not_responded" |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + s.Phone = decodeString("phone") |
| 118 | + s.LinkedIn = decodeString("linkedin") |
| 119 | + s.WantsLinkedIn = decodeLinkedIn("wantsLinkedinTag") |
| 120 | + s.Observations = decodeString("observations") |
| 121 | + return nil |
| 122 | +} |
| 123 | + |
| 124 | +// MarshalBSON always writes string values. |
| 125 | +func (s SpeakerTaskConfirmation) MarshalBSON() ([]byte, error) { |
| 126 | + return bson.Marshal(bson.D{ |
| 127 | + {Key: "phone", Value: s.Phone}, |
| 128 | + {Key: "linkedin", Value: s.LinkedIn}, |
| 129 | + {Key: "wantsLinkedinTag", Value: s.WantsLinkedIn}, |
| 130 | + {Key: "observations", Value: s.Observations}, |
| 131 | + }) |
| 132 | +} |
| 133 | + |
| 134 | +// SpeakerTaskFlightLeg stores one leg (arrival or departure). |
| 135 | +type SpeakerTaskFlightLeg struct { |
| 136 | + Airport string `json:"airport" bson:"airport"` |
| 137 | + FlightNumber string `json:"flightNumber" bson:"flightNumber"` |
| 138 | + Date *time.Time `json:"date,omitempty" bson:"date,omitempty"` |
| 139 | + Time string `json:"time" bson:"time"` |
| 140 | +} |
| 141 | + |
| 142 | +// SpeakerTaskFlightDetails stores pricing / status / booking info. |
| 143 | +type SpeakerTaskFlightDetails struct { |
| 144 | + Price string `json:"price" bson:"price"` |
| 145 | + Status string `json:"status" bson:"status"` // "pending", "received", "approved", "bought" |
| 146 | + Link string `json:"link" bson:"link"` |
| 147 | + BookingRef string `json:"bookingRef" bson:"bookingRef"` |
| 148 | +} |
| 149 | + |
| 150 | +// SpeakerTaskFlightRefund stores refund info. |
| 151 | +type SpeakerTaskFlightRefund struct { |
| 152 | + Amount string `json:"amount" bson:"amount"` |
| 153 | + Method string `json:"method" bson:"method"` |
| 154 | + InfoNeeded string `json:"infoNeeded" bson:"infoNeeded"` |
| 155 | + Status string `json:"status" bson:"status"` // "not_started", "receipt_requested", "info_requested", "done" |
| 156 | +} |
| 157 | + |
| 158 | +// SpeakerTaskFlights stores everything flight-related. |
| 159 | +type SpeakerTaskFlights struct { |
| 160 | + NeedsFlights string `json:"needsFlights" bson:"needsFlights"` // "not_responded", "yes", "no" |
| 161 | + Requested bool `json:"requested" bson:"requested"` |
| 162 | + Arrival SpeakerTaskFlightLeg `json:"arrival" bson:"arrival"` |
| 163 | + Departure SpeakerTaskFlightLeg `json:"departure" bson:"departure"` |
| 164 | + Details SpeakerTaskFlightDetails `json:"details" bson:"details"` |
| 165 | + Refund SpeakerTaskFlightRefund `json:"refund" bson:"refund"` |
| 166 | +} |
| 167 | + |
| 168 | +// SpeakerTaskCoverage tracks video/photo coverage confirmation. |
| 169 | +type SpeakerTaskCoverage struct { |
| 170 | + Video string `json:"video" bson:"video"` // "not_responded", "yes", "no" |
| 171 | + Streaming string `json:"streaming" bson:"streaming"` // "not_responded", "yes", "no" |
| 172 | + Photo string `json:"photo" bson:"photo"` // "not_responded", "yes", "no" |
| 173 | +} |
| 174 | + |
| 175 | +// boolToString converts legacy boolean values to the new string format. |
| 176 | +func boolToString(v interface{}) string { |
| 177 | + switch b := v.(type) { |
| 178 | + case bool: |
| 179 | + if b { |
| 180 | + return "yes" |
| 181 | + } |
| 182 | + return "no" |
| 183 | + case string: |
| 184 | + return b |
| 185 | + default: |
| 186 | + return "not_responded" |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +// UnmarshalBSON handles legacy boolean fields being decoded into string fields. |
| 191 | +func (c *SpeakerTaskCoverage) UnmarshalBSON(data []byte) error { |
| 192 | + var raw bson.Raw |
| 193 | + if err := bson.Unmarshal(data, &raw); err != nil { |
| 194 | + return err |
| 195 | + } |
| 196 | + |
| 197 | + decodeField := func(key string) string { |
| 198 | + val, err := raw.LookupErr(key) |
| 199 | + if err != nil { |
| 200 | + return "not_responded" |
| 201 | + } |
| 202 | + switch val.Type { |
| 203 | + case bsontype.Boolean: |
| 204 | + b, _ := val.BooleanOK() |
| 205 | + return boolToString(b) |
| 206 | + case bsontype.String: |
| 207 | + s, _ := val.StringValueOK() |
| 208 | + return s |
| 209 | + default: |
| 210 | + return "not_responded" |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + c.Video = decodeField("video") |
| 215 | + c.Streaming = decodeField("streaming") |
| 216 | + c.Photo = decodeField("photo") |
| 217 | + return nil |
| 218 | +} |
| 219 | + |
| 220 | +// MarshalBSON always writes string values. |
| 221 | +func (c SpeakerTaskCoverage) MarshalBSON() ([]byte, error) { |
| 222 | + return bson.Marshal(bson.D{ |
| 223 | + {Key: "video", Value: c.Video}, |
| 224 | + {Key: "streaming", Value: c.Streaming}, |
| 225 | + {Key: "photo", Value: c.Photo}, |
| 226 | + }) |
| 227 | +} |
| 228 | + |
| 229 | +// SpeakerTaskMaterials tracks talk info and materials delivery. |
| 230 | +type SpeakerTaskMaterials struct { |
| 231 | + Requested bool `json:"requested" bson:"requested"` |
| 232 | + TalkTitle string `json:"talkTitle" bson:"talkTitle"` |
| 233 | + TalkDescription string `json:"talkDescription" bson:"talkDescription"` |
| 234 | + Received bool `json:"received" bson:"received"` |
| 235 | + TestSchedule string `json:"testSchedule" bson:"testSchedule"` |
| 236 | + TestDone bool `json:"testDone" bson:"testDone"` |
| 237 | +} |
| 238 | + |
| 239 | +// SpeakerTaskHotel stores hotel / booking / payment info. |
| 240 | +type SpeakerTaskHotel struct { |
| 241 | + NeedsHotel string `json:"needsHotel" bson:"needsHotel"` // "not_responded", "yes", "no" |
| 242 | + Requested bool `json:"requested" bson:"requested"` |
| 243 | + HotelName string `json:"hotelName" bson:"hotelName"` |
| 244 | + RoomType string `json:"roomType" bson:"roomType"` |
| 245 | + Price string `json:"price" bson:"price"` |
| 246 | + CheckIn *time.Time `json:"checkIn,omitempty" bson:"checkIn,omitempty"` |
| 247 | + CheckOut *time.Time `json:"checkOut,omitempty" bson:"checkOut,omitempty"` |
| 248 | + NumNights string `json:"numNights" bson:"numNights"` |
| 249 | + NumGuests string `json:"numGuests" bson:"numGuests"` |
| 250 | + GuestNames string `json:"guestNames" bson:"guestNames"` |
| 251 | + Invoice bool `json:"invoice" bson:"invoice"` |
| 252 | + Paid bool `json:"paid" bson:"paid"` |
| 253 | + Notes string `json:"notes" bson:"notes"` |
| 254 | +} |
| 255 | + |
| 256 | +// SpeakerTasks is the top-level task object embedded in SpeakerParticipation. |
| 257 | +type SpeakerTasks struct { |
| 258 | + Confirmation SpeakerTaskConfirmation `json:"confirmation" bson:"confirmation"` |
| 259 | + Logos TaskLogos `json:"logos" bson:"logos"` |
| 260 | + AskedForInfo bool `json:"askedForInfo" bson:"askedForInfo"` |
| 261 | + Flights SpeakerTaskFlights `json:"flights" bson:"flights"` |
| 262 | + Coverage SpeakerTaskCoverage `json:"coverage" bson:"coverage"` |
| 263 | + Materials SpeakerTaskMaterials `json:"materials" bson:"materials"` |
| 264 | + Hotel SpeakerTaskHotel `json:"hotel" bson:"hotel"` |
| 265 | +} |
0 commit comments