Add a screen to set prices per branch
Branch price overrides were already applied at the till, in cash sales and in M-Pesa, but nothing could set them. - Products -> Branch prices (/admin/prices): pick a branch and set its price for any product; setting it back to the default removes the override, and "Use default" resets it. Branch managers see their own branch's prices read-only. - /api/branch-prices: GET (owner: any branch; manager: own), PUT and DELETE (owner only). Branch and product must belong to the business; prices must be above zero; who changed a price is recorded. - Branch pricing is a plan feature, as the Growth plan already advertised: Growth and Enterprise, plus any business on its free trial. A paid Starter business keeps any prices set during its trial and can reset them, but cannot set new ones. - The catalogue view on the Products page always shows default prices; the till's view shows what the branch charges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ export interface IBranchPricing extends Document {
|
||||
branchId: Types.ObjectId;
|
||||
productId: Types.ObjectId;
|
||||
unitPrice: number;
|
||||
updatedBy: Types.ObjectId | null;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ const branchPricingSchema = new Schema<IBranchPricing>(
|
||||
branchId: { type: Schema.Types.ObjectId, ref: "Branch", required: true },
|
||||
productId: { type: Schema.Types.ObjectId, ref: "Product", required: true },
|
||||
unitPrice: { type: Number, required: true, min: 0 },
|
||||
updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface TierDef {
|
||||
priceKsh: number; // monthly price in KSh
|
||||
maxBranches: number | null; // null = unlimited
|
||||
maxUsers: number | null; // total users in the business (owner + managers + staff); null = unlimited
|
||||
branchPricing: boolean; // may set a different price per branch
|
||||
features: string[];
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ export const TIERS: Record<TierId, TierDef> = {
|
||||
priceKsh: 3500,
|
||||
maxBranches: 1,
|
||||
maxUsers: 6, // owner + up to 5 staff/manager
|
||||
branchPricing: false,
|
||||
features: [
|
||||
"1 branch",
|
||||
"Up to 5 staff accounts",
|
||||
@@ -37,6 +39,7 @@ export const TIERS: Record<TierId, TierDef> = {
|
||||
priceKsh: 7500,
|
||||
maxBranches: 5,
|
||||
maxUsers: 30,
|
||||
branchPricing: true,
|
||||
features: [
|
||||
"Up to 5 branches",
|
||||
"Up to 30 staff accounts",
|
||||
@@ -51,6 +54,7 @@ export const TIERS: Record<TierId, TierDef> = {
|
||||
priceKsh: 15000,
|
||||
maxBranches: null,
|
||||
maxUsers: null,
|
||||
branchPricing: true,
|
||||
features: [
|
||||
"Unlimited branches",
|
||||
"Unlimited staff accounts",
|
||||
@@ -72,6 +76,15 @@ export const TRIAL_DAYS = 14;
|
||||
// applies to adding branches; existing branches are never removed.
|
||||
export const TRIAL_MAX_BRANCHES = 2;
|
||||
|
||||
/**
|
||||
* Whether the business may set branch-specific prices right now: a plan
|
||||
* feature (Growth and up), also available during the free trial so it can
|
||||
* be tried with the trial's two branches.
|
||||
*/
|
||||
export function canUseBranchPricing(tier: TierId, status: SubscriptionStatus): boolean {
|
||||
return TIERS[tier].branchPricing || status === "TRIALING";
|
||||
}
|
||||
|
||||
/** Branch limit for a business right now (null = unlimited). */
|
||||
export function effectiveMaxBranches(tier: TierId, status: SubscriptionStatus): number | null {
|
||||
const max = TIERS[tier].maxBranches;
|
||||
|
||||
Reference in New Issue
Block a user