132 lines
4.3 KiB
Go
132 lines
4.3 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"nearle/models"
|
|
)
|
|
|
|
// "Compare online and counter sales."
|
|
//
|
|
// Two ledgers, not two columns of one. App orders live in `orders`; counter
|
|
// bills arrive from the tills and live in the POS tables. Nothing reconciles
|
|
// them, and the console's own Sales page says so in as many words — so this
|
|
// tool reports them side by side and states plainly that they are separate
|
|
// books. A single "total sales" figure invented by adding them would be the
|
|
// most useful-looking wrong number on the page.
|
|
//
|
|
// ── The model never sends a date ────────────────────────────────────────────
|
|
//
|
|
// It sends a number of days, and the range is computed here. Dates are the
|
|
// thing models get wrong most reliably — a format, a timezone, an off-by-one on
|
|
// "last week" — and every one of those errors produces a plausible figure for
|
|
// the wrong period, which is undetectable in a sentence.
|
|
|
|
// RevenueReader is the app-order side.
|
|
type RevenueReader interface {
|
|
GetRevenueSummary(tid, lid int, fdate, tdate string) (*models.TenantRevenueSummary, error)
|
|
}
|
|
|
|
// CounterReader is the till side.
|
|
type CounterReader interface {
|
|
SalesSummary(f models.PosSalesFilter) (*models.PosSalesSummary, error)
|
|
}
|
|
|
|
// ChannelTotals is one channel's takings over the period.
|
|
type ChannelTotals struct {
|
|
Channel string `json:"channel"`
|
|
Revenue float64 `json:"revenue"`
|
|
// Bills only exist on the counter side; app orders are counted by the
|
|
// revenue read, which returns no order count. Omitted rather than reported
|
|
// as 0, which would read as "no orders".
|
|
Bills int `json:"bills,omitempty"`
|
|
Ledger string `json:"ledger"`
|
|
Caveat string `json:"caveat,omitempty"`
|
|
}
|
|
|
|
// defaultChannelDays is the period when nobody says otherwise.
|
|
//
|
|
// Seven, so the answer covers a full trading week including the weekend. A
|
|
// single day makes every Monday look like a collapse.
|
|
const defaultChannelDays = 7
|
|
|
|
// SalesByChannel builds the tool.
|
|
func SalesByChannel(orders RevenueReader, counter CounterReader, now func() time.Time) Tool {
|
|
if now == nil {
|
|
now = time.Now
|
|
}
|
|
|
|
return Tool{
|
|
Name: "sales_by_channel",
|
|
Description: "App order revenue and counter (till) takings side by side for one branch, over a recent period. " +
|
|
"Use for questions comparing online and counter sales, or asking which channel is bigger. " +
|
|
"The two come from separate ledgers and are never added together.",
|
|
Needs: RequiresBranch,
|
|
Scope: ScopeRead,
|
|
Schema: Schema{Fields: []Field{{
|
|
Name: "days",
|
|
Description: "How many days back to cover, ending today. Defaults to 7.",
|
|
Kind: KindInt,
|
|
Min: 1,
|
|
Max: 365,
|
|
Default: defaultChannelDays,
|
|
}}},
|
|
Handler: func(_ context.Context, req Request) (Result, error) {
|
|
days := req.Int("days")
|
|
if days <= 0 {
|
|
days = defaultChannelDays
|
|
}
|
|
to := now()
|
|
from := to.AddDate(0, 0, -(days - 1))
|
|
fromDate, toDate := from.Format("2006-01-02"), to.Format("2006-01-02")
|
|
|
|
rows := make([]ChannelTotals, 0, 2)
|
|
|
|
revenue, err := orders.GetRevenueSummary(req.Caller.Tenantid, req.Caller.Locationid, fromDate, toDate)
|
|
if err != nil {
|
|
return Result{}, err
|
|
}
|
|
if revenue != nil {
|
|
rows = append(rows, ChannelTotals{
|
|
Channel: "App orders",
|
|
Revenue: revenue.OverallRevenue,
|
|
Ledger: "orders",
|
|
})
|
|
}
|
|
|
|
bills, err := counter.SalesSummary(models.PosSalesFilter{
|
|
Locationid: req.Caller.Locationid,
|
|
Fromdate: fromDate,
|
|
Todate: toDate,
|
|
})
|
|
if err != nil {
|
|
return Result{}, err
|
|
}
|
|
if bills != nil {
|
|
rows = append(rows, ChannelTotals{
|
|
Channel: "Counter",
|
|
Revenue: bills.Grosssales,
|
|
Bills: bills.Billcount,
|
|
Ledger: "pos",
|
|
// Gross, so it is not comparable like for like with the app
|
|
// figure without saying so. Better stated on the row than
|
|
// left for a reader to assume either way.
|
|
Caveat: "Gross, before discount and round-off.",
|
|
})
|
|
}
|
|
|
|
return Result{
|
|
Rows: rows,
|
|
Count: len(rows),
|
|
Source: "/admin/sales",
|
|
Scope: "this branch",
|
|
Note: fmt.Sprintf(
|
|
"%s to %s. These are two separate ledgers — report them side by side and do not add them into one total.",
|
|
fromDate, toDate),
|
|
}, nil
|
|
},
|
|
}
|
|
}
|