Address the 11 October test report findings
The initial test report (run before the pilot-readiness changes) raised seven findings and a retest plan. This closes the ones still open. - F-01: a business on its free trial may run 2 active branches whatever its tier, so an owner can try transfers and per-branch stock. Only active branches count towards any limit. - F-06: owners can rename branches and deactivate/reactivate them from the Branches page. A branch can only be deactivated once it holds no stock and no active staff; its history stays in every report, and it leaves every picker. - F-04: the Products page lists deactivated products under a toggle with Reactivate. Deactivating warns about stock still on hand, which stays on the Stock page so it can be transferred or counted out. - F-05: products carry a unit (L, ml, kg, g, pcs, tray, pack) instead of everything being litres. - CAT-12: product prices must be above zero and names are unique per business, ignoring case. - F-03: a reversed report period is refused by the page and the API, and the page labels which period the cards show. Report dates are Nairobi business days. - F-07: cost of goods sold is units sold x average buying price. Stock bought in the period is reported separately, with a cash surplus line, so unsold stock no longer shows as a loss. - STA-04: staff and new branch managers can be invited with a one-time, 7-day link; the person sets their own password. Managers can issue a new link for their own cashiers. - OFF-02 / POS-02: the till shows each product's stock and deducts every sale immediately, online or offline; it warns before selling more than the recorded stock, and has a Clear sale button. - npm run backfill:stock turns purchases recorded before the stock ledger into stock, so existing tenants (and the report's ZERRO fixtures) start from what they bought. tests/retest-plan.test.ts runs the report's completion and retest plan and checks every balance and total it predicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,12 @@ export interface IBranch extends Document {
|
||||
branchName: string;
|
||||
locationCity: string;
|
||||
managerId: Types.ObjectId | null;
|
||||
// A deactivated branch keeps all its sales, purchases and stock history
|
||||
// and still appears in reports for past periods, but takes no new staff,
|
||||
// transfers or purchases and does not count towards the plan's limit.
|
||||
// Rows created before this field existed have no value: treat as active
|
||||
// (query with `isActive: { $ne: false }`).
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -16,9 +22,13 @@ const branchSchema = new Schema<IBranch>(
|
||||
branchName: { type: String, required: true, trim: true },
|
||||
locationCity: { type: String, required: true, trim: true },
|
||||
managerId: { type: Schema.Types.ObjectId, ref: "User", default: null },
|
||||
isActive: { type: Boolean, default: true },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
/** Filter for branches that are in use. */
|
||||
export const ACTIVE_BRANCH = { isActive: { $ne: false } } as const;
|
||||
|
||||
export const Branch =
|
||||
(models.Branch as Model<IBranch>) || model<IBranch>("Branch", branchSchema);
|
||||
|
||||
@@ -4,8 +4,10 @@ export interface IProduct extends Document {
|
||||
_id: Types.ObjectId;
|
||||
businessId: Types.ObjectId; // tenant this product belongs to
|
||||
label: string;
|
||||
// Size measured in `unit` (litres for water, a count for side items).
|
||||
// The field keeps its historical name so existing data needs no migration.
|
||||
sizeLiters: number;
|
||||
unit: string; // display unit, e.g. "L" for water, "pcs" / "tray" for eggs
|
||||
unit: string; // see PRODUCT_UNITS in src/lib/units.ts: "L", "pcs", "tray"...
|
||||
category: string; // e.g. "Water", "Eggs"
|
||||
unitPrice: number;
|
||||
sortOrder: number;
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface IUser extends Document {
|
||||
isActive: boolean;
|
||||
failedLoginAttempts: number;
|
||||
lockedUntil: Date | null;
|
||||
// Pending invite: the person sets their own password through a one-time
|
||||
// link. Only the SHA-256 of the token is stored. Until they accept, the
|
||||
// account has a random unknown password, so it cannot be signed into.
|
||||
inviteTokenHash: string | null;
|
||||
inviteExpiresAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
@@ -34,6 +39,8 @@ const userSchema = new Schema<IUser>(
|
||||
isActive: { type: Boolean, default: true },
|
||||
failedLoginAttempts: { type: Number, default: 0 },
|
||||
lockedUntil: { type: Date, default: null },
|
||||
inviteTokenHash: { type: String, default: null, index: { sparse: true } },
|
||||
inviteExpiresAt: { type: Date, default: null },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user