1 package main 2 3 import ( 4 "compress/gzip" 5 "encoding/csv" 6 "fmt" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11 12 "vimagination.zapto.org/form" 13 "vimagination.zapto.org/memio" 14 ) 15 16 func (c *Calls) export(w http.ResponseWriter, r *http.Request) { 17 switch r.PostFormValue("type") { 18 case "driverEvents": 19 c.exportDriverEvents(w, r) 20 case "clientEvents": 21 c.exportClientEvents(w, r) 22 case "companyEvents": 23 c.exportCompanyEvents(w, r) 24 case "companyClients": 25 c.exportCompanyClients(w, r) 26 case "clientList": 27 c.exportClientList(w, r) 28 case "companyList": 29 c.exportCompanyList(w, r) 30 case "companiesOverview": 31 c.exportOverview(w, r, false) 32 case "driversOverview": 33 c.exportOverview(w, r, true) 34 default: 35 w.WriteHeader(http.StatusBadRequest) 36 } 37 } 38 39 func formatDateTime(msec int64) string { 40 return time.Unix(msec/1000, (msec%1000)*1000000).Format("02/01/2006 15:04") 41 } 42 43 func formatDate(msec int64) string { 44 return time.Unix(msec/1000, (msec%1000)*1000000).Format("02/01/2006") 45 } 46 47 func formatTime(msec int64) string { 48 return time.Unix(msec/1000, (msec%1000)*1000000).Format("15:04") 49 } 50 51 func formatMoney(pence int64) string { 52 return strconv.FormatFloat(float64(pence)/100, 'f', 2, 64) 53 } 54 55 func (c *Calls) exportDriverEvents(w http.ResponseWriter, r *http.Request) { 56 var f EventsFilter 57 err := form.Process(r, &f) 58 if err != nil { 59 w.WriteHeader(http.StatusBadRequest) 60 if e, ok := err.(form.ErrorMap); ok { 61 for k, v := range e { 62 fmt.Fprintf(w, "%s = %s\n", k, v) 63 } 64 } else { 65 w.Write([]byte(err.Error())) 66 } 67 return 68 } 69 if f.Start > f.End { 70 w.WriteHeader(http.StatusBadRequest) 71 w.Write([]byte("invalid times")) 72 return 73 } 74 dateStr := formatDate(f.Start) 75 if f.Start != f.End { 76 dateStr += " to " + formatDate(f.End) 77 } 78 f.End += 24 * 3600 * 1000 79 var ( 80 e []Event 81 d Driver 82 ) 83 err = c.DriverEvents(f, &e) 84 if err != nil { 85 w.WriteHeader(http.StatusInternalServerError) 86 w.Write([]byte(err.Error())) 87 return 88 } 89 err = c.GetDriver(f.ID, &d) 90 if err != nil { 91 w.WriteHeader(http.StatusInternalServerError) 92 w.Write([]byte(err.Error())) 93 return 94 } 95 buf := make([]byte, 0, 1024*1024) 96 ss := csv.NewWriter(memio.Create(&buf)) 97 ss.WriteAll([][]string{ 98 {"Driver Sheet for " + d.Name + " for " + dateStr}, 99 {}, 100 { 101 "Date", 102 "Time", 103 "Client Name", 104 "Phone Number", 105 "Pick Up", 106 "Destination", 107 "In Car Time", 108 "Waiting Time (mins)", 109 "Drop", 110 "Miles", 111 "Hours", 112 "Parking (GBP)", 113 }, 114 }) 115 lastDate := time.Unix(0, 0) 116 for n, ev := range e { 117 var ( 118 ef EventFinals 119 cl Client 120 ) 121 if err := c.GetEventFinals(ev.ID, &ef); err != nil { 122 w.WriteHeader(http.StatusInternalServerError) 123 w.Write([]byte(err.Error())) 124 return 125 } 126 if err := c.GetClient(ev.ClientID, &cl); err != nil { 127 w.WriteHeader(http.StatusInternalServerError) 128 w.Write([]byte(err.Error())) 129 return 130 } 131 record := make([]string, 6, 12) 132 thisDate := time.Unix((ev.Start-(ev.Start%(24*3600000)))/1000, 0) 133 record[0] = formatDate(ev.Start) 134 if n > 0 { 135 if !thisDate.Equal(lastDate) { 136 ss.Write([]string{}) 137 } else { 138 record[0] = "" 139 } 140 } 141 lastDate = thisDate 142 record[1] = formatTime(ev.Start) 143 record[2] = cl.Name 144 record[3] = " " + cl.PhoneNumber 145 record[4] = ev.From 146 record[5] = ev.To 147 if ef.FinalsSet { 148 record = append(record, 149 formatTime(ef.InCar), 150 strconv.Itoa(int(ef.Waiting)), 151 formatTime(ef.Drop), 152 strconv.Itoa(int(ef.Miles)), 153 formatTime(ef.DriverHours), 154 formatMoney(ef.Parking), 155 ) 156 } 157 ss.Write(record) 158 } 159 ss.Flush() 160 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 161 w.Header().Set("Content-Disposition", "inline; filename=\"driverEvents-"+d.Name+"-"+dateStr+".csv\"") 162 w.Header().Add("Content-Length", strconv.Itoa(len(buf))) 163 164 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 165 if strings.TrimSpace(e) == "gzip" { 166 w.Header().Set("Content-Encoding", "gzip") 167 gw := gzip.NewWriter(w) 168 gw.Write(buf) 169 gw.Close() 170 return 171 } 172 } 173 w.Write(buf) 174 } 175 176 func (c *Calls) exportClientEvents(w http.ResponseWriter, r *http.Request) { 177 var f EventsFilter 178 err := form.Process(r, &f) 179 if err != nil { 180 w.WriteHeader(http.StatusBadRequest) 181 if e, ok := err.(form.ErrorMap); ok { 182 for k, v := range e { 183 fmt.Fprintf(w, "%s = %s\n", k, v) 184 } 185 } else { 186 w.Write([]byte(err.Error())) 187 } 188 return 189 } 190 if f.Start > f.End { 191 w.WriteHeader(http.StatusBadRequest) 192 w.Write([]byte("invalid times")) 193 return 194 } 195 var ( 196 e []Event 197 cl Client 198 cy Company 199 ) 200 dateStr := formatDate(f.Start) 201 if f.Start != f.End { 202 dateStr += " to " + formatDate(f.End) 203 } 204 f.End += 24 * 3600 * 1000 205 err = c.ClientEvents(f, &e) 206 if err != nil { 207 w.WriteHeader(http.StatusInternalServerError) 208 w.Write([]byte(err.Error())) 209 return 210 } 211 err = c.GetClient(f.ID, &cl) 212 if err != nil { 213 w.WriteHeader(http.StatusInternalServerError) 214 w.Write([]byte(err.Error())) 215 return 216 } 217 err = c.GetCompany(cl.CompanyID, &cy) 218 if err != nil { 219 w.WriteHeader(http.StatusInternalServerError) 220 w.Write([]byte(err.Error())) 221 return 222 } 223 buf := make([]byte, 0, 1024*1024) 224 ss := csv.NewWriter(memio.Create(&buf)) 225 ss.WriteAll([][]string{ 226 {"Client Events for " + cl.Name + " for " + dateStr}, 227 {}, 228 { 229 "Driver", 230 "From", 231 "To", 232 "Start", 233 "End", 234 "In Car", 235 "Waiting", 236 "Drop Off", 237 "Flight Time", 238 "Price (GBP)", 239 }, 240 }) 241 for _, ev := range e { 242 var ( 243 ef EventFinals 244 d Driver 245 ) 246 if err := c.GetEventFinals(ev.ID, &ef); err != nil { 247 w.WriteHeader(http.StatusInternalServerError) 248 w.Write([]byte(err.Error())) 249 return 250 } 251 if err := c.GetDriver(ev.DriverID, &d); err != nil { 252 w.WriteHeader(http.StatusInternalServerError) 253 w.Write([]byte(err.Error())) 254 return 255 } 256 record := make([]string, 5, 10) 257 record[0] = d.Name 258 record[1] = ev.From 259 record[2] = ev.To 260 record[3] = formatDateTime(ev.Start) 261 record[4] = formatDateTime(ev.End) 262 if ef.FinalsSet { 263 record = append(record, 264 formatTime(ef.InCar), 265 strconv.Itoa(int(ef.Waiting)), 266 formatTime(ef.Drop), 267 formatTime(ef.Trip), 268 formatMoney(ef.Price), 269 ) 270 } 271 ss.Write(record) 272 } 273 ss.Flush() 274 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 275 w.Header().Set("Content-Disposition", "inline; filename=\"clientEvents-"+cl.Name+"-"+dateStr+".csv\"") 276 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 277 if strings.TrimSpace(e) == "gzip" { 278 w.Header().Set("Content-Encoding", "gzip") 279 gw := gzip.NewWriter(w) 280 gw.Write(buf) 281 gw.Close() 282 return 283 } 284 } 285 w.Write(buf) 286 } 287 288 func (c *Calls) exportCompanyEvents(w http.ResponseWriter, r *http.Request) { 289 var f EventsFilter 290 err := form.Process(r, &f) 291 if err != nil { 292 w.WriteHeader(http.StatusBadRequest) 293 if e, ok := err.(form.ErrorMap); ok { 294 for k, v := range e { 295 fmt.Fprintf(w, "%s = %s\n", k, v) 296 } 297 } else { 298 w.Write([]byte(err.Error())) 299 } 300 return 301 } 302 if f.Start > f.End { 303 w.WriteHeader(http.StatusBadRequest) 304 w.Write([]byte("invalid times")) 305 return 306 } 307 dateStr := formatDate(f.Start) 308 if f.Start != f.End { 309 dateStr += " to " + formatDate(f.End) 310 } 311 f.End += 24 * 3600 * 1000 312 var cy Company 313 err = c.GetCompany(f.ID, &cy) 314 if err != nil { 315 w.WriteHeader(http.StatusBadRequest) 316 w.Write([]byte(err.Error())) 317 return 318 } 319 var e []Event 320 err = c.CompanyEvents(f, &e) 321 if err != nil { 322 w.WriteHeader(http.StatusBadRequest) 323 w.Write([]byte(err.Error())) 324 return 325 } 326 buf := make([]byte, 0, 1024*1024) 327 ss := csv.NewWriter(memio.Create(&buf)) 328 ss.WriteAll([][]string{ 329 {"Company Events for " + cy.Name + " for " + dateStr}, 330 {}, 331 { 332 "Start", 333 "End", 334 "Client", 335 "Reference", 336 "From", 337 "To", 338 "Driver", 339 "Parking (GBP)", 340 "Price (GBP)", 341 }, 342 }) 343 for _, ev := range e { 344 var ( 345 ef EventFinals 346 cl Client 347 d Driver 348 ) 349 if err := c.GetEventFinals(ev.ID, &ef); err != nil { 350 w.WriteHeader(http.StatusInternalServerError) 351 w.Write([]byte(err.Error())) 352 return 353 } 354 if err := c.GetClient(ev.ClientID, &cl); err != nil { 355 w.WriteHeader(http.StatusInternalServerError) 356 w.Write([]byte(err.Error())) 357 return 358 } 359 if err := c.GetDriver(ev.DriverID, &d); err != nil { 360 w.WriteHeader(http.StatusInternalServerError) 361 w.Write([]byte(err.Error())) 362 return 363 } 364 record := make([]string, 7, 9) 365 record[0] = formatDateTime(ev.Start) 366 record[1] = formatDateTime(ev.End) 367 record[2] = cl.Name 368 record[3] = cl.Reference 369 record[4] = ev.From 370 record[5] = ev.To 371 record[6] = d.Name 372 if ef.FinalsSet { 373 record = append(record, 374 formatMoney(ef.Parking), 375 formatMoney(ef.Price), 376 ) 377 } 378 ss.Write(record) 379 } 380 ss.Flush() 381 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 382 w.Header().Set("Content-Disposition", "inline; filename=\"companyEvents-"+cy.Name+"-"+dateStr+".csv\"") 383 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 384 if strings.TrimSpace(e) == "gzip" { 385 w.Header().Set("Content-Encoding", "gzip") 386 gw := gzip.NewWriter(w) 387 gw.Write(buf) 388 gw.Close() 389 return 390 } 391 } 392 w.Write(buf) 393 } 394 395 func (c *Calls) exportOverview(w http.ResponseWriter, r *http.Request, drivers bool) { 396 var f CEventsFilter 397 err := form.Process(r, &f) 398 if err != nil { 399 w.WriteHeader(http.StatusBadRequest) 400 if e, ok := err.(form.ErrorMap); ok { 401 for k, v := range e { 402 fmt.Fprintf(w, "%s = %s\n", k, v) 403 } 404 } else { 405 w.Write([]byte(err.Error())) 406 } 407 return 408 } 409 if f.Start > f.End { 410 w.WriteHeader(http.StatusBadRequest) 411 w.Write([]byte("invalid times")) 412 return 413 } 414 dateStr := formatDate(f.Start) 415 if f.Start != f.End { 416 dateStr += " to " + formatDate(f.End) 417 } 418 f.End += 24 * 3600 * 1000 419 var e []Event 420 if drivers { 421 err = c.DriversEvents(f, &e) 422 } else { 423 err = c.CompaniesEvents(f, &e) 424 } 425 if err != nil { 426 w.WriteHeader(http.StatusBadRequest) 427 w.Write([]byte(err.Error())) 428 return 429 } 430 buf := make([]byte, 0, 1024*1024) 431 ss := csv.NewWriter(memio.Create(&buf)) 432 var title string 433 if drivers { 434 title = "Drivers Events for " + dateStr 435 } else { 436 title = "Company Events for " + dateStr 437 } 438 ss.WriteAll([][]string{ 439 {title}, 440 {}, 441 { 442 "Start", 443 "End", 444 "Client", 445 "Company", 446 "Reference", 447 "From", 448 "To", 449 "Driver", 450 "Parking (GBP)", 451 "Price (GBP)", 452 }, 453 }) 454 for _, ev := range e { 455 var ( 456 ef EventFinals 457 cl Client 458 cy Company 459 d Driver 460 ) 461 if err := c.GetEventFinals(ev.ID, &ef); err != nil { 462 w.WriteHeader(http.StatusInternalServerError) 463 w.Write([]byte(err.Error())) 464 return 465 } 466 if err := c.GetClient(ev.ClientID, &cl); err != nil { 467 w.WriteHeader(http.StatusInternalServerError) 468 w.Write([]byte(err.Error())) 469 return 470 } 471 if err := c.GetCompany(cl.CompanyID, &cy); err != nil { 472 w.WriteHeader(http.StatusInternalServerError) 473 w.Write([]byte(err.Error())) 474 return 475 } 476 if err := c.GetDriver(ev.DriverID, &d); err != nil { 477 w.WriteHeader(http.StatusInternalServerError) 478 w.Write([]byte(err.Error())) 479 return 480 } 481 record := make([]string, 8, 10) 482 record[0] = formatDateTime(ev.Start) 483 record[1] = formatDateTime(ev.End) 484 record[2] = cl.Name 485 record[3] = cy.Name 486 record[4] = cl.Reference 487 record[5] = ev.From 488 record[6] = ev.To 489 record[7] = d.Name 490 if ef.FinalsSet { 491 record = append(record, 492 formatMoney(ef.Parking), 493 formatMoney(ef.Price), 494 ) 495 } 496 ss.Write(record) 497 } 498 ss.Flush() 499 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 500 w.Header().Set("Content-Disposition", "inline; filename=\"overview-"+dateStr+".csv\"") 501 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 502 if strings.TrimSpace(e) == "gzip" { 503 w.Header().Set("Content-Encoding", "gzip") 504 gw := gzip.NewWriter(w) 505 gw.Write(buf) 506 gw.Close() 507 return 508 } 509 } 510 w.Write(buf) 511 } 512 513 func (c *Calls) exportCompanyClients(w http.ResponseWriter, r *http.Request) { 514 var companyID struct { 515 ID int64 `form:"id,post"` 516 } 517 err := form.Process(r, &companyID) 518 if err != nil { 519 w.WriteHeader(http.StatusBadRequest) 520 if e, ok := err.(form.ErrorMap); ok { 521 for k, v := range e { 522 fmt.Fprintf(w, "%s = %s\n", k, v) 523 } 524 } else { 525 w.Write([]byte(err.Error())) 526 } 527 return 528 } 529 var cy Company 530 err = c.GetCompany(companyID.ID, &cy) 531 if err != nil { 532 w.WriteHeader(http.StatusInternalServerError) 533 w.Write([]byte(err.Error())) 534 return 535 } 536 var cl []Client 537 err = c.ClientsForCompany(companyID.ID, &cl) 538 if err != nil { 539 w.WriteHeader(http.StatusInternalServerError) 540 w.Write([]byte(err.Error())) 541 return 542 } 543 buf := make([]byte, 0, 1024*1024) 544 ss := csv.NewWriter(memio.Create(&buf)) 545 ss.WriteAll([][]string{ 546 {"Client List for " + cy.Name}, 547 {}, 548 { 549 "Client Name", 550 "Reference", 551 "Phone Number", 552 }, 553 }) 554 for _, client := range cl { 555 ss.Write([]string{ 556 client.Name, 557 " " + client.Reference, 558 client.PhoneNumber, 559 }) 560 } 561 ss.Flush() 562 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 563 w.Header().Set("Content-Disposition", "inline; filename=\"clientList-"+cy.Name+".csv\"") 564 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 565 if strings.TrimSpace(e) == "gzip" { 566 w.Header().Set("Content-Encoding", "gzip") 567 gw := gzip.NewWriter(w) 568 gw.Write(buf) 569 gw.Close() 570 return 571 } 572 } 573 w.Write(buf) 574 } 575 576 func (c *Calls) exportCompanyList(w http.ResponseWriter, r *http.Request) { 577 var cy []Company 578 err := c.Companies(struct{}{}, &cy) 579 if err != nil { 580 w.WriteHeader(http.StatusInternalServerError) 581 if e, ok := err.(form.ErrorMap); ok { 582 for k, v := range e { 583 fmt.Fprintf(w, "%s = %s\n", k, v) 584 } 585 } else { 586 w.Write([]byte(err.Error())) 587 } 588 return 589 } 590 buf := make([]byte, 0, 1024*1024) 591 ss := csv.NewWriter(memio.Create(&buf)) 592 ss.WriteAll([][]string{ 593 {"Company List"}, 594 {}, 595 { 596 "Company Name", 597 "Address", 598 }, 599 }) 600 for _, company := range cy { 601 ss.Write([]string{ 602 company.Name, 603 company.Address, 604 }) 605 } 606 ss.Flush() 607 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 608 w.Header().Set("Content-Disposition", "inline; filename=\"companyList.csv\"") 609 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 610 if strings.TrimSpace(e) == "gzip" { 611 w.Header().Set("Content-Encoding", "gzip") 612 gw := gzip.NewWriter(w) 613 gw.Write(buf) 614 gw.Close() 615 return 616 } 617 } 618 w.Write(buf) 619 } 620 621 func (c *Calls) exportClientList(w http.ResponseWriter, r *http.Request) { 622 var cl []Client 623 err := c.Clients(struct{}{}, &cl) 624 if err != nil { 625 w.WriteHeader(http.StatusInternalServerError) 626 return 627 } 628 buf := make([]byte, 0, 1024*1024) 629 ss := csv.NewWriter(memio.Create(&buf)) 630 ss.WriteAll([][]string{ 631 {"Client List"}, 632 {}, 633 { 634 "Client Name", 635 "Company Name", 636 "Reference", 637 "Phone Number", 638 }, 639 }) 640 for _, client := range cl { 641 var cy Company 642 if err := c.GetCompany(client.CompanyID, &cy); err != nil { 643 w.WriteHeader(http.StatusInternalServerError) 644 w.Write([]byte(err.Error())) 645 return 646 } 647 ss.Write([]string{ 648 client.Name, 649 cy.Name, 650 client.Reference, 651 " " + client.PhoneNumber, 652 }) 653 } 654 ss.Flush() 655 w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 656 w.Header().Set("Content-Disposition", "inline; filename=\"clientList.csv\"") 657 for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 658 if strings.TrimSpace(e) == "gzip" { 659 w.Header().Set("Content-Encoding", "gzip") 660 gw := gzip.NewWriter(w) 661 gw.Write(buf) 662 gw.Close() 663 return 664 } 665 } 666 w.Write(buf) 667 }