package models // Scan-to-order. // // A customer points the app at a packet, Google Lens (on the phone) reads a // label off it, and the app asks: "which of MY shops has this, in what sizes, // and which one should I buy from?" These are the shapes on both sides of // that conversation. // ScanLookupRequest is what the app sends once Lens has produced a label. type ScanLookupRequest struct { Customerid int `json:"customerid"` // What Lens read: "Milk Bikis", "Dabur Honey 500g". Free text, trimmed // and capped by the service. Label string `json:"label"` // Where the customer is right now. Optional: without it the customer's // saved primary address is used, and without that stores are listed in // registration order with no distance. Latitude FlexibleString `json:"latitude"` Longitude FlexibleString `json:"longitude"` // The tenants the app believes the customer has scanned into. Optional and // never trusted on its own: the server intersects it with the // tenantcustomers table and reports anything it dropped. Tenantids []int `json:"tenantids"` // How many stores to return. 0 = all registered stores that stock it. Limit int `json:"limit"` } // ScanStore is one of the customer's registered outlets. type ScanStore struct { Tenantid int `json:"tenantid"` Tenantname string `json:"tenantname"` Locationid int `json:"locationid"` Locationname string `json:"locationname"` Address string `json:"address,omitempty"` Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` // Kilometres from the customer, or -1 when either side has no usable // coordinates. Never omitted: a missing number is easy to misread as 0. DistanceKm float64 `json:"distance_km"` // Delivery reach in the outlet's own units, straight from tenantlocations. Deliveryradius int `json:"deliveryradius"` Deliverymins int `json:"deliverymins"` Open bool `json:"open"` } // ScanOption is one thing the customer can actually put in the basket at one // store: the matched product itself, or one of its sizes. Each is a real // product row with its own price and stock, which is why they are flat. type ScanOption struct { Productid int `json:"productid"` Productname string `json:"productname"` Size string `json:"size"` // "500 g", "1 kg" — unitvalue + productunit Price float64 `json:"price"` Stock int `json:"stock"` Available bool `json:"available"` Image string `json:"image,omitempty"` // Is this the product that matched, or a size hanging under it? IsVariant bool `json:"is_variant"` Variantname string `json:"variantname,omitempty"` // How the row was tied back to the catalogue: "imageid", // "brand+catalogueid", "name" or "variant-of:". MatchedBy string `json:"matched_by"` } // ScanStoreOffer is one store and what it can sell. type ScanStoreOffer struct { ScanStore // Nearest store with at least one option in stock. Exactly one offer // carries this, and only when something is in stock somewhere. Recommended bool `json:"recommended"` // Any option in stock here. Available bool `json:"available"` Options []ScanOption `json:"options"` } // ScanCatalogueMatch is what the catalogue search settled on. type ScanCatalogueMatch struct { Brand string `json:"brand"` Catalogueid int64 `json:"catalogueid"` Imageid string `json:"imageid,omitempty"` ProductName string `json:"product_name"` Title string `json:"title,omitempty"` Category string `json:"category,omitempty"` Size string `json:"size,omitempty"` VariantKey string `json:"variant_key,omitempty"` Image string `json:"image,omitempty"` Score float64 `json:"score"` // "vector", "vector+text" or "text" — how the score was produced. The app // can be more cautious with a text-only match. Method string `json:"method"` } // ScanLookupResponse is the answer to a scan. type ScanLookupResponse struct { Label string `json:"label"` // The best catalogue product for the label, and the sizes of it the // catalogue knows about (each a separate catalogue row). Match *ScanCatalogueMatch `json:"match"` Variants []ScanCatalogueMatch `json:"catalogue_variants"` // 0..1. Below ~0.5 the app should confirm with the customer before // showing prices. Confidence float64 `json:"confidence"` // Registered stores that stock the product, nearest first, in-stock // first. Empty with Available=false when none does. Stores []ScanStoreOffer `json:"stores"` Available bool `json:"available"` // The locationid of the store marked Recommended, or 0. RecommendedLocationid int `json:"recommended_locationid"` // Tenant ids the app sent that the customer is not actually registered // with. Empty normally; non-empty means the app's local list is stale. UnregisteredTenantids []int `json:"unregistered_tenantids,omitempty"` Message string `json:"message"` } // ScanConfirmRequest is sent when the customer taps a store and a size. type ScanConfirmRequest struct { Customerid int `json:"customerid"` Tenantid int `json:"tenantid"` Locationid int `json:"locationid"` Productid int `json:"productid"` Quantity int `json:"quantity"` Latitude FlexibleString `json:"latitude"` Longitude FlexibleString `json:"longitude"` } // ScanConfirmResponse says whether the pick still holds, and where to go if // it does not. type ScanConfirmResponse struct { Ok bool `json:"ok"` // "in_stock", "insufficient_stock", "out_of_stock", "not_sold_here", // "store_not_registered". Reason string `json:"reason"` Store *ScanStore `json:"store,omitempty"` Option *ScanOption `json:"option,omitempty"` Requested int `json:"requested"` // The next-nearest registered store with enough of the same product, when // the chosen one has run out. Nil when there is none. Alternative *ScanStoreOffer `json:"alternative,omitempty"` Message string `json:"message"` }