mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) add users.options.isConsultant flag, and omit such users from billing
Summary: This adds an optional `isConsultant` flag to `users.options`, and an endpoint that allows the support user to turn it on or off. Users marked as consultants are not counted as billable members. Follows the example of existing `allowGoogleLogin` option. Billable members are counted when members are added or removed from a site. Changing the `isConsultant` flag has no immediate or retroactive effect on billing. The number of users in stripe is now set unconditionally, rather than only when it has changed. Notifications to billing managers are not aware of this billing nuance, but continue to report user counts that include consultants. The notifications link users to the billing page. Test Plan: extended test Reviewers: georgegevoian Reviewed By: georgegevoian Subscribers: anaisconce, jarek Differential Revision: https://phab.getgrist.com/D3362
This commit is contained in:
parent
782bb44ed5
commit
14f7e30e6f
@ -174,7 +174,7 @@ export class BillingPage extends Disposable {
|
||||
] : null,
|
||||
moneyPlan.amount ? [
|
||||
makeSummaryFeature([`Your team site has `, `${sub.userCount}`,
|
||||
` member${sub.userCount > 1 ? 's' : ''}`]),
|
||||
` member${sub.userCount !== 1 ? 's' : ''}`]),
|
||||
tier ? this.buildAppSumoPlanNotes(discountName!) : null,
|
||||
// Currently the subtotal is misleading and scary when tiers are in effect.
|
||||
// In this case, for now, just report what will be invoiced.
|
||||
|
@ -135,6 +135,9 @@ export interface Document extends DocumentProperties {
|
||||
export interface UserOptions {
|
||||
// Whether signing in with Google is allowed. Defaults to true if unset.
|
||||
allowGoogleLogin?: boolean;
|
||||
// Whether user is a consultant. Consultant users can be added to sites
|
||||
// without being counted for billing. Defaults to false if unset.
|
||||
isConsultant?: boolean;
|
||||
}
|
||||
|
||||
export interface PermissionDelta {
|
||||
@ -312,6 +315,7 @@ export interface UserAPI {
|
||||
getUserProfile(): Promise<FullUser>;
|
||||
updateUserName(name: string): Promise<void>;
|
||||
updateAllowGoogleLogin(allowGoogleLogin: boolean): Promise<void>;
|
||||
updateIsConsultant(userId: number, isConsultant: boolean): Promise<void>;
|
||||
getWorker(key: string): Promise<string>;
|
||||
getWorkerAPI(key: string): Promise<DocWorkerAPI>;
|
||||
getBillingAPI(): BillingAPI;
|
||||
@ -608,6 +612,13 @@ export class UserAPIImpl extends BaseAPI implements UserAPI {
|
||||
});
|
||||
}
|
||||
|
||||
public async updateIsConsultant(userId: number, isConsultant: boolean): Promise<void> {
|
||||
await this.request(`${this._url}/api/profile/isConsultant`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({userId, isConsultant})
|
||||
});
|
||||
}
|
||||
|
||||
public async getWorker(key: string): Promise<string> {
|
||||
const json = await this.requestJson(`${this._url}/api/worker/${key}`, {
|
||||
method: 'GET',
|
||||
|
@ -379,6 +379,25 @@ export class ApiServer {
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
this._app.post('/api/profile/isConsultant', expressWrap(async (req, res) => {
|
||||
const userId = getAuthorizedUserId(req);
|
||||
if (userId !== this._dbManager.getSupportUserId()) {
|
||||
throw new ApiError('Only support user can enable/disable isConsultant', 401);
|
||||
}
|
||||
const isConsultant: boolean | undefined = req.body.isConsultant;
|
||||
const targetUserId: number | undefined = req.body.userId;
|
||||
if (isConsultant === undefined) {
|
||||
throw new ApiError('Missing body param: isConsultant', 400);
|
||||
}
|
||||
if (targetUserId === undefined) {
|
||||
throw new ApiError('Missing body param: targetUserId', 400);
|
||||
}
|
||||
await this._dbManager.updateUserOptions(targetUserId, {
|
||||
isConsultant
|
||||
});
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
// GET /api/profile/apikey
|
||||
// Get user's apiKey
|
||||
this._app.get('/api/profile/apikey', expressWrap(async (req, res) => {
|
||||
|
@ -4185,7 +4185,7 @@ function getFrom(queryBuilder: SelectQueryBuilder<any>): string {
|
||||
}
|
||||
|
||||
// Flatten a map of users per role into a simple list of users.
|
||||
function removeRole(usersWithRoles: Map<roles.NonGuestRole, User[]>) {
|
||||
export function removeRole(usersWithRoles: Map<roles.NonGuestRole, User[]>) {
|
||||
return flatten([...usersWithRoles.values()]);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user