Compare commits
3 Commits
d4cbf92661
...
5d9879657b
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d9879657b | |||
| 84dfa8e640 | |||
| 7db81c0e68 |
@@ -25,7 +25,7 @@ type TenantRepository interface {
|
|||||||
GetStaffs(tid int) ([]models.StaffInfo, error)
|
GetStaffs(tid int) ([]models.StaffInfo, error)
|
||||||
CreateStaff(user models.User) error
|
CreateStaff(user models.User) error
|
||||||
UpdateStaff(user models.User) error
|
UpdateStaff(user models.User) error
|
||||||
CreateTenantLocation(data models.Tenantlocations) error
|
CreateTenantLocation(data models.Tenantlocations) (models.Tenantlocations, error)
|
||||||
UpdateTenantLocation(data models.Tenantlocations) error
|
UpdateTenantLocation(data models.Tenantlocations) error
|
||||||
CheckTenantByNo(cno string) int
|
CheckTenantByNo(cno string) int
|
||||||
CreateTenantUser(data models.Tenants) (bool, error)
|
CreateTenantUser(data models.Tenants) (bool, error)
|
||||||
@@ -346,7 +346,7 @@ func (r *tenantRepository) UpdateStaff(user models.User) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tenantRepository) CreateTenantLocation(data models.Tenantlocations) error {
|
func (r *tenantRepository) CreateTenantLocation(data models.Tenantlocations) (models.Tenantlocations, error) {
|
||||||
var user models.Tenantuser
|
var user models.Tenantuser
|
||||||
tx := r.db.Begin()
|
tx := r.db.Begin()
|
||||||
|
|
||||||
@@ -360,10 +360,12 @@ func (r *tenantRepository) CreateTenantLocation(data models.Tenantlocations) err
|
|||||||
data.Status = "Active"
|
data.Status = "Active"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Insert into tenantlocations
|
// Step 1: Insert into tenantlocations. GORM writes the DB-assigned
|
||||||
|
// locationid back onto data, which callers need to build the store's
|
||||||
|
// QR code (payload is just {tenantid, locationid}) right after onboarding.
|
||||||
if err := tx.Create(&data).Error; err != nil {
|
if err := tx.Create(&data).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return err
|
return models.Tenantlocations{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Insert into app_users
|
// Step 2: Insert into app_users
|
||||||
@@ -389,15 +391,15 @@ func (r *tenantRepository) CreateTenantLocation(data models.Tenantlocations) err
|
|||||||
|
|
||||||
if err := tx.Table("app_users").Create(&user).Error; err != nil {
|
if err := tx.Table("app_users").Create(&user).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return err
|
return models.Tenantlocations{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit
|
// Commit
|
||||||
if err := tx.Commit().Error; err != nil {
|
if err := tx.Commit().Error; err != nil {
|
||||||
return err
|
return models.Tenantlocations{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tenantRepository) UpdateTenantLocation(input models.Tenantlocations) error {
|
func (r *tenantRepository) UpdateTenantLocation(input models.Tenantlocations) error {
|
||||||
|
|||||||
146
scratch/backfill_location_coordinates.go
Normal file
146
scratch/backfill_location_coordinates.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// One-off backfill for tenants/tenantlocations rows that were onboarded
|
||||||
|
// before the admin console geocoded addresses automatically, so their
|
||||||
|
// latitude/longitude columns were left blank. Geocodes each row's existing
|
||||||
|
// address via OpenStreetMap Nominatim (same keyless provider
|
||||||
|
// AddressAutocomplete.tsx already uses on the frontend) and writes the
|
||||||
|
// result back. Run manually: `go run scratch/backfill_location_coordinates.go`
|
||||||
|
// — do not wire this into any request path, it's a single pass over
|
||||||
|
// historical rows.
|
||||||
|
const nominatimURL = "https://nominatim.openstreetmap.org/search"
|
||||||
|
|
||||||
|
// Nominatim's usage policy caps free lookups at ~1 request/sec.
|
||||||
|
const rateLimit = 1100 * time.Millisecond
|
||||||
|
|
||||||
|
type nominatimRow struct {
|
||||||
|
Lat string `json:"lat"`
|
||||||
|
Lon string `json:"lon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func geocode(client *http.Client, address string) (lat, lon string, ok bool) {
|
||||||
|
q := url.Values{}
|
||||||
|
q.Set("format", "json")
|
||||||
|
q.Set("limit", "1")
|
||||||
|
q.Set("q", address)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", nominatimURL+"?"+q.Encode(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
// Required by Nominatim's usage policy — identifies the calling app.
|
||||||
|
req.Header.Set("User-Agent", "fiesta-backend-backfill/1.0 (care@nearle.in)")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(" geocode request error: %v\n", err)
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var rows []nominatimRow
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&rows); err != nil || len(rows) == 0 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return rows[0].Lat, rows[0].Lon, true
|
||||||
|
}
|
||||||
|
|
||||||
|
type row struct {
|
||||||
|
id int
|
||||||
|
address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func backfillTable(db *sql.DB, client *http.Client, table, idCol string) {
|
||||||
|
fmt.Printf("\n=== %s ===\n", table)
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT %s,
|
||||||
|
TRIM(BOTH ', ' FROM CONCAT_WS(', ', address, suburb, city, state, postcode))
|
||||||
|
FROM %s
|
||||||
|
WHERE (latitude IS NULL OR latitude = '' OR latitude = '0')
|
||||||
|
AND (address IS NOT NULL AND address <> '')`, idCol, table)
|
||||||
|
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s: query error: %v", table, err)
|
||||||
|
}
|
||||||
|
var targets []row
|
||||||
|
for rows.Next() {
|
||||||
|
var r row
|
||||||
|
if err := rows.Scan(&r.id, &r.address); err != nil {
|
||||||
|
log.Fatalf("%s: scan error: %v", table, err)
|
||||||
|
}
|
||||||
|
targets = append(targets, r)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Found %d row(s) missing coordinates.\n", len(targets))
|
||||||
|
|
||||||
|
updated, skipped := 0, 0
|
||||||
|
for i, r := range targets {
|
||||||
|
if i > 0 {
|
||||||
|
time.Sleep(rateLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, lon, ok := geocode(client, r.address)
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf(" [%s=%d] %q — geocode FAILED, left untouched\n", idCol, r.id, r.address)
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := db.Exec(
|
||||||
|
fmt.Sprintf(`UPDATE %s SET latitude = $1, longitude = $2 WHERE %s = $3`, table, idCol),
|
||||||
|
lat, lon, r.id,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(" [%s=%d] update error: %v\n", idCol, r.id, err)
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n, _ := res.RowsAffected()
|
||||||
|
if n > 0 {
|
||||||
|
fmt.Printf(" [%s=%d] %q -> lat=%s lon=%s\n", idCol, r.id, r.address, lat, lon)
|
||||||
|
updated++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s: updated=%d skipped=%d\n", table, updated, skipped)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsn := "host=66.116.207.225 port=5433 user=admin password=Package@123# dbname=nearledb sslmode=disable"
|
||||||
|
db, err := sql.Open("pgx", dsn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("open error: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
log.Fatalf("ping error: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Connected to nearledb.")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
|
backfillTable(db, client, "tenants", "tenantid")
|
||||||
|
backfillTable(db, client, "tenantlocations", "locationid")
|
||||||
|
|
||||||
|
fmt.Println(strings.Repeat("-", 40))
|
||||||
|
fmt.Println("Done.")
|
||||||
|
}
|
||||||
@@ -107,7 +107,7 @@ func (s *tenantService) UpdateStaff(user models.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *tenantService) CreateTenantLocation(data models.Tenantlocations) map[string]interface{} {
|
func (s *tenantService) CreateTenantLocation(data models.Tenantlocations) map[string]interface{} {
|
||||||
err := s.repo.CreateTenantLocation(data)
|
created, err := s.repo.CreateTenantLocation(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"code": http.StatusConflict,
|
"code": http.StatusConflict,
|
||||||
@@ -116,10 +116,14 @@ func (s *tenantService) CreateTenantLocation(data models.Tenantlocations) map[st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "details" carries back the DB-assigned locationid so the frontend can
|
||||||
|
// build the store's QR code (tenantid+locationid) immediately after
|
||||||
|
// onboarding, instead of having to look the new location up separately.
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"code": http.StatusCreated,
|
"code": http.StatusCreated,
|
||||||
"message": "Tenant Location Successfully Created",
|
"message": "Tenant Location Successfully Created",
|
||||||
"status": true,
|
"status": true,
|
||||||
|
"details": created,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,5 +48,25 @@ func (s *utilsService) GetAppConfig(configID int) (models.Appconfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *utilsService) GetAppCategory() ([]models.AppCategory, error) {
|
func (s *utilsService) GetAppCategory() ([]models.AppCategory, error) {
|
||||||
return s.repo.GetAppCategory()
|
categories, err := s.repo.GetAppCategory()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// "All" is a synthetic pseudo-category, not a real app_category row — it
|
||||||
|
// must never be inserted into app_category itself, since product-listing
|
||||||
|
// filters (e.g. ProductController.GetAllProducts) already treat
|
||||||
|
// categoryid=0 as "no category filter". Keeping this row's id at 0 reuses
|
||||||
|
// that existing convention instead of adding a new one.
|
||||||
|
all := models.AppCategory{
|
||||||
|
Categoryid: 0,
|
||||||
|
Categoryname: "All",
|
||||||
|
Categorytype: 7,
|
||||||
|
Sortorder: 0,
|
||||||
|
Crossaxis: 1,
|
||||||
|
Mainaxis: 1,
|
||||||
|
Status: "Active",
|
||||||
|
}
|
||||||
|
|
||||||
|
return append([]models.AppCategory{all}, categories...), nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user