516 lines
24 KiB
Go
516 lines
24 KiB
Go
package models
|
||
|
||
import "time"
|
||
|
||
type ProductSubCategory struct {
|
||
Subcatid int `json:"subcatid"`
|
||
Categoryid int `json:"categoryid,omitempty"`
|
||
Tenantid int `json:"tenantid,omitempty"`
|
||
Subcatname string `json:"subcatname,omitempty"`
|
||
Image string `json:"image,omitempty"`
|
||
Status string `json:"status,omitempty"`
|
||
Sortorder int `json:"sortorder,omitempty"`
|
||
Createdby int `json:"createdby,omitempty"`
|
||
Created time.Time `json:"created,omitempty"`
|
||
Updated time.Time `json:"updated,omitempty"`
|
||
}
|
||
|
||
type Productcount struct {
|
||
Total int `json:"total"`
|
||
Available int `json:"available"`
|
||
Outofstock int `json:"outofstock"`
|
||
}
|
||
|
||
type ProductCategory struct {
|
||
Categoryid int `json:"categoryid"`
|
||
Moduleid int `json:"moduleid"`
|
||
Tenantid int `json:"tenantid,omitempty"`
|
||
Categorytypeid int `json:"categorytypeid,omitempty"`
|
||
Categoryname string `json:"categoryname,omitempty"`
|
||
Image string `json:"image,omitempty"`
|
||
Catalougecategoryid int `json:"catalougecategoryid,omitempty"`
|
||
Sortorder int `json:"sortorder,omitempty"`
|
||
Status string `json:"status"`
|
||
Createdby int `json:"createdby,omitempty"`
|
||
Created time.Time `json:"created"`
|
||
Updated time.Time `json:"updated"`
|
||
}
|
||
|
||
// One size of one product — "500ml" hanging under the parent "Coke".
|
||
//
|
||
// The table always had the two columns that make this a relationship rather
|
||
// than a list of words: `productid` names the PARENT, `variantproductid` names
|
||
// the product a shopper actually orders when they pick this size. Neither was
|
||
// mapped here, so nothing could read or write them: `createproductvariant`
|
||
// wrote rows with no parent, and `getproductvariants` returned a flat
|
||
// per-tenant list of names. Measured 2026-09-02 across ten tenants: two rows
|
||
// existed on the whole platform, named "testing" and "demo", attached to
|
||
// nothing.
|
||
//
|
||
// The consequence is the ordering bug. With no way to attach a variant to a
|
||
// product, the app had only `products.variants` — a bare group number, 0 on
|
||
// every product — to go on.
|
||
type Productvariant struct {
|
||
Variantid int `json:"variantid" gorm:"primaryKey;autoIncrement"`
|
||
Tenantid int `json:"tenantid"`
|
||
|
||
// The parent. A variant is meaningless without one, so this is what
|
||
// AddProductVariant refuses to accept as zero.
|
||
Productid int `json:"productid"`
|
||
|
||
// The product to actually put in the basket for this size. It is a real
|
||
// product row, so it carries its own price, stock and barcode — which is
|
||
// why a variant does not need to duplicate any of them.
|
||
Variantproductid int `json:"variantproductid"`
|
||
|
||
Variantname string `json:"variantname"`
|
||
Varianttype string `json:"varianttype,omitempty"`
|
||
|
||
// A per-variant price override. 0 means "no override, use the product’s
|
||
// own price" — which is a real answer, so it is always emitted. With
|
||
// omitempty the key vanished at 0 and a client reading `price` got
|
||
// undefined, then rendered it.
|
||
Price float64 `json:"price"`
|
||
|
||
Categoryid int `json:"categoryid" gorm:"default:0"`
|
||
Categoryname string `json:"categoryname" gorm:"-"`
|
||
Subcategoryid int `json:"subcategoryid"`
|
||
Status string `json:"status" gorm:"default:Active"`
|
||
|
||
// Read-only, from the variant's own product row. The app needs a name and a
|
||
// price to draw a size picker; without these it would have to fetch each
|
||
// variant separately to render one screen.
|
||
Variantproductname string `json:"variantproductname" gorm:"->"`
|
||
Variantprice float64 `json:"variantprice" gorm:"->"`
|
||
Variantstock int `json:"variantstock" gorm:"->"`
|
||
|
||
// The size, as the product itself records it. Without these the only way to
|
||
// label a size was to join the parent’s unit fields to the variant’s name,
|
||
// which is how "null kg" reaches a screen.
|
||
Variantunitvalue string `json:"variantunitvalue" gorm:"->"`
|
||
Variantproductunit string `json:"variantproductunit" gorm:"->"`
|
||
}
|
||
|
||
type Products struct {
|
||
Productid int `json:"productid" gorm:"primaryKey;autoIncrement"`
|
||
AppLocationid int `json:"applocationid" gorm:"column:applocationid"`
|
||
Productlocationid int `json:"productlocationid" gorm:"->"`
|
||
Tenantid int `json:"tenantid,omitempty"`
|
||
Categoryid int `json:"categoryid"`
|
||
Categoryname string `json:"categoryname" gorm:"->"`
|
||
Subcategoryid int `json:"subcategoryid,omitempty"`
|
||
Subcategoryname string `json:"Subcategoryname" gorm:"->"`
|
||
Catalogueid int `json:"catalogueid,omitempty"`
|
||
// The catalogue's own stable key for this product, e.g. `cheetos_chips_2d6bf74f`.
|
||
//
|
||
// `catalogueid` cannot do this job and never could. The catalogue is
|
||
// rebuilt by scrape and every row is renumbered when it is: pepsico's live
|
||
// ids run 3, 6, 9 … 27, 30, so a product imported when it was id 26 now
|
||
// points at nothing. Eleven of the nineteen links on the platform were
|
||
// dangling this way, and none could be repaired — the re-scrape had also
|
||
// changed the pack sizes, so the product they named no longer existed.
|
||
//
|
||
// `image_id` is the key the catalogue itself deduplicates on and it
|
||
// survives both. Written on import; `catalogueid` is kept beside it for
|
||
// rows imported before this column existed, and as the id the import call
|
||
// still addresses.
|
||
Imageid string `json:"imageid,omitempty" gorm:"column:imageid"`
|
||
Addonid int `json:"addonid,omitempty"`
|
||
Discountid int `json:"discountid"`
|
||
Discountvalue float64 `json:"discountvalue"`
|
||
Pricingid int `json:"pricingid,omitempty"`
|
||
Productname string `json:"productname,omitempty"`
|
||
Productimage string `json:"productimage,omitempty"`
|
||
|
||
// Every photo the product has, as a JSON array of URLs.
|
||
//
|
||
// `Productimage` above stays the first of these and is left untouched: every
|
||
// existing reader — the store catalogue, the customer app, the POS catalogue
|
||
// pull, each order line — reads that column, and repointing them all at an
|
||
// array is a far larger change than giving the extra photos somewhere to live.
|
||
//
|
||
// Before this the import kept `Images[0]` and discarded the rest, so a product
|
||
// with ten photos in the global catalogue arrived in a shop with one. 90 of
|
||
// nestle's 123 products have more than one.
|
||
//
|
||
// Held as a string rather than a []string because GORM's raw scan-into-struct
|
||
// silently drops slice-kind destination fields — the same reason
|
||
// `catalogueProductColumns` casts its text[] columns to text.
|
||
Productimages string `json:"productimages,omitempty" gorm:"column:productimages;type:jsonb"`
|
||
|
||
Productdesc string `json:"productdesc,omitempty"`
|
||
Productsku string `json:"productsku,omitempty"`
|
||
Brandid int `json:"brandid,omitempty"`
|
||
Productbrand string `json:"productbrand,omitempty"`
|
||
Productunit string `json:"productunit"`
|
||
Unitvalue string `json:"unitvalue"`
|
||
Toppicks string `json:"toppicks,omitempty"`
|
||
Productcost float64 `json:"productcost,omitempty"`
|
||
Taxamount float64 `json:"taxamount,omitempty"`
|
||
Taxpercent float64 `json:"taxpercent,omitempty"`
|
||
Producttax int `json:"producttax" gorm:"default:0"`
|
||
Productstock int `json:"productstock" gorm:"default:0"`
|
||
Productcombo int `json:"productcombo" gorm:"default:0"`
|
||
Variants int `json:"variants" gorm:"default:0"`
|
||
|
||
// The sizes hanging under this product, so one call draws the whole screen.
|
||
//
|
||
// A separate field from `Variants` above, which is the legacy group NUMBER
|
||
// and stays exactly as it was — renaming it would break every caller, and it
|
||
// is still what an old client reads. This is the list.
|
||
//
|
||
// Always present, and EMPTY for a product with no sizes. That is the case
|
||
// that matters: an empty list is a complete answer meaning "order this one
|
||
// directly", which is what lets the app proceed instead of stalling on a
|
||
// choice that does not exist.
|
||
Variantoptions []Productvariant `json:"variantoptions" gorm:"-"`
|
||
|
||
Quantity int `json:"quantity"`
|
||
// Price is the EFFECTIVE selling price at the location a query was scoped
|
||
// to: productlocations.price when the store has set one, otherwise the
|
||
// master Retailprice below. Read-only — it is computed by the query, never
|
||
// written through this struct. Location-scoped endpoints must expose it, or
|
||
// a price the admin sets per store can never reach the customer app: they
|
||
// returned only Retailprice, which the admin catalogue never writes.
|
||
// Same meaning as Locationproducts.Price, so both product feeds agree.
|
||
Price float64 `json:"price" gorm:"->"`
|
||
Retailprice float64 `json:"retailprice"`
|
||
Diffprice float64 `json:"diffprice,omitempty"`
|
||
Diffpercent float64 `json:"diffpercent,omitempty"`
|
||
Othercost float64 `json:"othercost,omitempty"`
|
||
Approve int `json:"approve"`
|
||
Productstatus string `json:"productstatus" `
|
||
// Populated only by queries scoped to a specific location (e.g.
|
||
// GetProductByVariant when locationid is passed): Productstock becomes
|
||
// the live SUM(in)-SUM(out) balance from productstocks — the same
|
||
// formula CreateOrder's stock check uses — and Locationstatus mirrors
|
||
// productlocations.status ("outofstock"/"available") for that store.
|
||
// Left at zero values for callers that don't scope to a location.
|
||
Locationstatus string `json:"locationstatus,omitempty" gorm:"->"`
|
||
// Status string `json:"status" gorm:"default:InActive"`
|
||
// Status string `json:"status" gorm:"-"`
|
||
}
|
||
|
||
type Locationproducts struct {
|
||
Productid int `json:"productid"`
|
||
AppLocationid int `json:"applocationid" gorm:"column:applocationid"`
|
||
Productlocationid int `json:"productlocationid" gorm:"->"`
|
||
Tenantid int `json:"tenantid,omitempty"`
|
||
Categoryid int `json:"categoryid"`
|
||
Categoryname string `json:"categoryname" gorm:"->"`
|
||
Subcategoryid int `json:"subcategoryid,omitempty"`
|
||
Subcategoryname string `json:"Subcategoryname" gorm:"->"`
|
||
Catalogueid int `json:"catalogueid,omitempty"`
|
||
Addonid int `json:"addonid,omitempty"`
|
||
Discountid int `json:"discountid,omitempty"`
|
||
Pricingid int `json:"pricingid,omitempty"`
|
||
Productname string `json:"productname,omitempty"`
|
||
Productimage string `json:"productimage,omitempty"`
|
||
Productdesc string `json:"productdesc,omitempty"`
|
||
Productsku string `json:"productsku,omitempty"`
|
||
Brandid int `json:"brandid,omitempty"`
|
||
Productbrand string `json:"productbrand,omitempty"`
|
||
Productunit string `json:"productunit"`
|
||
Unitvalue string `json:"unitvalue"`
|
||
Toppicks string `json:"toppicks,omitempty"`
|
||
Productcost float64 `json:"productcost,omitempty"`
|
||
Taxamount float64 `json:"taxamount,omitempty"`
|
||
Taxpercent float64 `json:"taxpercent,omitempty"`
|
||
Producttax int `json:"producttax" gorm:"default:0"`
|
||
Productstock int `json:"productstock" gorm:"default:0"`
|
||
Productcombo int `json:"productcombo" gorm:"default:0"`
|
||
Variants int `json:"variants" gorm:"default:0"`
|
||
Quantity int `json:"quantity"`
|
||
// Price is the per-store selling price from productlocations.price — the one
|
||
// CreateProductLocation upserts. Read-only here: it comes from the joined
|
||
// productlocations row, not from products. Without it a store could set a
|
||
// price and never read it back, so the UI always showed the master price.
|
||
Price float64 `json:"price" gorm:"->"`
|
||
Retailprice float64 `json:"retailprice"`
|
||
Diffprice float64 `json:"diffprice,omitempty"`
|
||
Diffpercent float64 `json:"diffpercent,omitempty"`
|
||
Othercost float64 `json:"othercost,omitempty"`
|
||
Approve int `json:"approve" gorm:"default:0"`
|
||
// Productstatus string `json:"productstatus" gorm:"default:available"`
|
||
Status string `json:"status" gorm:"default:outofstock"`
|
||
|
||
// Set only when the admin has released this product to the shops; NULL
|
||
// while it sits in the admin catalogue awaiting a price. This — not
|
||
// `Status` — is what a store view filters on. See models.Productlocations.
|
||
Publishedat *time.Time `json:"publishedat"`
|
||
}
|
||
|
||
type Productstocks struct {
|
||
Productstockid int `json:"productstockid" gorm:"Primary_Key"`
|
||
Locationid int `json:"locationid"`
|
||
Tenantid int `json:"tenantid"`
|
||
Stockdate string `json:"stockdate"`
|
||
Productid int `json:"productid"`
|
||
Quantity int `json:"quantity"`
|
||
Stocktype string `json:"stocktype"`
|
||
Status string `json:"status"`
|
||
AppLocationid int `json:"applocationid"`
|
||
Categoryid int `json:"categoryid"`
|
||
Categoryname string `json:"categoryname" gorm:"->"`
|
||
Subcategoryid int `json:"subcategoryid,omitempty"`
|
||
Subcategoryname string `json:"Subcategoryname" `
|
||
Productname string `json:"productname,omitempty"`
|
||
Productimage string `json:"productimage,omitempty"`
|
||
Productdesc *string `json:"productdesc,omitempty"`
|
||
Productsku *string `json:"productsku,omitempty"`
|
||
Brandid *int `json:"brandid,omitempty"`
|
||
Productbrand *string `json:"productbrand,omitempty"`
|
||
Productunit *string `json:"productunit,omitempty"`
|
||
Unitvalue *string `json:"unitvalue,omitempty"`
|
||
Toppicks *string `json:"toppicks,omitempty"`
|
||
Productcost *float64 `json:"productcost,omitempty"`
|
||
Taxamount *float64 `json:"taxamount,omitempty"`
|
||
Taxpercent *float64 `json:"taxpercent,omitempty"`
|
||
Producttax *int `json:"producttax,omitempty"`
|
||
Productstock *int `json:"productstock,omitempty"`
|
||
Productcombo *int `json:"productcombo,omitempty"`
|
||
Variants int `json:"variants"`
|
||
Retailprice float64 `json:"retailprice,omitempty"`
|
||
Diffprice float64 `json:"diffprice,omitempty"`
|
||
Diffpercent float64 `json:"diffpercent,omitempty"`
|
||
Othercost float64 `json:"othercost,omitempty"`
|
||
Approve *int `json:"approve"`
|
||
}
|
||
|
||
type Productstock struct {
|
||
Productstockid int `json:"productstockid" gorm:"Primary_Key"`
|
||
Tenantid int `json:"tenantid"`
|
||
Stockdate time.Time `json:"stockdate"`
|
||
Locationid int `json:"locationid"`
|
||
Productid int `json:"productid"`
|
||
Quantity int `json:"quantity"`
|
||
Stocktype string `json:"stocktype"`
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
type Productstockstatement struct {
|
||
Productid int `json:"productid"`
|
||
Productname string `json:"productname"`
|
||
Productimage string `json:"productimage"`
|
||
Categoryid int `json:"categoryid"`
|
||
Subcategoryid int `json:"subcategoryid"`
|
||
Productunit string `json:"productunit"`
|
||
Unitvalue string `json:"unitvalue"`
|
||
Productcost float32 `json:"productcost"`
|
||
Taxpercent float32 `json:"taxpercent"`
|
||
Taxamount float32 `json:"taxamount"`
|
||
Retailprice float32 `json:"retailprice"`
|
||
Tenantid int `json:"tenantid"`
|
||
Locationid int `json:"locationid"`
|
||
Opening int `json:"opening"`
|
||
Credit int `json:"credit"`
|
||
Debit int `json:"debit"`
|
||
Closing int `json:"closing"`
|
||
}
|
||
|
||
type ProductSummary struct {
|
||
Subcategoryid int `json:"subcategoryid"`
|
||
Subcategroyname string `json:"subcategroyname"`
|
||
Image string `json:"image"`
|
||
Productcount int `json:"productcount"`
|
||
}
|
||
|
||
type Tenantproducts struct {
|
||
Tenant TenantInfo `json:"tenant"`
|
||
Products []Products `json:"products"`
|
||
// Locationproducts []Locationproducts `json:"locationproducts"`
|
||
}
|
||
|
||
type TenantInfo struct {
|
||
Tenantid int `json:"tenantid"`
|
||
Tenantname string `json:"tenantname"`
|
||
Userfcmtoken string `json:"userfcmtoken"`
|
||
Address string `json:"address"`
|
||
Licenseno string `json:"licenseno"`
|
||
Primaryemail string `json:"primaryemail"`
|
||
Primarycontact string `json:"primarycontact"`
|
||
Pickuplocationid int `json:"pickuplocationid"`
|
||
Applocationid int `json:"applocationid"`
|
||
Suburb string `json:"suburb"`
|
||
City string `json:"city"`
|
||
Latitude string `json:"latitude"`
|
||
Longitude string `json:"longitude"`
|
||
Postcode string `json:"postcode"`
|
||
Tenantimage string `json:"tenantimage"`
|
||
Locationid int `json:"locationid"`
|
||
Locationname string `json:"locationname"`
|
||
Subcategoryid int `json:"subcategoryid"`
|
||
Categoryid int `json:"categoryid"`
|
||
Registrationno string `json:"registrationno"`
|
||
Orderscount int `json:"orderscount"`
|
||
// Products []Products `json:"products" gorm:"-"`
|
||
ProductSubcategory []ProductSubcategory `json:"productsubcategory" gorm:"-"`
|
||
}
|
||
|
||
type SubcategoryProductResponse struct {
|
||
SubcategoryID int `json:"subcategoryid"`
|
||
SubcategoryName string `json:"subcategoryname"`
|
||
Image string `json:"image"`
|
||
Products []Products `json:"products"`
|
||
}
|
||
|
||
type ProductFilter struct {
|
||
CategoryID int
|
||
TenantID int
|
||
AppLocationID int
|
||
ProductID int
|
||
Keyword string
|
||
LocationID int
|
||
}
|
||
|
||
type Subcategory struct {
|
||
Subcategoryid int `json:"subcategoryid" gorm:"column:subcatid"`
|
||
Subcategoryname string `json:"subcategoryname" gorm:"column:subcatname"`
|
||
Categoryid int `json:"categoryid" gorm:"column:categoryid"`
|
||
Image string `json:"image" gorm:"column:image"`
|
||
}
|
||
|
||
// TenantCategory is a categoryid actually in use by a tenant's own products,
|
||
// with a best-effort name. Used instead of the global productcategories list
|
||
// for the import category picker, since that master table is missing rows
|
||
// for categoryids that are nonetheless in real use (e.g. categoryid 2).
|
||
type TenantCategory struct {
|
||
Categoryid int `json:"categoryid"`
|
||
Categoryname string `json:"categoryname"`
|
||
}
|
||
|
||
// ImportedCatalogueRef identifies a catalogue product a tenant has already
|
||
// imported. Brand is always included, even when a caller filtered by a
|
||
// single brand, because a bare catalogueid is ambiguous across brand tables.
|
||
type ImportedCatalogueRef struct {
|
||
Brand string `json:"brand"`
|
||
Catalogueid int64 `json:"catalogueid"`
|
||
// The stable key, when the product carries one.
|
||
//
|
||
// A browse screen should match on this in preference to the id: the id is
|
||
// renumbered by every re-scrape, so a tick placed by catalogueid lands on
|
||
// whatever product now holds that number, or on nothing at all. Empty for a
|
||
// product imported before the column existed.
|
||
Imageid string `json:"imageid"`
|
||
}
|
||
|
||
// ImportCatalogueProductRequest is the payload for importing a product from
|
||
// the global catalogue (CatalogueDB) into a tenant's own store catalogue.
|
||
// Brand+Catalogueid is the bridge key back to CatalogueDB: a catalogue row's
|
||
// bare id is only unique within its brand table, so both are required.
|
||
type ImportCatalogueProductRequest struct {
|
||
Tenantid int `json:"tenantid"`
|
||
Locationid int `json:"locationid"`
|
||
Brand string `json:"brand"`
|
||
Catalogueid int64 `json:"catalogueid"`
|
||
Categoryid int `json:"categoryid"`
|
||
Subcategoryid int `json:"subcategoryid"`
|
||
Quantity int `json:"quantity"`
|
||
Stocktype string `json:"stocktype"`
|
||
Status string `json:"status"`
|
||
Retailprice float64 `json:"retailprice"`
|
||
Productcost float64 `json:"productcost"`
|
||
Taxpercent float64 `json:"taxpercent"`
|
||
}
|
||
|
||
type Productlocations struct {
|
||
Productlocationid int `json:"productlocationid" gorm:"Primary_Key"`
|
||
Tenantid int `json:"tenantid"`
|
||
Locationid int `json:"locationid"`
|
||
Productid int `json:"productid"`
|
||
Catlougeid int `json:"catlougeid"`
|
||
Minquantity int `json:"minquantity" gorm:"default:0"`
|
||
Maxquantity int `json:"maxquantity" gorm:"default:0"`
|
||
Price float32 `json:"price" gorm:"default:0.0"`
|
||
Quantity int `json:"quantity" gorm:"<-:false"`
|
||
Stocktype string `json:"stocktype" gorm:"<-:false"`
|
||
Status string `json:"status"`
|
||
|
||
// When this product was released to the store, and the only thing that
|
||
// decides whether a store user can see it.
|
||
//
|
||
// NULL means the admin has imported it but not published it: it belongs to
|
||
// the admin catalogue alone. Deliberately NOT another `Status` value —
|
||
// syncProductLocationStatus rewrites that column on every stock movement,
|
||
// which is why the import's 'Draft' survived on one row out of 6,755.
|
||
//
|
||
// Read-only through this struct (`<-:false`). It is set by Publish and
|
||
// cleared by Unpublish, never as a side effect of an ordinary
|
||
// product-location write: setting a price must not be able to release a
|
||
// product to every shop in the tenant.
|
||
Publishedat *time.Time `json:"publishedat" gorm:"column:publishedat;<-:false"`
|
||
}
|
||
|
||
// ProductLocationRef identifies a single (tenant, location, product) row in
|
||
// productlocations — used to reactivate it after new stock arrives, the
|
||
// per-location counterpart to CreateOrder's outofstock flag.
|
||
type ProductLocationRef struct {
|
||
Tenantid int
|
||
Locationid int
|
||
Productid int
|
||
}
|
||
|
||
// SaleTemplateRow is one line of the downloadable offline-sales spreadsheet: a
|
||
// product stocked at one branch, with the numbers the person at the till needs
|
||
// to see before typing a sold quantity against it.
|
||
//
|
||
// Tenantid and Locationid ride on every row because one workbook covers every
|
||
// branch a merchant runs. The row's own Locationid decides which branch's stock
|
||
// its sale comes out of — a tenant-level import would be wrong, since the same
|
||
// product is held separately at each outlet.
|
||
//
|
||
// Productid is the only field that identifies the product. It cannot be
|
||
// productsku: across the live catalogue 6,245 products share just 93 distinct
|
||
// sku values (one tenant has 463 products all carrying sku "1"), and 154 are
|
||
// blank, so a sku is not a key. Productname is nearly unique per tenant but not
|
||
// reliably ("rice" appears 6 times for one tenant), so it travels as a
|
||
// human-readable confirmation only and is never matched on. That is why the
|
||
// spreadsheet has to be generated from this endpoint rather than typed from
|
||
// scratch — productid and locationid are filled in for the user.
|
||
type SaleTemplateRow struct {
|
||
Tenantid int `json:"tenantid"`
|
||
Locationid int `json:"locationid"`
|
||
Locationname string `json:"locationname"`
|
||
Productid int `json:"productid"`
|
||
Productname string `json:"productname"`
|
||
Productunit string `json:"productunit"`
|
||
Unitvalue string `json:"unitvalue"`
|
||
Categoryname string `json:"categoryname"`
|
||
Currentstock int `json:"currentstock"`
|
||
Price float64 `json:"price"`
|
||
Taxpercent float64 `json:"taxpercent"`
|
||
}
|
||
|
||
// SaleTemplateLocation is one branch covered by the workbook, so the sheet can
|
||
// list what it spans and the UI can summarise it without walking every row.
|
||
type SaleTemplateLocation struct {
|
||
Locationid int `json:"locationid"`
|
||
Locationname string `json:"locationname"`
|
||
Productcount int `json:"productcount"`
|
||
}
|
||
|
||
// SaleTemplate is the payload the web app turns into an .xlsx workbook.
|
||
//
|
||
// Locationid is 0 when the template spans every branch of the tenant, which is
|
||
// the normal case for an owner or admin. A store user gets a template for their
|
||
// own branch only, and it is then the single entry in Locations.
|
||
type SaleTemplate struct {
|
||
Tenantid int `json:"tenantid"`
|
||
Locationid int `json:"locationid"`
|
||
Locations []SaleTemplateLocation `json:"locations"`
|
||
Products []SaleTemplateRow `json:"products"`
|
||
}
|
||
|
||
type ProductSubcategory struct {
|
||
Subcatid int `json:"subcatid"`
|
||
Categoryid int `json:"categoryid"`
|
||
Tenantid int `json:"tenantid"`
|
||
Subcatname string `json:"subcatname"`
|
||
Status string `json:"status"`
|
||
Sortorder int `json:"sortorder"`
|
||
Createdby int `json:"createdby"`
|
||
Created time.Time `json:"created"`
|
||
Updated time.Time `json:"updated"`
|
||
Image string `json:"image"`
|
||
}
|