Files
backend_fiesta/services/tools/approvestock.go
2026-09-23 17:26:13 +05:30

125 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package tools
import (
"context"
"fmt"
"strconv"
"nearle/models"
)
// "Approve that stock request" — the first thing Buddy can change.
//
// Chosen to be first because it is the smallest honest write in the product: one
// column, one row, reversible by a human in the same screen, and it pairs with a
// read the assistant already has. Somebody asks what is waiting, Buddy lists it,
// they say approve the one for rice, and a card appears naming that request.
//
// ── Ownership is re-derived, never taken from the argument ──────────────────
//
// The model supplies a request id and nothing else. Both halves look that id up
// among THIS tenant's pending requests and refuse if it is not there — so an id
// belonging to another merchant is not found rather than approved, and the check
// is a lookup rather than a comparison somebody could forget to make.
// ApprovalWriter is the write half. Split from ApprovalReader so a tool that
// only lists cannot be handed the ability to change anything.
type ApprovalWriter interface {
UpdateStockRequest(requestID int, status string) error
}
// stockApprovedStatus is the word the column expects.
//
// Capitalised, matching `Pending`, because the repository compares it as given.
// A lowercase "approved" would write a status nothing else recognises — the
// request would leave the pending list and arrive nowhere.
const stockApprovedStatus = "Approved"
// ApproveStockRequest builds the tool.
func ApproveStockRequest(requests ApprovalReader, writer ApprovalWriter) Tool {
find := func(tenantid, locationid, requestid int) (models.StockRequest, error) {
rows, err := requests.GetStockRequests(tenantid, locationid, "Pending", "", 1, 500)
if err != nil {
return models.StockRequest{}, err
}
for _, row := range rows {
if row.Requestid == requestid {
return row, nil
}
}
// One message for "does not exist", "belongs to another shop" and
// "somebody already approved it". Deliberately: the first two must not
// be distinguishable, or this becomes a way to ask whether a given id
// exists somewhere else on the platform.
return models.StockRequest{}, fmt.Errorf(
"stock request %d is not waiting for approval in this shop — it may already have been approved", requestid)
}
return WriteTool(Tool{
Name: "approve_stock_request",
Description: "Approve one stock request that a branch is waiting on, so the stock reaches their shelf. " +
"Use when somebody asks to approve, allow or release a specific request. " +
"This does not happen immediately — it produces a confirmation for the person to check first.",
Schema: Schema{Fields: []Field{{
Name: "requestid",
Description: "The id of the request to approve, from the pending approvals list.",
Kind: KindInt,
Required: true,
Min: 1,
}}},
},
// Propose: resolve the id into something a person can check, and refuse
// now rather than after they have pressed the button.
func(_ context.Context, req Request) (Proposal, error) {
id := req.Int("requestid")
row, err := find(req.Caller.Tenantid, req.Caller.Locationid, id)
if err != nil {
return Proposal{}, err
}
proposal := Proposal{
Summary: fmt.Sprintf("Approve %d × %s for %s", row.Qty, row.Productname, row.Locationname),
Details: []ProposalDetail{
// Id and name together. An id alone cannot be checked by a
// person; a name alone is ambiguous when two branches ask
// for the same product.
{Label: "Request", Value: "#" + strconv.Itoa(row.Requestid)},
{Label: "Product", Value: row.Productname},
{Label: "Quantity", Value: strconv.Itoa(row.Qty)},
{Label: "Branch", Value: row.Locationname},
{Label: "Requested", Value: row.Created.Format("2 January 2006")},
},
// The resolved id, sealed into the card. The model's arguments
// are not carried forward — only what this lookup confirmed.
args: map[string]any{"requestid": row.Requestid},
}
if row.Qty > 500 {
proposal.Warning = "That is a large quantity. Check it is what the branch meant to ask for."
}
return proposal, nil
},
// Execute: everything checked at proposal time may have changed while
// the card was on screen. Somebody else may have approved it; the branch
// may have withdrawn it.
func(_ context.Context, req Request) (Result, error) {
id := req.Int("requestid")
row, err := find(req.Caller.Tenantid, req.Caller.Locationid, id)
if err != nil {
return Result{}, err
}
if err := writer.UpdateStockRequest(row.Requestid, stockApprovedStatus); err != nil {
return Result{}, err
}
return Result{
Rows: []ProposalDetail{
{Label: "Approved", Value: fmt.Sprintf("%d × %s for %s", row.Qty, row.Productname, row.Locationname)},
},
Count: 1,
Source: "/admin/inventory",
Scope: scopeWords(req.Caller),
Note: "Done. The branch can now receive this stock.",
}, nil
})
}