Compare commits

...

4 Commits

Author SHA1 Message Date
Alex Ant c0d236dff3 chore(release): publish
- coopback@1.7.30
 - coopdoc-generator-ts@1.0.74
 - cooptypes@1.0.19
 - coop-notificator@0.0.23
 - terminal@0.1.34
2024-08-13 23:03:13 +05:00
Alex Ant 743d5fc306 разделение формы для ввода персональных данных с регистратором; зародыш формы добавления пайщика в реестр. 2024-08-13 23:00:09 +05:00
Alex Ant 2f51516163 step to install | works on add user by api 2024-08-13 15:21:23 +05:00
Alex Ant b606b46aa4 страница настроек с кнопками выхода для пайщика 2024-08-05 14:31:27 +05:00
120 changed files with 1223 additions and 1173 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
"project": "./tsconfig.json"
},
"rules": {
"no-console": "error",
"no-console": "warn",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.30](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29...coopback@1.7.30) (2024-08-13)
**Note:** Version bump only for package coopback
## [1.7.29](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29-alpha.0...coopback@1.7.29) (2024-08-05)
**Note:** Version bump only for package coopback
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "create-nodejs-express-app",
"version": "1.7.29",
"version": "1.7.30",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-nodejs-express-app",
"version": "1.7.29",
"version": "1.7.30",
"license": "MIT",
"dependencies": {
"@a2seven/yoo-checkout": "^1.1.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "coopback",
"version": "1.7.29",
"version": "1.7.30",
"description": "",
"private": true,
"bin": "bin/createNodejsApp.js",
+1 -1
View File
@@ -17,7 +17,7 @@ async function deleteUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
await userService.deleteUserById(username);
await userService.deleteUserByUsername(username);
console.log('Пользователь удалён: ', username);
} catch (e: any) {
+1 -1
View File
@@ -25,7 +25,7 @@ async function init() {
email: 'dacom.dark.sun@gmail.com',
password,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'chairman',
role: 'user',
type: 'individual',
individual_data: {
first_name: 'Иван',
+4
View File
@@ -21,6 +21,9 @@ const envVarsSchema = Joi.object()
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which verify email token expires'),
JWT_INVITE_EXPIRATION_MINUTES: Joi.number().default(3600).description('minutes after invite token expires'),
SMTP_HOST: Joi.string().description('server that will send the emails'),
SMTP_PORT: Joi.number().description('port to connect to the email server'),
SMTP_USERNAME: Joi.string().description('username for email server'),
@@ -57,6 +60,7 @@ export default {
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
inviteExpirationMinutes: envVars.JWT_INVITE_EXPIRATION_MINUTES,
},
email: {
smtp: {
+2 -2
View File
@@ -1,7 +1,7 @@
const allRoles = {
user: [''],
service: ['sendNotification'],
chairman: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
service: ['addUser', 'sendNotification'],
chairman: ['addUser', 'getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
member: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
};
+1
View File
@@ -3,4 +3,5 @@ export const tokenTypes = {
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
INVITE: 'invite',
};
@@ -26,8 +26,8 @@ export const refreshTokens = catchAsync(async (req: RRefreshTokens, res: any) =>
});
export const lostKey = catchAsync(async (req: RForgotKey, res) => {
const resetKeyToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetKeyToken);
const resetKeyToken = await tokenService.generateResetKeyToken(req.body.email);
await emailService.sendResetKeyEmail(req.body.email, resetKeyToken);
res.status(NO_CONTENT).send();
});
@@ -1,12 +1,16 @@
import http from 'http-status';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService, tokenService } from '../services';
import { RCreateUser, RJoinCooperative } from '../types';
import { userService, tokenService, emailService, blockchainService } from '../services';
import { IAddUser, ICreateUser, RCreateUser, RJoinCooperative } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
import { IUser } from '../models/user.model';
import { Request, Response } from 'express';
import { generateUsername } from '../../tests/utils/generateUsername';
import config from '../config/config';
import logger from '../config/logger';
/**
* Порядок регистрации:
@@ -30,6 +34,38 @@ export const createUser = catchAsync(async (req: RCreateUser, res) => {
res.status(http.CREATED).send({ user, tokens });
});
export const addUser = catchAsync(async (req: Request, res: Response) => {
const body: IAddUser = req.body;
const newUser: ICreateUser = {
...body,
public_key: '',
role: 'user',
username: generateUsername(),
};
const user = await userService.createUser(newUser);
try {
await blockchainService.addUser({
...body,
...newUser,
registrator: config.service_username,
referer: body.referer ? body.referer : '',
coopname: config.coopname,
meta: '',
});
const token = await tokenService.generateInviteToken(user.email);
await emailService.sendInviteEmail(req.body.email, token);
} catch (e) {
logger.warn('error on add user: ', e);
await userService.deleteUserByUsername(newUser.username);
}
res.status(httpStatus.CREATED).send({ user });
});
export const joinCooperative = catchAsync(async (req: RJoinCooperative, res) => {
await userService.joinCooperative(req.body);
@@ -82,6 +118,6 @@ export const updateUser = catchAsync(async (req, res) => {
});
export const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserById(req.params.username);
await userService.deleteUserByUsername(req.params.username);
res.status(http.NO_CONTENT).send();
});
@@ -25,7 +25,7 @@ const tokenSchema = new Schema<IToken>(
},
type: {
type: String,
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL],
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL, tokenTypes.INVITE],
required: true,
},
expires: {
+1 -30
View File
@@ -19,7 +19,6 @@ export interface IUser {
public_key: string;
referer: string;
email: string;
password: string;
role: string;
is_email_verified: boolean;
statement: {
@@ -36,7 +35,6 @@ export interface IUser {
getPrivateData(): Promise<
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
>;
isPasswordMatch(password: string): Promise<boolean>;
}
interface IUserModel extends Model<IUser> {
@@ -72,7 +70,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
public_key: {
type: String,
required: true,
default: '',
},
referer: {
type: String,
@@ -90,18 +88,6 @@ const userSchema = new Schema<IUser, IUserModel>(
}
},
},
password: {
type: String,
required: true,
trim: true,
minlength: 8,
validate(value) {
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
throw new Error('Password must contain at least one letter and one number');
}
},
private: true, // used by the toJSON plugin
},
role: {
type: String,
enum: roles,
@@ -163,21 +149,6 @@ userSchema.methods.getPrivateData = async function (): Promise<
return result;
};
userSchema.methods.isPasswordMatch = async function (password) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const user = this;
return compare(password, user.password);
};
userSchema.pre('save', async function (next) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const user = this;
if (user.isModified('password')) {
user.password = await hash(user.password, 8);
}
next();
});
const User = model<IUser, IUserModel>('User', userSchema);
export default User;
@@ -11,9 +11,9 @@ router
.post(validate(userValidation.RCreateUser), userController.createUser)
.get(auth('getUsers'), validate(userValidation.RGetUsers), userController.getUsers);
router
.route('/join-cooperative')
.post(validate(userValidation.RJoinCooperative), userController.joinCooperative)
router.route('/join-cooperative').post(validate(userValidation.RJoinCooperative), userController.joinCooperative);
router.route('/add').post(auth('addUser'), validate(userValidation.RAddUser), userController.addUser);
router
.route('/:username')
@@ -41,27 +41,13 @@ export const loginUserWithSignature = async (email, now, signature) => {
if (!hasKey) throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
} catch (e) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Аккаунт в блокчейне не найден');
throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
}
}
return user;
};
/**
* Login with email and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
export const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};
/**
* Logout
* @param {string} refreshToken
@@ -102,26 +88,26 @@ export const refreshAuth = async (data: IRefreshTokens) => {
*/
export const resetKey = async (resetKeyToken, publicKey) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetKeyToken, tokenTypes.RESET_PASSWORD);
const resetKeyTokenDoc = await tokenService.verifyToken(resetKeyToken, tokenTypes.RESET_PASSWORD);
const user = await userService.getUserById(resetPasswordTokenDoc.user);
const user = await userService.getUserById(resetKeyTokenDoc.user);
if (!user) {
throw new Error();
}
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.service_username,
username: user.username,
public_key: publicKey,
});
if (config.env !== 'test')
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.service_username,
username: user.username,
public_key: publicKey,
});
await userService.updateUserById(user._id, { public_key: publicKey });
await Token.deleteMany({ user: user._id, type: tokenTypes.RESET_PASSWORD });
} catch (error) {
console.log(error);
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
throw new ApiError(httpStatus.UNAUTHORIZED, 'Token reset failed');
}
};
@@ -266,6 +266,34 @@ async function failOrder(data) {
);
}
export async function addUser(data: RegistratorContract.Actions.AddUser.IAddUser) {
const eos = await getInstance(config.service_wif);
const actions = [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
export async function changeKey(data: RegistratorContract.Actions.ChangeKey.IChangeKey) {
const eos = await getInstance(config.service_wif);
@@ -4,7 +4,7 @@ import logger from '../config/logger';
const { email, env } = config;
const transport = createTransport(email.smtp);
export const transport = createTransport(email.smtp);
/* istanbul ignore next */
if (env !== 'test') {
@@ -21,7 +21,7 @@ if (env !== 'test') {
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to, subject, text) => {
export const sendEmail = async (to, subject, text) => {
const msg = { from: email.from, to, subject, text };
await transport.sendMail(msg);
};
@@ -32,7 +32,7 @@ const sendEmail = async (to, subject, text) => {
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to, token) => {
export const sendResetKeyEmail = async (to, token) => {
const subject = 'Восстановление доступа';
const resetPasswordUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
@@ -44,13 +44,29 @@ const sendResetPasswordEmail = async (to, token) => {
await sendEmail(to, subject, text);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
export const sendInviteEmail = async (to, token) => {
const subject = 'Восстановление доступа';
const inviteUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
const text = `Вам отправлено приглашение на подключение к Цифровому Кооперативу в качестве пайщика.
Для того, чтобы воспользоваться приглашени, нажмите на ссылку: ${token}. Время действия ссылки - 24 часа.`;
await sendEmail(to, subject, text);
};
/**
* Send verification email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to, token) => {
export const sendVerificationEmail = async (to, token) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
@@ -59,5 +75,3 @@ To verify your email, click on this link: ${verificationEmailUrl}
If you did not create an account, then ignore this email.`;
await sendEmail(to, subject, text);
};
export { transport, sendEmail, sendResetPasswordEmail, sendVerificationEmail };
@@ -107,7 +107,7 @@ export const generateAuthTokens = async (user) => {
* @param {string} email
* @returns {Promise<string>}
*/
export const generateResetPasswordToken = async (email) => {
export const generateResetKeyToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
@@ -118,6 +118,22 @@ export const generateResetPasswordToken = async (email) => {
return resetKeyToken;
};
/**
* Generate invite token
* @param {string} email
* @returns {Promise<string>}
*/
export const generateInviteToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.inviteExpirationMinutes, 'minutes');
const inviteToken = generateToken(user.id, expires, tokenTypes.INVITE);
await saveToken(inviteToken, user.id, expires, tokenTypes.INVITE);
return inviteToken;
};
/**
* Generate verify email token
* @param {User} user
@@ -3,7 +3,6 @@ import { User } from '../models';
import ApiError from '../utils/ApiError';
import { generator } from './document.service';
import { ICreateUser, IJoinCooperative } from '../types/auto-generated/user.validation';
import ecc from 'eosjs-ecc';
import { PublicKey, Signature } from '@wharfkit/antelope';
import faker from 'faker';
import { randomBytes } from 'crypto';
@@ -28,6 +27,7 @@ export const createServiceUser = async (username: string) => {
export const createUser = async (userBody: ICreateUser) => {
//TODO проверяем на существование пользователя
//допускаем обновление личных данных, если пользователь находится в статусе 'created'
const exist = await User.findOne({ email: userBody.email });
if (userBody.type === 'individual' && !userBody.individual_data)
@@ -186,7 +186,7 @@ export const updateUserByUsername = async (username, updateBody) => {
* @param {string} username
* @returns {Promise<User>}
*/
export const deleteUserById = async (username) => {
export const deleteUserByUsername = async (username) => {
const user = await getUserByUsername(username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
@@ -3,6 +3,84 @@
* Do not modify this file manually
*/
export interface IAddUser {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
}
export interface ICreateUser {
email: string;
entrepreneur_data?: {
@@ -73,10 +151,9 @@ export interface ICreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -169,6 +246,87 @@ export interface IOrganizationData {
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
}
export interface RAddUser {
body: {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
meta?: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
};
}
export interface RCreateUser {
body: {
email: string;
@@ -240,10 +398,9 @@ export interface RCreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
};
@@ -280,7 +437,6 @@ export interface RJoinCooperative {
export interface RUpdateUser {
body?: {
email?: string;
password?: string;
};
params?: {
username: string;
@@ -1,5 +1,5 @@
import * as Joi from 'joi';
import { password, objectId } from './custom.validation';
import { password } from './custom.validation';
import { IBankAccount } from './payment.validation';
export const IIndividualData = Joi.object({
@@ -57,8 +57,7 @@ export const IEntrepreneurData = Joi.object({
export const ICreateUser = Joi.object({
email: Joi.string().required().email(),
password: Joi.string().required().custom(password),
role: Joi.string().required().valid('user', 'chairman', 'member'),
role: Joi.string().required().valid('user'),
public_key: Joi.string().required(),
username: Joi.string().required().length(12),
referer: Joi.string().length(12).allow('').optional(),
@@ -68,6 +67,25 @@ export const ICreateUser = Joi.object({
entrepreneur_data: IEntrepreneurData.optional(),
});
export const IAddUser = Joi.object({
email: Joi.string().required().email(),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
individual_data: IIndividualData.optional(),
organization_data: IOrganizationData.optional(),
entrepreneur_data: IEntrepreneurData.optional(),
spread_initial: Joi.boolean().required(),
created_at: Joi.string().required(),
initial: Joi.string().required(),
minimum: Joi.string().required(),
});
export const RAddUser = Joi.object({
body: IAddUser.required(),
});
export const IDocument = Joi.object().keys({
hash: Joi.string().required(),
signature: Joi.string().required(),
@@ -104,7 +122,6 @@ export const RUpdateUser = Joi.object({
body: Joi.object()
.keys({
email: Joi.string().email(),
password: Joi.string().custom(password),
})
.min(1),
});
@@ -1,17 +1,11 @@
import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';
import faker from 'faker';
import User from '../../src/models/user.model';
import { generateUsername } from '../utils/generateUsername';
import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
const password = 'password1';
const email1 = faker.internet.email().toLowerCase();
export const participantOne: ICreateUser = {
email: email1,
password,
username: generateUsername(),
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
+1 -10
View File
@@ -7,10 +7,6 @@ import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
import { ObjectId } from 'mongodb';
const password = 'password1';
const salt = bcrypt.genSaltSync(8);
const hashedPassword = bcrypt.hashSync(password, salt);
const generateRandomId = () => new mongoose.Types.ObjectId();
type testUser = Omit<IUser, 'getPrivateData' | 'isPasswordMatch' | 'private_data'> & {
@@ -33,7 +29,6 @@ const adminUsername = generateUsername();
export const admin: testUser = {
_id: generateRandomId(),
email: email1,
password,
status: 'active',
has_account: false,
message: '',
@@ -68,7 +63,6 @@ const usernameOne = generateUsername();
export const userOne: testUser = {
_id: generateRandomId(),
email: email2,
password,
has_account: false,
status: 'active',
message: '',
@@ -104,7 +98,6 @@ export const userTwo: testUser = {
_id: generateRandomId(),
email: email3,
has_account: false,
password,
status: 'active',
message: '',
is_registered: true,
@@ -138,7 +131,6 @@ export const chairman: testUser = {
_id: generateRandomId(),
email: email4,
has_account: false,
password,
status: 'active',
message: '',
is_registered: true,
@@ -171,7 +163,6 @@ export const chairman: testUser = {
export const voskhod: testUser = {
_id: generateRandomId(),
email: email5,
password,
has_account: false,
status: 'active',
message: '',
@@ -259,7 +250,7 @@ export const insertUsers = async (users: testUser[]) => {
for (const user of users) {
const { type, individual_data, organization_data, entrepreneur_data, block_num, ...rest } = user;
await User.insertMany([{ ...rest, type, password: hashedPassword }]);
await User.insertMany([{ ...rest, type }]);
if (type === 'individual' && individual_data) {
await insertPrivateIndividualUserData({
@@ -29,7 +29,6 @@ describe('Auth routes', () => {
newUser = {
email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -50,8 +49,6 @@ describe('Auth routes', () => {
test('should return 201 and successfully register user if request data is ok', async () => {
const res = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(res.body.user).not.toHaveProperty('password');
expect(res.body.user).toEqual({
id: expect.anything(),
email: newUser.email,
@@ -76,7 +73,6 @@ describe('Auth routes', () => {
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
expect(dbUser).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
expect(res.body.tokens).toEqual({
@@ -97,26 +93,10 @@ describe('Auth routes', () => {
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
newUser.password = 'password';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
newUser.password = '11111111';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/login', () => {
test('should return 200 and login user if email and password match', async () => {
test('should return 200 and login user if email and signature match', async () => {
await insertUsers([userOne]);
const now = (await getBlockchainInfo()).head_block_time;
@@ -154,10 +134,16 @@ describe('Auth routes', () => {
test('should return 401 error if there are no users with that email', async () => {
const now = (await getBlockchainInfo()).head_block_time;
const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const signature = privateKey.signDigest(checksum);
const loginCredentials = {
now,
email: userOne.email,
signature: userOne.password,
signature,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
@@ -166,7 +152,9 @@ describe('Auth routes', () => {
});
// test('should return 401 error if signature is wrong', async () => {
// Восстановить проверку. Необходимо подложить аккаунт тестового юзера в коде вместо обращения к блокчейну.
// await insertUsers([userOne]);
// const now = (await getBlockchainInfo()).head_block_time;
// const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
@@ -288,14 +276,14 @@ describe('Auth routes', () => {
jest.spyOn(emailService.transport, 'sendMail').mockResolvedValue();
});
test('should return 204 and send reset password email to the user', async () => {
test('should return 204 and send reset key email to the user', async () => {
await insertUsers([userOne]);
const sendResetPasswordEmailSpy = jest.spyOn(emailService, 'sendResetPasswordEmail');
const sendResetKeyEmailSpy = jest.spyOn(emailService, 'sendResetKeyEmail');
await request(app).post('/v1/auth/lost-key').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
expect(sendResetPasswordEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetKeyToken = sendResetPasswordEmailSpy.mock.calls[0][1];
expect(sendResetKeyEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetKeyToken = sendResetKeyEmailSpy.mock.calls[0][1];
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetKeyToken, user: userOne._id.toString() });
expect(dbResetPasswordTokenDoc).toBeDefined();
});
@@ -314,13 +302,15 @@ describe('Auth routes', () => {
});
describe('POST /v1/auth/reset-key', () => {
test('should return 204 and reset the password', async () => {
test('should return 204 and reset the key', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const res = await request(app).post('/v1/auth/reset-key').send({ token: resetKeyToken, password: 'password2' });
const res = await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: userOne.public_key });
expect(res.status).toBe(httpStatus.NO_CONTENT);
@@ -328,9 +318,6 @@ describe('Auth routes', () => {
expect(dbUser).not.toBeUndefined();
if (dbUser) {
const isPasswordMatch = await bcrypt.compare('password2', dbUser.password);
expect(isPasswordMatch).toBe(true);
const dbResetPasswordTokenCount = await Token.countDocuments({
user: userOne._id.toString(),
type: tokenTypes.RESET_PASSWORD,
@@ -339,13 +326,16 @@ describe('Auth routes', () => {
}
});
test('should return 400 if reset password token is missing', async () => {
test('should return 400 if reset key token is missing', async () => {
await insertUsers([userOne]);
await request(app).post('/v1/auth/reset-key').send({ password: 'password2' }).expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.BAD_REQUEST);
});
test('should return 401 if reset password token is blacklisted', async () => {
test('should return 401 if reset key token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
@@ -357,7 +347,7 @@ describe('Auth routes', () => {
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 if reset password token is expired', async () => {
test('should return 401 if reset key token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
@@ -379,30 +369,6 @@ describe('Auth routes', () => {
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 400 if password is missing or invalid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app).post('/v1/auth/reset-key').send({ token: resetKeyToken }).expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'stort' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'password' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: '11111111' })
.expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/send-verification-email', () => {
@@ -56,7 +56,7 @@ describe('Проверка получения документов', () => {
skip_save: false,
};
let res = await request(app)
const res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
@@ -26,7 +26,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,7 +50,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -67,7 +67,6 @@ describe('Проверка получения документов', () => {
const email = faker.internet.email().toLowerCase();
const newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -4,10 +4,10 @@ import app from '../../src/app';
import { setupTestDB } from '../utils/setupTestDB';
import { User } from '../../src/models';
import { userOne, userTwo, admin, insertUsers } from '../fixtures/user.fixture';
import { userOneAccessToken, adminAccessToken } from '../fixtures/token.fixture';
import { userOneAccessToken, adminAccessToken, userTwoAccessToken } from '../fixtures/token.fixture';
import request from 'supertest';
import { generateUsername } from '../utils/generateUsername';
import { IDocument, IIndividualData, IJoinCooperative } from '../../src/types';
import { IIndividualData } from '../../src/types';
setupTestDB();
@@ -19,7 +19,6 @@ describe('User routes', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,11 +50,9 @@ describe('User routes', () => {
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user).not.toHaveProperty('password');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -65,23 +62,15 @@ describe('User routes', () => {
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: newUser.role, is_email_verified: false });
});
test('should be able to create an admin as well', async () => {
test('should not be able to create an admin as well', async () => {
await insertUsers([admin]);
newUser.role = 'member';
const res = await request(app)
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user.role).toBe('member');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser?.role).toBe('member');
.expect(httpStatus.BAD_REQUEST);
});
test('успешная регистрация без токена доступа', async () => {
@@ -122,36 +111,6 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
await insertUsers([admin]);
newUser.password = 'passwo1';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
await insertUsers([admin]);
newUser.password = 'password';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
newUser.password = '1111111';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if role is neither user nor admin', async () => {
await insertUsers([admin]);
newUser.role = 'invalid';
@@ -164,6 +123,71 @@ describe('User routes', () => {
});
});
describe('POST /v1/users/add', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
referer: '',
type: 'individual',
individual_data: {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email,
full_address: 'Russia, Moscow, Tverskaya street, 10',
},
};
});
test('should return 201 and successfully add new user if data is ok', async () => {
await insertUsers([admin]);
const res = await request(app).post('/v1/users/add').set('Authorization', `Bearer ${adminAccessToken}`).send(newUser);
// if (res.status !== httpStatus.CREATED) {
// console.error(res.body); // Выводит тело ответа, если статус не CREATED
// }
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.user.username).toBeDefined();
const dbUser = await User.findOne({ username: res.body.user.username });
expect(dbUser).toBeDefined();
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
expect(privateData.first_name).toEqual(newUser.individual_data?.first_name);
expect(privateData.last_name).toEqual(newUser.individual_data?.last_name);
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
});
test('should return 403 if service user is not admin but data new user is ok', async () => {
await insertUsers([userTwo]);
const res = await request(app)
.post('/v1/users/add')
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(newUser);
expect(res.status).toBe(httpStatus.FORBIDDEN);
});
test('should return 401 if service user is not have access token', async () => {
const res = await request(app).post('/v1/users/add').send(newUser);
expect(res.status).toBe(httpStatus.UNAUTHORIZED);
});
});
describe('GET /v1/users', () => {
test('should return 200 and apply the default query options', async () => {
await insertUsers([userOne, userTwo, admin]);
@@ -513,7 +537,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -528,7 +551,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -542,7 +564,6 @@ describe('User routes', () => {
const dbUser = await User.findById(userOne._id);
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(updateBody.password);
expect(dbUser).toMatchObject({ email: updateBody.email, role: 'user' });
});
@@ -550,7 +571,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app).patch(`/v1/users/${userOne.username}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
@@ -560,7 +580,6 @@ describe('User routes', () => {
await insertUsers([userOne, userTwo]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -574,7 +593,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -588,7 +606,6 @@ describe('User routes', () => {
await insertUsers([admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -600,7 +617,7 @@ describe('User routes', () => {
test('should return 400 if email is invalid', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: 'invalidEmail', password: 'GoodPassword!23' };
const updateBody = { email: 'invalidEmail' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -611,7 +628,7 @@ describe('User routes', () => {
test('should return 400 if email is already taken', async () => {
await insertUsers([userOne, userTwo, admin]);
const updateBody = { email: userTwo.email, password: 'GoodPassword!23' };
const updateBody = { email: userTwo.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -620,41 +637,9 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password length is less than 8 characters', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'passwo1' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password does not contain both letters and numbers', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'password' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
updateBody.password = '11111111';
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
//
test('should return 400 if email is my email then taken', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: userOne.email, password: 'veryStrongPassword!2' };
const updateBody = { email: userOne.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -10,7 +10,6 @@ describe('User model', () => {
newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
type: 'individual',
@@ -27,36 +26,9 @@ describe('User model', () => {
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain numbers', async () => {
newUser.password = 'password';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain letters', async () => {
newUser.password = '11111111';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if role is unknown', async () => {
newUser.role = 'invalid';
await expect(new User(newUser).validate()).rejects.toThrow();
});
});
describe('User toJSON()', () => {
test('should not return user password when toJSON is called', () => {
const newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
};
expect(new User(newUser).toJSON()).not.toHaveProperty('password');
});
});
});
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.74](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73...coopdoc-generator-ts@1.0.74) (2024-08-13)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.73](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73-alpha.0...coopdoc-generator-ts@1.0.73) (2024-08-05)
**Note:** Version bump only for package coopdoc-generator-ts
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "coopdoc-generator-ts",
"type": "module",
"version": "1.0.73",
"version": "1.0.74",
"private": true,
"packageManager": "pnpm@9.0.6",
"description": "",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.19](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.18...cooptypes@1.0.19) (2024-08-13)
**Note:** Version bump only for package cooptypes
## [1.0.18](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.18-alpha.0...cooptypes@1.0.18) (2024-08-05)
**Note:** Version bump only for package cooptypes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "cooptypes",
"type": "module",
"version": "1.0.18",
"version": "1.0.19",
"description": "_description_",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
@@ -4,7 +4,7 @@ import * as ContractNames from '../../../common/names'
/**
* Имя таблицы
*/
export const tableName = 'fundwallet'
export const tableName = 'coopwallet'
/**
* Таблица хранится в {@link ContractNames._fund | области памяти контракта}.
@@ -14,4 +14,4 @@ export const scope = ContractNames._fund
/**
* @interface
*/
export type IFundWallet = Fund.IFundwallet
export type ICoopWallet = Fund.ICoopwallet
@@ -12,7 +12,7 @@ export * as ExpensedFunds from './expenseFunds'
/**
* Таблица содержит сводную информацию о балансе кошелька всех фондов кооператива.
*/
export * as FundWallet from './fundWallet'
export * as CoopWallet from './coopWallet'
/**
* Таблица содержит информацию о запросах на списание по статьям и выводах из фондов.
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'adduser'
/**
* @interface
*/
export type IAddUser = Registrator.IAdduser
@@ -49,3 +49,9 @@ export * as ConfirmJoin from './confirmJoin'
* @private
*/
export * as Init from './init'
/**
* Действие добавления пайщика в обход процедуры регистрации.
* @private
*/
export * as AddUser from './addUser'
@@ -36,6 +36,11 @@ export interface IAddexpense {
quantity: IAsset
}
export interface IAddinitial {
coopname: IName
quantity: IAsset
}
export interface IAuthorize {
coopname: IName
type: IName
@@ -48,6 +53,15 @@ export interface IComplete {
withdraw_id: IUint64
}
export interface ICoopwallet {
id: IUint64
coopname: IName
circulating_account: ISimpleWallet
initial_account: ISimpleWallet
accumulative_account: ISimpleWallet
accumulative_expense_account: ISimpleWallet
}
export interface ICounts extends ICountsBase {
}
@@ -140,6 +154,9 @@ export interface IInit {
initial: IAsset
}
export interface IMigrate {
}
export interface INewfund {
coopname: IName
type: IName
@@ -152,6 +169,11 @@ export interface INewwithdraw {
id: IUint64
}
export interface ISimpleWallet {
available: IAsset
withdrawed: IAsset
}
export interface ISpreadamount {
coopname: IName
quantity: IAsset
@@ -20,6 +20,19 @@ export interface IAccount {
registered_at: ITimePointSec
}
export interface IAdduser {
registrator: IName
coopname: IName
referer: IName
username: IName
type: IName
created_at: ITimePointSec
initial: IAsset
minimum: IAsset
spread_initial: boolean
meta: string
}
export interface IBalances extends IBalancesBase {
}
@@ -36,12 +49,6 @@ export interface IChangekey {
public_key: IPublicKey
}
export interface ICheck {
hash: IChecksum256
public_key: IPublicKey
signature: ISignature
}
export interface IConfirmreg {
coopname: IName
username: IName
@@ -71,9 +78,6 @@ export interface IDocument {
meta: string
}
export interface IFix {
}
export interface IInit {
}
@@ -84,6 +88,9 @@ export interface IJoincoop {
document: IDocument
}
export interface IMigrate {
}
export interface INewaccount {
registrator: IName
coopname: IName
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.23](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.22...coop-notificator@0.0.23) (2024-08-13)
**Note:** Version bump only for package coop-notificator
## [0.0.22](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.22-alpha.0...coop-notificator@0.0.22) (2024-08-05)
**Note:** Version bump only for package coop-notificator
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "coop-notificator",
"type": "module",
"version": "0.0.22",
"version": "0.0.23",
"private": true,
"packageManager": "pnpm@9.1.1",
"description": "_description_",
+1
View File
@@ -2,6 +2,7 @@
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.tabCompletion": "onlySnippets",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.34](https://github.com/coopenomics/monocoop/compare/terminal@0.1.33...terminal@0.1.34) (2024-08-13)
**Note:** Version bump only for package terminal
## [0.1.33](https://github.com/coopenomics/monocoop/compare/terminal@0.1.33-alpha.0...terminal@0.1.33) (2024-08-05)
**Note:** Version bump only for package terminal
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "terminal",
"version": "0.1.33",
"version": "0.1.34",
"description": "A Terminal Project",
"productName": "Terminal App",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
@@ -1,47 +0,0 @@
<template lang="pug">
div(v-if="coop" flat)
div.row
div.col-md-4.q-pa-md
q-input(v-model="coop.username" flat readonly label="Имя аккаунта")
// q-input(flat readonly label="Верификация" )
// span(v-for="verification of coop.verifications") {{verification.verificator}}
// q-badge {{verification.is_verified}}
// q-input(flat readonly label="Хранилища данных" )
// q-btn(v-for="storage of coop.storages" flat) {{storage.storage_username}}
q-input(v-model="coop.initial" flat readonly label="Вступительный взнос")
q-input(v-model="coop.minimum" flat readonly label="Минимальный паевый взнос")
q-input(v-model="coop.coop_type" flat readonly label="Тип кооператива")
div.col-md-8.q-pa-md
q-input(v-model="coop.announce" flat label="Краткое описание").q-mb-lg
// q-input(flat label="Подробное описание" v-model="coop.description" type="textarea")
span(style="color: '#666666'; font-size: 12px;").text-grey Подробное описание
//- TiptapEditor(v-if="description" :v-model="description" @update:model-value="updateDescription")
q-btn(color="green" @click="save") сохранить
</template>
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue'
import { useCooperativeStore } from 'src/entities/Cooperative';
import { COOPNAME } from 'src/shared/config';
const cooperativeStore = useCooperativeStore()
const description = ref<string | undefined>('')
onMounted(async () => {
await cooperativeStore.loadPublicCooperativeData(COOPNAME)
description.value = coop.value?.description
})
let coop = computed(() => cooperativeStore.publicCooperativeData)
const save = () => {
console.log('on save')
}
</script>
@@ -28,11 +28,8 @@ div
// p Дашборд
q-tab-panel(name="Кооператив")
cooperative
// boards.col-md-12.col-xs-12.q-pa-md
q-tab-panel(name="Члены совета")
members
//- cooperative
// boards.col-md-12.col-xs-12.q-pa-md
q-tab-panel(name="Участки")
p Действующие участки
@@ -43,13 +40,13 @@ div
staff
q-tab-panel(name="Повестка")
decisions
//- decisions
q-tab-panel(name="Пайщики")
participants
//- participants
q-tab-panel(name="Документы")
documents(:receiver="coopname")
//- documents(:receiver="coopname")
q-tab-panel(name="Программы")
div.text-h6 Программы
@@ -67,15 +64,8 @@ div
</template>
<script setup lang="ts">//
import { computed, ref } from 'vue'
import cooperative from './cooperative.vue'
import members from './members.vue'
import { ref } from 'vue'
import staff from './staff.vue'
import decisions from './decisions.vue'
import participants from './participants.vue'
import documents from './documents.vue'
import { COOPNAME } from 'src/shared/config'
const coopname = computed(() => COOPNAME)
const tab = ref('Повестка')
</script>
@@ -1,21 +0,0 @@
import index from './index.vue'
export default {
'menu': [{
'pageName': 'Совет',
'name': 'soviet',
'icon': 'fa-regular fa-circle',
'isMobile': true,
'protected': true,
'params': {
coopname: ''
}
}],
'routes': [
{
path: ':coopname/soviet',
name: 'soviet',
component: index,
}
]
}
@@ -1,211 +0,0 @@
<template lang="pug">
div.row
div(v-if="members").col-md-12
q-card(v-if="members.length > 0" flat)
q-table(
ref="tableRef" flat
bordered
:rows="members"
:columns="columns"
:table-colspan="9"
row-key="username"
:pagination="pagination"
virtual-scroll
:virtual-scroll-item-size="48"
:rows-per-page-options="[10]"
).full-height
template(#bottom)
q-btn(icon="add" flat @click="showAdd = true") добавить участника
template(#header="props")
q-tr(:props="props")
q-th(
v-for="col in props.cols"
:key="col.name"
:props="props"
) {{ col.label }}
q-th(auto-width)
template(#body="props")
q-tr(:key="`m_${props.row.username}`" :props="props")
//- q-td {{ props.row.position_title }}
q-td {{ props.row.username }}
q-td {{ props.row.data?.user_profile?.last_name }}
q-td {{ props.row.data?.user_profile?.first_name }}
q-td {{ props.row.data?.user_profile?.middle_name }}
q-td {{ props.row.data?.user_profile?.phone }}
q-td {{ props.row.data?.email }}
q-td {{ moment(props.row.data?.user_profile?.birthday).format('DD.MM.YY') }}
// q-td
// q-badge(v-for="(right, index) of props.row.rights" v-bind:key="index" ).q-pa-xs.q-ma-xs {{right.contract}}::{{right.action_name}}
q-td(auto-width)
q-btn(size="sm" color="primary" round dense icon="clear" @click="removeMember(props.row.username)")
q-dialog(v-model="showAdd" persistent :maximized="false" )
q-card
div()
q-bar.bg-primary.text-white
span Добавить сотрудника
q-space
q-btn(v-close-popup dense flat icon="close")
q-tooltip Close
div.q-pa-sm.row.justify-center
div.q-pa-md
span Внимание! Вы собиретесь добавить участика в совет кооператива.
div
q-input(v-model="persona.username" label="Имя аккаунта")
q-input(v-model="persona.position_title" label="Позиция")
div.q-mt-lg
q-btn(flat @click="showAdd = false") Назад
q-btn(color="primary" @click="addMember") Добавить
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Notify } from 'quasar';
import moment from 'moment-with-locales-es6'
import { readBlockchain, sendGET } from 'src/shared/api'
import { useSessionStore } from 'src/entities/Session';
import { useUpdateBoard } from 'src/features/Cooperative/UpdateBoard';
import { COOPNAME } from 'src/shared/config';
const route = useRoute();
const showAdd = ref(false)
const members = ref<any>([]);
let members_reserve: any = []
const persona = ref({
username: '',
position_title: '',
})
const loadMembers = async () => {
try {
members.value = await sendGET('/v1/coop/members', {
coopname: route.params.coopname
})
members_reserve = JSON.parse(JSON.stringify(members.value))
} catch (e: any) {
Notify.create({
message: e.message,
type: 'negative',
})
}
}
loadMembers()
const addMember = async () => {
const verified = await verify(persona.value.username)
if (!verified) {
Notify.create({
message: 'Имя аккаунта на найдено',
type: 'negative',
})
return
}
members.value.push({ username: persona.value.username, position_title: persona.value.position_title, position: 'member', is_voting: true });
updateBoard()
showAdd.value = false
};
const removeMember = (username: string) => {
members.value = members.value.filter((el: { username: string; }) => el.username != username)
updateBoard()
};
const verify = async (username: string) => {
try {
await readBlockchain.v1.chain.get_account(username);
return true;
} catch (e) {
return false;
}
};
const updateBoard = async () => {
try {
let members_for_send = members.value.map((el: { username: any; position_title: any; position: any; is_voting: any; }) => {
return {
username: el.username,
position_title: el.position_title,
position: el.position,
is_voting: el.is_voting
}
})
await useUpdateBoard().updateBoard({
coopname: COOPNAME,
chairman: useSessionStore().username,
board_id: 0,
members: members_for_send,
name: 'Совет',
description: 'Совет кооператива'
})
Notify.create({
message: 'Совет обновлен',
type: 'positive',
})
loadMembers()
} catch (e: any) {
members.value = members_reserve
Notify.create({
message: e.message,
type: 'negative',
})
}
}
const columns = [
// { name: 'position_title', align: 'left', label: 'Позиция', field: 'position_title', sortable: true },
{ name: 'username', align: 'left', label: 'Аккаунт', field: 'username', sortable: true },
{ name: 'last_name', align: 'left', label: 'Фамилия', field: 'last_name', sortable: true },
{ name: 'first_name', align: 'left', label: 'Имя', field: 'first_name', sortable: true },
{ name: 'middle_name', align: 'left', label: 'Отчество', field: 'middle_name', sortable: true },
{ name: 'phone', align: 'left', label: 'Телефон', field: 'phone', sortable: false },
{ name: 'email', align: 'left', label: 'Е-почта', field: 'email', sortable: false },
{ name: 'birthday', align: 'left', label: 'Дата рождения', field: 'birthday', sortable: true },
] as any
const tableRef = ref(null)
const pagination = ref({ rowsPerPage: 10 })
</script>
@@ -1,107 +0,0 @@
<template lang="pug">
div.row.justify-center.full-height
div.col-12.full-height
q-table(
ref="tableRef" v-model:expanded="expanded"
flat
bordered
:rows="participants.results"
:columns="columns"
:table-colspan="9"
row-key="username"
:pagination="pagination"
virtual-scroll
:virtual-scroll-item-size="48"
:rows-per-page-options="[10]"
:loading="onLoading"
:no-data-label="'У кооператива нет пайщиков'"
).full-height
template(#header="props")
q-tr(:props="props")
q-th(auto-width)
q-th(
v-for="col in props.cols"
:key="col.name"
:props="props"
) {{ col.label }}
template(#body="props")
q-tr(:key="`m_${props.row.username}`" :props="props")
q-td(auto-width)
// q-toggle(v-model="props.expand" checked-icon="fas fa-chevron-circle-left" unchecked-icon="fas fa-chevron-circle-right" )
q-btn(size="sm" color="primary" round dense :icon="props.expand ? 'remove' : 'add'" @click="props.expand = !props.expand")
q-td {{ props.row.username }}
q-td {{ props.row.private_data?.last_name }}
q-td {{ props.row.private_data?.first_name }}
q-td {{ props.row.private_data?.middle_name }}
q-td {{ props.row.private_data?.phone }}
q-td {{ props.row.private_data?.email }}
q-td {{ moment(props.row.private_data?.birthdate).format('DD.MM.YY') }}
q-td {{ moment(props.row.private_data._created_at).format('DD.MM.YY HH:mm:ss') }}
q-tr(v-show="props.expand" :key="`e_${props.row.username}`" :props="props" class="q-virtual-scroll--with-prev")
q-td(colspan="100%")
documents(v-if="props.expand" :receiver="props.row.username")
// div.text-left Здесь будет история подписанных документов: {{ props.row.username }}
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Notify } from 'quasar'
import documents from './documents.vue'
import { sendGET } from 'src/shared/api';
const participants = ref({ results: [] })
const onLoading = ref(false)
import moment from 'moment-with-locales-es6'
const loadParticipants = async () => {
try {
onLoading.value = true
participants.value = await sendGET('/v1/users', {
limit: 100
})
onLoading.value = false
} catch (e: any) {
onLoading.value = false
Notify.create({
message: e.message,
type: 'negative',
})
}
}
loadParticipants()
const columns = [
{ name: 'username', align: 'left', label: 'Аккаунт', field: 'username', sortable: true },
{ name: 'last_name', align: 'left', label: 'Фамилия', field: 'last_name', sortable: true },
{ name: 'first_name', align: 'left', label: 'Имя', field: 'first_name', sortable: true },
{ name: 'middle_name', align: 'left', label: 'Отчество', field: 'middle_name', sortable: true },
{ name: 'phone', align: 'left', label: 'Телефон', field: 'phone', sortable: false },
{ name: 'email', align: 'left', label: 'Е-почта', field: 'email', sortable: false },
{ name: 'birthday', align: 'left', label: 'Дата рождения', field: 'birthday', sortable: true },
{
name: 'created_at',
align: 'left',
label: 'Зарегистрирован',
field: 'created_at',
sortable: true,
},
] as any
const expanded = ref([])
const tableRef = ref(null)
const pagination = ref({ rowsPerPage: 10 })
</script>
@@ -1,9 +1,10 @@
import { UserIdentityPage } from 'src/pages/User/IdentityPage';
import { UserPaymentMethodsPage } from 'src/pages/User/PaymentMethodsPage';
import { UserWalletPage } from 'src/pages/User/WalletPage';
import decisions from 'src/components/soviet/decisions.vue';
import participants from 'src/components/soviet/participants.vue';
import documents from 'src/components/soviet/documents.vue';
import { ListOfQuestionsWidget } from 'src/widgets/Cooperative/Agenda/ListOfQuestions';
import { ListOfDocumentsWidget } from 'src/widgets/Cooperative/Documents/ListOfDocuments';
import { markRaw } from 'vue';
// import { Commutator } from 'src/widgets/Commutator';
@@ -19,6 +20,8 @@ import { CooperativeDetails } from 'src/widgets/Cooperative/Details';
import { CooperativeMembers } from 'src/widgets/Cooperative/Members';
import { ChangeCooperativeContributions } from 'src/widgets/Cooperative/Contributions';
import { ChangeCooperativeContacts } from 'src/widgets/Cooperative/Contacts';
import { UserSettingsPage } from 'src/pages/User/SettingsPage';
import { ListOfParticipantsPage } from 'src/pages/Cooperative/ListOfParticipants';
export const manifest = {
'name': 'UserDesktop',
@@ -65,6 +68,17 @@ export const manifest = {
name: 'user-payment-methods',
component: markRaw(UserPaymentMethodsPage),
children: [],
},
{
meta: {
title: 'Настройки',
icon: '',
roles: [],
},
path: 'settings',
name: 'user-settings',
component: markRaw(UserSettingsPage),
children: [],
}
],
},
@@ -81,7 +95,7 @@ export const manifest = {
{
path: 'agenda',
name: 'agenda',
component: markRaw(decisions),
component: markRaw(ListOfQuestionsWidget),
meta: {
title: 'Повестка',
icon: 'fa-solid fa-check-to-slot',
@@ -91,7 +105,7 @@ export const manifest = {
{
path: 'participants',
name: 'participants',
component: markRaw(participants),
component: markRaw(ListOfParticipantsPage),
meta: {
title: 'Пайщики',
icon: 'fa-solid fa-users',
@@ -101,7 +115,7 @@ export const manifest = {
{
path: 'documents',
name: 'documents',
component: markRaw(documents),
component: markRaw(ListOfDocumentsWidget),
meta: {
title: 'Документы',
icon: 'fa-solid fa-file-invoice',
@@ -33,17 +33,17 @@ async function loadContacts(): Promise<Cooperative.Model.IContacts> {
return (await sendGET('/v1/coop/contacts', {}, true)) as Cooperative.Model.IContacts;
}
async function loadFundWallet(coopname: string): Promise<FundContract.Tables.FundWallet.IFundWallet>{
async function loadCoopWallet(coopname: string): Promise<FundContract.Tables.CoopWallet.ICoopWallet>{
return (
await fetchTable(
FundContract.contractName.production,
coopname,
FundContract.Tables.FundWallet.tableName,
FundContract.Tables.CoopWallet.tableName,
0,
0,
1
)
)[0] as FundContract.Tables.FundWallet.IFundWallet;
)[0] as FundContract.Tables.CoopWallet.ICoopWallet;
}
async function loadAccumulationFunds(coopname: string): Promise<FundContract.Tables.AccumulatedFunds.IAccumulatedFund[]>{
@@ -100,7 +100,7 @@ export const api = {
loadAdmins,
loadPrivateCooperativeData,
loadContacts,
loadFundWallet,
loadCoopWallet,
loadAccumulationFunds,
loadExpenseFunds
};
@@ -29,7 +29,7 @@ interface ICooperativeStore {
publicCooperativeData: Ref<RegistratorContract.Tables.Cooperatives.ICooperative | undefined>;
privateCooperativeData: Ref<Cooperative.Model.ICooperativeData | undefined>;
fundWallet: Ref<FundContract.Tables.FundWallet.IFundWallet | undefined>
coopWallet: Ref<FundContract.Tables.CoopWallet.ICoopWallet | undefined>
accumulationFunds: Ref<FundContract.Tables.AccumulatedFunds.IAccumulatedFund[]>
expenseFunds: Ref<FundContract.Tables.ExpensedFunds.IExpensedFund[]>
}
@@ -42,14 +42,14 @@ export const useCooperativeStore = defineStore(
const admins = ref([] as IAdministratorData[]);
const publicCooperativeData = ref<RegistratorContract.Tables.Cooperatives.ICooperative>();
const privateCooperativeData = ref<Cooperative.Model.ICooperativeData>()
const fundWallet = ref<FundContract.Tables.FundWallet.IFundWallet>()
const coopWallet = ref<FundContract.Tables.CoopWallet.ICoopWallet>()
const accumulationFunds = ref<FundContract.Tables.AccumulatedFunds.IAccumulatedFund[]>([])
const expenseFunds = ref<FundContract.Tables.ExpensedFunds.IExpensedFund[]>([])
const contacts = ref<Cooperative.Model.IContacts>()
const loadFunds = async(coopname: string) : Promise<void> => {
fundWallet.value = await api.loadFundWallet(coopname)
coopWallet.value = await api.loadCoopWallet(coopname)
accumulationFunds.value = (await api.loadAccumulationFunds(coopname)).map(el => ({
...el,
percent: Number(el.percent) / 10000
@@ -105,7 +105,7 @@ export const useCooperativeStore = defineStore(
loadAdmins,
admins,
fundWallet,
coopWallet,
accumulationFunds,
expenseFunds
};
@@ -5,8 +5,6 @@ import { FailAlert } from 'src/shared/api'
import { RouteRecordRaw, type RouteLocationNormalized} from 'vue-router'
import { IDesktop, IRoute, IBlockchainDesktops, IHealthResponse } from './types'
import { api } from '../api'
import { useRouter } from 'vue-router'
const desktopHashMap = {
'hash1': Desktops.UserDesktopModel.manifest, //User
@@ -0,0 +1 @@
export {default as BaseDocument} from './BaseDocument.vue'
@@ -1,14 +1,14 @@
<template lang="pug">
.row.justify-center
div.col-md-8.col-xs-12
doc(:doc="complexDocument.statement.document" :action="complexDocument.statement.action" style="margin-bottom: 50px;")
doc(v-if="complexDocument.decision.document" :doc="complexDocument.decision.document" :action="complexDocument.decision.action")
BaseDocument(:doc="complexDocument.statement.document" :action="complexDocument.statement.action" style="margin-bottom: 50px;")
BaseDocument(v-if="complexDocument.decision.document" :doc="complexDocument.decision.document" :action="complexDocument.decision.action")
</template>
<script setup lang="ts">
import { computed } from 'vue'
import doc from './../doc.vue'
import { BaseDocument } from '../../BaseDocument';
import { Cooperative } from 'cooptypes'
const props = defineProps({
@@ -0,0 +1 @@
export {default as RegistratorJoincoopDocument} from './RegistratorJoincoopDocument.vue'
@@ -3,7 +3,7 @@ import { reactive } from 'vue';
interface IInstallCooperative {
data: {
test1: string
wif: string
}
}
@@ -14,11 +14,13 @@ export const useInstallCooperativeStore = defineStore(
(): IInstallCooperative => {
const data = reactive({
test1: 'test'
wif: ''
})
return {
data
}
}, {
persist: true,
})
@@ -1 +1,2 @@
export * as InstallCooperativeModel from './model'
export * from './ui'
@@ -0,0 +1,16 @@
<template lang="pug">
q-btn(@click="next" color="primary" :disabled="!wif").q-mt-md продолжить
</template>
<script lang="ts" setup>
import { useInstallCooperativeStore } from 'src/entities/Installer/model';
import { computed } from 'vue';
const install = useInstallCooperativeStore()
const wif = computed(() => install.data.wif)
const next = () => {
console.log('on next feature')
}
</script>
@@ -0,0 +1 @@
export {default as SetInstallKeyButton} from './SetInstallKeyButton.vue'
@@ -1,15 +0,0 @@
<template lang="pug">
div
p Введите приватный ключ кооператива, выданный при регистрации в ПК ВОСХОД.
q-input(label="Приватный ключ кооператива" v-model="key")
</template>
<script lang="ts" setup>
import { useInstallCooperative } from 'src/features/InstallCooperative/model';
import { ref } from 'vue';
const key = ref('')
// const {authorizeKey} = useInstallCooperative()
</script>
@@ -1 +0,0 @@
export default {}
@@ -23,17 +23,15 @@ import {
IUserData,
useCurrentUserStore,
} from 'src/entities/User';
import { hashSHA256 } from 'src/shared/api/crypto';
export interface ICreateUser {
email: string;
entrepreneur_data?: IEntrepreneurData;
individual_data?: IIndividualData;
organization_data?: IOrganizationData;
password: string;
public_key: string;
referer?: string;
role: 'user' | 'admin';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -150,7 +148,6 @@ export function useCreateUser() {
userData: IUserData,
account: IGeneratedAccount
): Promise<void> {
const password = await hashSHA256(account.private_key);
const synthData = { type: userData.type } as any;
@@ -167,9 +164,8 @@ export function useCreateUser() {
email,
username: account.username,
public_key: account.public_key,
password,
};
console.log(data);
const { user, tokens } = await api.createUser(data);
const globalStore = useGlobalStore();
@@ -0,0 +1,40 @@
<template lang="pug">
div
q-btn(outline @click="showAdd = true") добавить пайщика
q-dialog(v-model="showAdd" persistent :maximized="true" )
q-card
div()
q-bar.bg-primary.text-white
span Добавить пайщика
q-space
q-btn(v-close-popup dense flat icon="close")
q-tooltip Закрыть
div.q-pa-sm.row.justify-center
div.q-pa-md
span Внимание! Вы собираетесь добавить действующего пайщика в реестр цифрового кооператива. Пайщик получит приглашение на электронную почту и сможет сразу использовать систему без заполнения заявления на вступление и оплаты вступительного взноса, т.к. он сделал это ранее вне цифровой системы.
q-stepper(v-model='step', vertical, animated, flat, done-color='primary')
SetUserDataForm
//- q-input(v-model="newPersona.username" label="Имя аккаунта")
//- q-input(v-model="newPersona.position_title" label="Должность")
div.q-mt-lg
q-btn(flat @click="showAdd = false") Назад
q-btn(color="primary" @click="addUser") Добавить
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { SetUserDataForm } from '../SetUserDataForm';
const showAdd = ref(false)
const step = ref(1)
const addUser = () => {
console.log('on add')
}
</script>
@@ -0,0 +1 @@
export {default as AddUserButton} from './AddUserButton.vue'
@@ -0,0 +1,280 @@
<template lang="pug">
q-form(ref="localUserDataForm")
div.full-width.text-center
q-btn-group(flat)
q-btn(glossy label="Физлицо" :color="userData.type === 'individual' ? 'primary' : undefined" @click="userData.type = 'individual'")
q-btn(glossy label="Предприниматель" :color="userData.type === 'entrepreneur' ? 'primary' : undefined" @click="userData.type='entrepreneur'")
q-btn(glossy label="Организация" :color="userData.type === 'organization' ? 'primary' : undefined" @click="userData.type = 'organization'")
div(v-if="userData.type === 'individual' && userData.individual_data").q-gutter-md.q-mt-md
q-input(v-model="userData.individual_data.last_name" filled hint="Иванов" label="Фамилия" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.individual_data.first_name" filled hint="Иван" label="Имя" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.individual_data.middle_name" filled hint="Иванович" label="Отчество" :rules="[val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.individual_data.full_address" filled hint="г. Москва, ул. Арбат, д.12" label="Адрес регистрации (как в паспорте)" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(
filled
v-model="userData.individual_data.birthdate"
mask="date"
label="Дата рождения"
hint="Формат: год/месяц/день"
:rules="['date', val => notEmpty(val)]"
autocomplete="off"
)
template(v-slot:append)
q-icon(name="event" class="cursor-pointer")
q-popup-proxy(cover transition-show="scale" transition-hide="scale")
q-date(v-model="userData.individual_data.birthdate")
.row.items-center.justify-end
q-btn(v-close-popup label="Close" color="primary" flat)
q-input(v-model="userData.individual_data.phone" filled mask="+7 (###) ###-##-##" fill-mask label="Номер телефона" hint="+7 (###) ###-##-##" :rules="[val => notEmpty(val)]" autocomplete="off")
div(v-if="userData.type === 'organization' && userData.organization_data").q-gutter-md.q-mt-md
q-select(
v-model="userData.organization_data.type"
label="Выберите тип организации"
filled
:options="[{ label: 'ООО', value: 'ooo' }, { label: 'Потребительский Кооператив', value: 'coop' }]"
emit-value
map-options)
q-input(v-model="userData.organization_data.short_name" filled hint="ООО Ромашка" label="Краткое наименование организации" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.full_name" filled hint="Общество Ограниченной Ответственности 'Ромашка'" label="Полное наименование организации" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.last_name" filled label="Фамилия представителя" hint="Иванов" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.first_name" filled label="Имя представителя" hint="Иван" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.middle_name" filled label="Отчество представителя" hint="Иванович" :rules="[val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.based_on" filled label="Представитель действует на основании" hint="решения учредителей №1 от 01.01.2021 г" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.position" filled label="Должность представителя" hint="Директор" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.phone" filled label="Номер телефона представителя" mask="+7 (###) ###-##-##" fill-mask hint="+7 (###) ###-##-##" :rules="[val => notEmpty(val)]" autocomplete="off")
q-select(v-model="userData.organization_data.country" filled map-options emit-value option-label="label" option-value="value" label="Страна" :options="[{ label: 'Россия', value: 'Russia' }]" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.city" filled label="Город" hint="Москва" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.full_address" filled hint="г. Москва, ул. Арбат, д.12" label="Юридический адрес регистрации" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(
v-model="userData.organization_data.details.inn"
filled
mask="############"
label="ИНН организации"
hint="10 или 12 цифр"
:rules="[val => notEmpty(val), val => (val.length === 10 || val.length === 12) || 'ИНН должен содержать 10 или 12 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.details.ogrn"
filled
mask="###############"
label="ОГРН организации"
hint="13 или 15 цифр"
:rules="[val => notEmpty(val), val => (val.length === 13 || val.length === 15) || 'ОГРН должен содержать 13 или 15 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.bank_name"
filled
label="Наименование банка"
hint="ПАО Сбербанк"
:rules="[val => notEmpty(val)]"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.details.corr"
filled
mask="####################"
label="Корреспондентский счет"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Корреспондентский счет должен содержать 20 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.details.bik"
filled
mask="#########"
label="БИК"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'БИК должен содержать 9 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.details.kpp"
filled
mask="#########"
label="КПП (банка)"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'КПП должен содержать 9 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.account_number"
filled
mask="####################"
label="Номер счета"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Номер счета должен содержать 20 цифр']"
autocomplete="off"
)
q-select(
v-model="userData.organization_data.bank_account.currency"
label="Валюта счёта"
filled
:options="[{ label: 'RUB', value: 'RUB' }]"
emit-value
:rules="[val => notEmpty(val)]"
map-options
)
div(v-if="userData.type === 'entrepreneur' && userData.entrepreneur_data").q-gutter-md.q-mt-md
q-input(v-model="userData.entrepreneur_data.last_name" filled hint="Иванов" label="Фамилия" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.first_name" filled hint="Иван" label="Имя" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.middle_name" filled hint="Иванович" label="Отчество" :rules="[val => validateNames(val)]" autocomplete="off")
q-input(
filled
v-model="userData.entrepreneur_data.birthdate"
mask="date"
label="Дата рождения"
hint="Формат: год/месяц/день"
:rules="['date', val => notEmpty(val)]"
autocomplete="off"
)
template(v-slot:append)
q-icon(name="event" class="cursor-pointer")
q-popup-proxy(cover transition-show="scale" transition-hide="scale")
q-date(v-model="userData.entrepreneur_data.birthdate")
.row.items-center.justify-end
q-btn(v-close-popup label="Close" color="primary" flat)
q-input(v-model="userData.entrepreneur_data.phone" filled mask="+7 (###) ###-##-##" fill-mask label="Номер телефона" hint="+7 (###) ###-##-##" :rules="[val => notEmpty(val)]" autocomplete="off")
q-select(v-model="userData.entrepreneur_data.country" filled map-options emit-value option-label="label" option-value="value" label="Страна" :options="[{ label: 'Россия', value: 'Russia' }]" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.city" filled label="Город" hint="Москва" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.full_address" filled label="Адрес регистрации (как в паспорте)" hint="г. Москва, ул. Арбат, д.12" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(
v-model="userData.entrepreneur_data.details.inn"
filled
mask="############"
label="ИНН предпринимателя"
hint="10 или 12 цифр"
:rules="[val => notEmpty(val), val => (val.length === 10 || val.length === 12) || 'ИНН должен содержать 10 или 12 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.details.ogrn"
filled
mask="###############"
label="ОГРНИП"
hint="13 или 15 цифр"
:rules="[val => notEmpty(val), val => (val.length === 13 || val.length === 15) || 'ОГРНИП должен содержать 13 или 15 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.bank_name"
filled
label="Наименование банка"
hint="ПАО Сбербанк"
:rules="[val => notEmpty(val)]"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.details.corr"
filled
mask="####################"
label="Корреспондентский счёт"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Корреспондентский счёт должен содержать 20 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.details.bik"
filled
mask="#########"
label="БИК"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'БИК должен содержать 9 цифр']"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.details.kpp"
filled
mask="#########"
label="КПП (банка)"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'КПП должен содержать 9 цифр']"
autocomplete="off"
)
q-select(v-model="userData.entrepreneur_data.bank_account.currency"
label="Валюта счёта"
filled
:options="[{ label: 'RUB', value: 'RUB' }]"
emit-value
map-options
)
q-input(
v-model="userData.entrepreneur_data.bank_account.account_number"
filled
mask="####################"
label="Номер счёта"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Номер счёта должен содержать 20 цифр']"
autocomplete="off"
)
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'
import { createUserStore as store } from '../../model'
import type { IUserData } from 'src/entities/User';
const userData = ref<IUserData>(store.userData)
watch(() => userData.value?.organization_data?.type, (newValue) => {
if (userData.value.organization_data) {
if (newValue === 'coop') {
userData.value.organization_data.is_cooperative = true;
} else {
userData.value.organization_data.is_cooperative = false;
}
}
});
const localUserDataForm = ref(null)
const emit = defineEmits(['setDataFormRef'])
const validateNames = (val: any) => {
return val === '' || /^[a-zA-Zа-яА-Я\- ]*$/.test(val) || 'Разрешены только буквы латинского алфавита, кириллица, знак - и пробел'
}
const notEmpty = (val: any) => {
return !!val || 'Это поле обязательно для заполнения'
}
onMounted(() => {
emit('setDataFormRef', localUserDataForm.value);
});
</script>
<style>
.dataInput .q-btn-selected {
color: teal;
}
</style>
@@ -0,0 +1 @@
export {default as SetUserDataForm} from './SetUserDataForm.vue'
@@ -0,0 +1,14 @@
<template lang="pug">
ListOfParticipantsWidget
template(#top)
AddUserButton
template(#default="{ expand, receiver }")
ListOfDocumentsWidget(v-if="expand" :reciever="receiver")
</template>
<script lang="ts" setup>
import { ListOfParticipantsWidget } from 'src/widgets/Cooperative/Participants/ListOfParticipants';
import { ListOfDocumentsWidget } from 'src/widgets/Cooperative/Documents/ListOfDocuments';
import { AddUserButton } from 'src/features/User/CreateUser/ui/AddUserButton';
</script>
@@ -0,0 +1 @@
export {default as ListOfParticipantsPage} from './ListOfParticipantsPage.vue'
@@ -1,7 +0,0 @@
<template lang="pug">
InstallCooperative
</template>
<script setup lang="ts">
import { InstallCooperative } from 'src/widgets/Cooperative/Install/ui';
</script>
@@ -0,0 +1,9 @@
<template lang="pug">
div.row.justify-center.q-pt-lg
div.col-md-6.col-xs-12
InstallCooperativeWidgetBase
</template>
<script setup lang="ts">
import { InstallCooperativeWidgetBase } from 'src/widgets/Cooperative/Install/InstallWidgetBase';
</script>
@@ -1 +1 @@
export {default as InstallCooperativePage} from './Install.vue'
export {default as InstallCooperativePage} from './InstallCooperativePage.vue'
@@ -3,7 +3,7 @@ PersonalCard(:username="username")
</template>
<script lang="ts" setup>
import { PersonalCard } from 'src/widgets/Wallet/PersonalCard/ui'
import { PersonalCard } from 'src/widgets/User/PersonalCard/ui'
import { computed } from 'vue';
import { useCurrentUserStore } from 'src/entities/User';
@@ -3,7 +3,7 @@ PaymentMethodsCard(:username="username")
</template>
<script lang="ts" setup>
import { PaymentMethodsCard } from 'src/widgets/Wallet/PaymentMethods';
import { PaymentMethodsCard } from 'src/widgets/User/PaymentMethods';
import { computed } from 'vue';
import { useCurrentUserStore } from 'src/entities/User';
@@ -0,0 +1,11 @@
<template lang="pug">
div.row.justify-center.q-mt-lg
div.col-md-6.col-xs-12
LogoutCard
ExitCard.q-mt-lg
</template>
<script lang="ts" setup>
import { LogoutCard } from 'src/widgets/User/LogoutCard';
import { ExitCard } from 'src/widgets/Cooperative/Participants/ExitCard';
</script>
@@ -0,0 +1 @@
export {default as UserSettingsPage} from './UserSettingsPage.vue'
@@ -2,7 +2,7 @@
WalletCard(:username="username")
</template>
<script lang="ts" setup>
import { WalletCard } from 'src/widgets/Wallet/WalletCard'
import { WalletCard } from 'src/widgets/User/WalletCard'
import { computed } from 'vue';
import { useCurrentUserStore } from 'src/entities/User';
@@ -1,312 +0,0 @@
<template lang="pug">
div(v-if="step").dataInput
q-step(
:name="2"
title="Заполните форму заявления на вступление"
:done="step > 2"
)
q-form(ref="userDataForm")
div.full-width.text-center
q-btn-group(flat)
q-btn(glossy label="Физлицо" :color="userData.type === 'individual' ? 'primary' : undefined" @click="userData.type = 'individual'")
q-btn(glossy label="Предприниматель" :color="userData.type === 'entrepreneur' ? 'primary' : undefined" @click="userData.type='entrepreneur'")
q-btn(glossy label="Организация" :color="userData.type === 'organization' ? 'primary' : undefined" @click="userData.type = 'organization'")
div(v-if="userData.type === 'individual' && userData.individual_data").q-gutter-md.q-mt-md
q-input(v-model="userData.individual_data.last_name" filled hint="Иванов" label="Фамилия" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.individual_data.first_name" filled hint="Иван" label="Имя" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.individual_data.middle_name" filled hint="Иванович" label="Отчество" :rules="[val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.individual_data.full_address" filled hint="г. Москва, ул. Арбат, д.12" label="Адрес регистрации (как в паспорте)" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(
filled
v-model="userData.individual_data.birthdate"
mask="date"
label="Дата рождения"
hint="Формат: год/месяц/день"
:rules="['date', val => notEmpty(val)]"
autocomplete="off"
)
template(v-slot:append)
q-icon(name="event" class="cursor-pointer")
q-popup-proxy(cover transition-show="scale" transition-hide="scale")
q-date(v-model="userData.individual_data.birthdate")
.row.items-center.justify-end
q-btn(v-close-popup label="Close" color="primary" flat)
q-input(v-model="userData.individual_data.phone" filled mask="+7 (###) ###-##-##" fill-mask label="Номер телефона" hint="+7 (###) ###-##-##" :rules="[val => notEmpty(val)]" autocomplete="off")
div(v-if="userData.type === 'organization' && userData.organization_data").q-gutter-md.q-mt-md
q-select(
v-model="userData.organization_data.type"
label="Выберите тип организации"
filled
:options="[{ label: 'ООО', value: 'ooo' }, { label: 'Потребительский Кооператив', value: 'coop' }]"
emit-value
map-options)
q-input(v-model="userData.organization_data.short_name" filled hint="ООО Ромашка" label="Краткое наименование организации" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.full_name" filled hint="Общество Ограниченной Ответственности 'Ромашка'" label="Полное наименование организации" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.last_name" filled label="Фамилия представителя" hint="Иванов" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.first_name" filled label="Имя представителя" hint="Иван" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.middle_name" filled label="Отчество представителя" hint="Иванович" :rules="[val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.based_on" filled label="Представитель действует на основании" hint="решения учредителей №1 от 01.01.2021 г" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.represented_by.position" filled label="Должность представителя" hint="Директор" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.phone" filled label="Номер телефона представителя" mask="+7 (###) ###-##-##" fill-mask hint="+7 (###) ###-##-##" :rules="[val => notEmpty(val)]" autocomplete="off")
q-select(v-model="userData.organization_data.country" filled map-options emit-value option-label="label" option-value="value" label="Страна" :options="[{ label: 'Россия', value: 'Russia' }]" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.city" filled label="Город" hint="Москва" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.organization_data.full_address" filled hint="г. Москва, ул. Арбат, д.12" label="Юридический адрес регистрации" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(
v-model="userData.organization_data.details.inn"
filled
mask="############"
label="ИНН организации"
hint="10 или 12 цифр"
:rules="[val => notEmpty(val), val => (val.length === 10 || val.length === 12) || 'ИНН должен содержать 10 или 12 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.details.ogrn"
filled
mask="###############"
label="ОГРН организации"
hint="13 или 15 цифр"
:rules="[val => notEmpty(val), val => (val.length === 13 || val.length === 15) || 'ОГРН должен содержать 13 или 15 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.bank_name"
filled
label="Наименование банка"
hint="ПАО Сбербанк"
:rules="[val => notEmpty(val)]"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.details.corr"
filled
mask="####################"
label="Корреспондентский счет"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Корреспондентский счет должен содержать 20 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.details.bik"
filled
mask="#########"
label="БИК"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'БИК должен содержать 9 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.details.kpp"
filled
mask="#########"
label="КПП (банка)"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'КПП должен содержать 9 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.organization_data.bank_account.account_number"
filled
mask="####################"
label="Номер счета"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Номер счета должен содержать 20 цифр']"
autocomplete="off"
)
q-select(
v-model="userData.organization_data.bank_account.currency"
label="Валюта счёта"
filled
:options="[{ label: 'RUB', value: 'RUB' }]"
emit-value
:rules="[val => notEmpty(val)]"
map-options
)
div(v-if="userData.type === 'entrepreneur' && userData.entrepreneur_data").q-gutter-md.q-mt-md
q-input(v-model="userData.entrepreneur_data.last_name" filled hint="Иванов" label="Фамилия" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.first_name" filled hint="Иван" label="Имя" :rules="[val => notEmpty(val), val => validateNames(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.middle_name" filled hint="Иванович" label="Отчество" :rules="[val => validateNames(val)]" autocomplete="off")
q-input(
filled
v-model="userData.entrepreneur_data.birthdate"
mask="date"
label="Дата рождения"
hint="Формат: год/месяц/день"
:rules="['date', val => notEmpty(val)]"
autocomplete="off"
)
template(v-slot:append)
q-icon(name="event" class="cursor-pointer")
q-popup-proxy(cover transition-show="scale" transition-hide="scale")
q-date(v-model="userData.entrepreneur_data.birthdate")
.row.items-center.justify-end
q-btn(v-close-popup label="Close" color="primary" flat)
q-input(v-model="userData.entrepreneur_data.phone" filled mask="+7 (###) ###-##-##" fill-mask label="Номер телефона" hint="+7 (###) ###-##-##" :rules="[val => notEmpty(val)]" autocomplete="off")
q-select(v-model="userData.entrepreneur_data.country" filled map-options emit-value option-label="label" option-value="value" label="Страна" :options="[{ label: 'Россия', value: 'Russia' }]" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.city" filled label="Город" hint="Москва" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(v-model="userData.entrepreneur_data.full_address" filled label="Адрес регистрации (как в паспорте)" hint="г. Москва, ул. Арбат, д.12" :rules="[val => notEmpty(val)]" autocomplete="off")
q-input(
v-model="userData.entrepreneur_data.details.inn"
filled
mask="############"
label="ИНН предпринимателя"
hint="10 или 12 цифр"
:rules="[val => notEmpty(val), val => (val.length === 10 || val.length === 12) || 'ИНН должен содержать 10 или 12 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.details.ogrn"
filled
mask="###############"
label="ОГРНИП"
hint="13 или 15 цифр"
:rules="[val => notEmpty(val), val => (val.length === 13 || val.length === 15) || 'ОГРНИП должен содержать 13 или 15 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.bank_name"
filled
label="Наименование банка"
hint="ПАО Сбербанк"
:rules="[val => notEmpty(val)]"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.details.corr"
filled
mask="####################"
label="Корреспондентский счёт"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Корреспондентский счёт должен содержать 20 цифр']"
autocomplete="off"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.details.bik"
filled
mask="#########"
label="БИК"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'БИК должен содержать 9 цифр']"
)
q-input(
v-model="userData.entrepreneur_data.bank_account.details.kpp"
filled
mask="#########"
label="КПП (банка)"
hint="9 цифр"
:rules="[val => notEmpty(val), val => val.length === 9 || 'КПП должен содержать 9 цифр']"
autocomplete="off"
)
q-select(v-model="userData.entrepreneur_data.bank_account.currency"
label="Валюта счёта"
filled
:options="[{ label: 'RUB', value: 'RUB' }]"
emit-value
map-options
)
q-input(
v-model="userData.entrepreneur_data.bank_account.account_number"
filled
mask="####################"
label="Номер счёта"
hint="20 цифр"
:rules="[val => notEmpty(val), val => val.length === 20 || 'Номер счёта должен содержать 20 цифр']"
autocomplete="off"
)
q-checkbox(v-model='store.agreements.condidential' full-width filled).q-mt-lg
| Я даю своё согласие на обработку своих персональных данных в соответствии с
a(@click.stop='(event) => event.stopPropagation()' href='/documents/regulation_on_the_procedure_and_rules_for_using_a_simple_electronic_signature.pdf' target='_blank').q-ml-xs Политикой Конфиденциальности
q-btn(flat, @click="store.step--")
i.fa.fa-arrow-left
span.q-ml-md назад
q-btn(
:disabled="!store.agreements.condidential"
color="primary"
label="Продолжить"
@click="setData"
).q-mt-lg.q-mb-lg
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue'
import { createUserStore as store } from 'src/features/User/CreateUser'
import type { IUserData } from 'src/entities/User';
const userData = ref<IUserData>(store.userData)
watch(() => userData.value?.organization_data?.type, (newValue) => {
if (userData.value.organization_data){
if (newValue === 'coop') {
userData.value.organization_data.is_cooperative = true;
} else {
userData.value.organization_data.is_cooperative = false;
}
}
});
const userDataForm = ref()
const step = computed(() => store.step)
const validateNames = (val: any) => {
return val === '' || /^[a-zA-Zа-яА-Я\- ]*$/.test(val) || 'Разрешены только буквы латинского алфавита, кириллица, знак - и пробел'
}
const notEmpty = (val: any) => {
return !!val || 'Это поле обязательно для заполнения'
}
const setData = () => {
userDataForm.value.validate().then(async (success: boolean) => {
if (success) {
store.userData = userData.value
store.step = store.step + 1
} else {
window.scrollTo(0, 0)
return
}
})
}
</script>
<style>
.dataInput .q-btn-selected {
color: teal;
}
</style>
@@ -0,0 +1,62 @@
<template lang="pug">
div(v-if="step")
q-step(
:name="2"
title="Заполните форму заявления на вступление"
:done="step > 2"
)
SetUserDataForm(@setDataFormRef="setDataFormRef")
q-checkbox(v-model='store.agreements.condidential' full-width filled).q-mt-lg
| Я даю своё согласие на обработку своих персональных данных в соответствии с
a(@click.stop='(event) => event.stopPropagation()' href='/documents/regulation_on_the_procedure_and_rules_for_using_a_simple_electronic_signature.pdf' target='_blank').q-ml-xs Политикой Конфиденциальности
q-btn(flat, @click="store.step--")
i.fa.fa-arrow-left
span.q-ml-md назад
q-btn(
:disabled="!store.agreements.condidential"
color="primary"
label="Продолжить"
@click="setData"
).q-mt-lg.q-mb-lg
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { createUserStore as store } from 'src/features/User/CreateUser';
import type { IUserData } from 'src/entities/User';
import { SetUserDataForm } from 'src/features/User/CreateUser/ui/SetUserDataForm';
const userData = ref<IUserData>(store.userData)
const userDataForm = ref()
const step = computed(() => store.step)
const setDataFormRef = (ref: any) => {
console.log('on set form: ', ref)
userDataForm.value = ref
}
const setData = () => {
userDataForm.value.validate().then(async (success: boolean) => {
if (success) {
store.userData = userData.value
store.step = store.step + 1
} else {
window.scrollTo(0, 0)
return
}
})
}
</script>
<style>
.dataInput .q-btn-selected {
color: teal;
}
</style>
@@ -8,7 +8,7 @@ div
q-stepper(v-model='store.step', vertical, animated, flat, done-color='primary')
EmailInput
DataInput
SetUserData
GenerateAccount
@@ -22,14 +22,13 @@ div
Welcome(v-model:data='store.userData', v-model:step='store.step')
</template>
<script lang="ts" setup>
import { watch, onMounted, onBeforeUnmount } from 'vue'
import EmailInput from './EmailInput.vue'
import GenerateAccount from './GenerateAccount.vue'
import DataInput from './DataInput.vue'
import SetUserData from './SetUserData.vue'
import SignStatement from './SignStatement.vue'
import ReadStatement from './ReadStatement.vue'
import PayInitial from './PayInitial.vue'
@@ -70,7 +70,7 @@ q-card(flat)
q-tr(v-show="props.expand" :key="`e_${props.row.table.id}`" :props="props" class="q-virtual-scroll--with-prev")
q-td(colspan="100%")
joincoopdoc(v-if="props.row.table.type == 'joincoop'" :documents="props.row.documents")
RegistratorJoincoopDocument(v-if="props.row.table.type == 'joincoop'" :documents="props.row.documents")
@@ -81,7 +81,7 @@ import { ref, onBeforeUnmount } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
import { Notify } from 'quasar'
import joincoopdoc from './docs/joincoop.vue'
import { RegistratorJoincoopDocument } from 'src/entities/Document/ui/Templates/RegistratorJoincoop';
import { sendGET } from 'src/shared/api';
import { useSessionStore } from 'src/entities/Session';
import { Cooperative, SovietContract } from 'cooptypes'
@@ -89,7 +89,7 @@ import { useVoteForDecision } from 'src/features/Cooperative/VoteForDecision';
import { useAuthorizeAndExecDecision } from 'src/features/Cooperative/AuthorizeAndExecDecision';
import { useVoteAgainstDecision } from 'src/features/Cooperative/VoteAgainstDecision';
import { COOPNAME } from 'src/shared/config';
import { useCooperativeStore } from '../../entities/Cooperative/model/stores';
import { useCooperativeStore } from 'src/entities/Cooperative/model/stores';
const session = useSessionStore()
const onLoading = ref(false)
@@ -0,0 +1 @@
export {default as ListOfQuestionsWidget} from './ListOfQuestions.vue'
@@ -35,22 +35,12 @@ div.row.justify-center
q-td {{ props.row.statement?.action?.data?.decision_id }}
q-td {{ props.row.statement?.document?.full_title }}
//- {{ getHumanActionName(props.row.act.data.action) }}
//- q-td {{ moment(props.row.statement.document).format('DD.MM.YY HH:mm:ss') }}
//- q-td {{ props.row.act.data.username }}
//- p {{ props.row?.statement?.action?.transaction_id }}
q-tr(v-if="expanded.get(props.row?.statement?.action?.transaction_id)" :key="`e_${props.row?.statement?.action?.transaction_id}`" :props="props" class="q-virtual-scroll--with-prev")
q-td(colspan="100%")
joincoopdoc(v-if="props.row?.statement?.action?.data?.action == 'joincoop'" :documents="props.row")
RegistratorJoincoopDocument(v-if="props.row?.statement?.action?.data?.action == 'joincoop'" :documents="props.row")
// div(v-else)
// p.full-width.text-center.text-grey.no-select у кооператива нет документов
</template>
<script setup lang="ts">
@@ -58,7 +48,7 @@ import { onMounted, ref, computed, reactive } from 'vue'
import { useRoute } from 'vue-router'
import { Notify } from 'quasar'
import { sendGET } from 'src/shared/api';
import joincoopdoc from './docs/joincoop.vue'
import { RegistratorJoincoopDocument } from 'src/entities/Document/ui/Templates/RegistratorJoincoop';
const route = useRoute()
const documents = ref([])
const onLoading = ref(false)
@@ -0,0 +1 @@
export {default as ListOfDocumentsWidget} from './ListOfDocumentsWidget.vue'
@@ -0,0 +1,14 @@
<template lang="pug">
div
q-card().q-pa-md
WelcomeAndRequestKeyOnInstallWidget
</template>
<script lang="ts" setup>
import { useInstallCooperativeStore } from 'src/entities/Installer/model';
import { WelcomeAndRequestKeyOnInstallWidget } from '../../WelcomeAndRequestKey';
const install = useInstallCooperativeStore()
</script>
@@ -0,0 +1 @@
export {default as InstallCooperativeWidgetBase} from './InstallWidgetBase.vue'
@@ -0,0 +1 @@
export * from './ui'
@@ -0,0 +1,24 @@
<template lang="pug">
div
p.text-h6.full-width.text-center Установка
div.q-mt-md
span Установщик настроит параметры работы Вашего Цифрового Кооператива. Для продолжения введите ключ Кооператива, который был выдан Вам при подключении:
q-input(filled label="Ключ Кооператива" v-model="wif" type="password" autocomplete="off").q-mt-md
SetInstallKeyButton
</template>
<script setup lang="ts">
import { useInstallCooperativeStore } from 'src/entities/Installer/model';
import { SetInstallKeyButton } from 'src/features/Install';
import { ref, watch } from 'vue';
const wif = ref('')
const install = useInstallCooperativeStore()
watch(wif, (newValue: string) => install.data.wif = newValue)
install.data.wif = ''
</script>
@@ -0,0 +1 @@
export {default as WelcomeAndRequestKeyOnInstallWidget } from './WelcomeAndRequestKey.vue'
@@ -1,9 +0,0 @@
<template lang="pug">
div install
</template>
<script setup lang="ts">
// import { useInstallCooperative } from 'src/features/InstallCooperative/model';
// const { install, store } = useInstallCooperative()
</script>
@@ -1 +0,0 @@
export {default as InstallCooperative } from './Install.vue'
@@ -0,0 +1 @@
export * from './ui'
@@ -0,0 +1,15 @@
<template lang="pug">
q-card(flat bordered).q-pa-md
span.text-h6 Выход из Кооператива
div
span После рассмотрения Советом вашего заявления о выходе из Кооператива, вам будет возвращен минимальный паевый взнос, а Вы будете исключены из реестра пайщиков кооператива.
q-btn(color="red" @click="exit").q-mt-md
q-icon( color="white" name="send")
span.q-ml-sm Отправить заявление
</template>
<script lang="ts" setup>
const exit = () => {
console.log('on exit')
}
</script>

Some files were not shown because too many files have changed in this diff Show More