feat(profile): add counterparty profile and enforce it for order creation
This commit is contained in:
@@ -66,6 +66,62 @@ function buildDefaultFullName(email) {
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value ?? '').trim();
|
||||
}
|
||||
|
||||
function normalizeOptionalText(value) {
|
||||
const normalized = normalizeText(value);
|
||||
return normalized ? normalized : null;
|
||||
}
|
||||
|
||||
function isCounterpartyProfileComplete(profile) {
|
||||
if (!profile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(
|
||||
normalizeText(profile.companyName) &&
|
||||
normalizeText(profile.companyFullName) &&
|
||||
normalizeText(profile.inn) &&
|
||||
normalizeText(profile.legalAddress) &&
|
||||
normalizeText(profile.bankName) &&
|
||||
normalizeText(profile.bik) &&
|
||||
normalizeText(profile.correspondentAccount) &&
|
||||
normalizeText(profile.checkingAccount) &&
|
||||
normalizeText(profile.signerFullName) &&
|
||||
normalizeText(profile.signerPosition) &&
|
||||
normalizeText(profile.signerBasis),
|
||||
);
|
||||
}
|
||||
|
||||
function toCounterpartyProfileInputData(input) {
|
||||
return {
|
||||
companyName: normalizeText(input.companyName),
|
||||
companyFullName: normalizeText(input.companyFullName),
|
||||
inn: normalizeText(input.inn),
|
||||
kpp: normalizeOptionalText(input.kpp),
|
||||
ogrn: normalizeOptionalText(input.ogrn),
|
||||
legalAddress: normalizeText(input.legalAddress),
|
||||
bankName: normalizeText(input.bankName),
|
||||
bik: normalizeText(input.bik),
|
||||
correspondentAccount: normalizeText(input.correspondentAccount),
|
||||
checkingAccount: normalizeText(input.checkingAccount),
|
||||
signerFullName: normalizeText(input.signerFullName),
|
||||
signerPosition: normalizeText(input.signerPosition),
|
||||
signerBasis: normalizeText(input.signerBasis),
|
||||
};
|
||||
}
|
||||
|
||||
async function requireCompletedCounterpartyProfile(context, userId) {
|
||||
const profile = await context.prisma.counterpartyProfile.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
if (!isCounterpartyProfileComplete(profile)) {
|
||||
throw new Error('Counterparty profile is incomplete. Fill profile before placing an order.');
|
||||
}
|
||||
}
|
||||
|
||||
function formatOrderStatusMessage(order, status, note) {
|
||||
const suffix = note ? `\nКомментарий: ${note}` : '';
|
||||
return `Заказ ${order.code} изменил статус: ${status}.${suffix}`;
|
||||
@@ -154,12 +210,22 @@ async function collectNotificationHistory(context, userId, channel, limit) {
|
||||
export const resolvers = {
|
||||
DateTime: dateTimeScalar,
|
||||
JSON: jsonScalar,
|
||||
CounterpartyProfile: {
|
||||
isComplete: (profile) => isCounterpartyProfileComplete(profile),
|
||||
},
|
||||
|
||||
Query: {
|
||||
healthcheck: () => 'ok',
|
||||
|
||||
me: (_, __, context) => context.user,
|
||||
|
||||
myCounterpartyProfile: async (_, __, context) => {
|
||||
const user = requireUser(context);
|
||||
return context.prisma.counterpartyProfile.findUnique({
|
||||
where: { userId: user.id },
|
||||
});
|
||||
},
|
||||
|
||||
myMessengerConnections: async (_, __, context) => {
|
||||
const user = requireUser(context);
|
||||
return context.prisma.messengerConnection.findMany({
|
||||
@@ -443,6 +509,24 @@ export const resolvers = {
|
||||
return user;
|
||||
},
|
||||
|
||||
upsertMyCounterpartyProfile: async (_, { input }, context) => {
|
||||
const user = requireUser(context);
|
||||
const payload = toCounterpartyProfileInputData(input);
|
||||
|
||||
if (!isCounterpartyProfileComplete(payload)) {
|
||||
throw new Error('Counterparty profile is incomplete. Fill all required fields.');
|
||||
}
|
||||
|
||||
return context.prisma.counterpartyProfile.upsert({
|
||||
where: { userId: user.id },
|
||||
update: payload,
|
||||
create: {
|
||||
userId: user.id,
|
||||
...payload,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
connectMessenger: (_, { input }, context) => {
|
||||
const user = requireUser(context);
|
||||
return context.prisma.messengerConnection.upsert({
|
||||
@@ -499,6 +583,7 @@ export const resolvers = {
|
||||
|
||||
submitReadyOrder: async (_, { input }, context) => {
|
||||
const customer = requireRole(context, 'CLIENT');
|
||||
await requireCompletedCounterpartyProfile(context, customer.id);
|
||||
if (!input.items.length) {
|
||||
throw new Error('Order must contain at least one item.');
|
||||
}
|
||||
@@ -548,6 +633,7 @@ export const resolvers = {
|
||||
|
||||
submitCalculationOrder: async (_, { input }, context) => {
|
||||
const customer = requireRole(context, 'CLIENT');
|
||||
await requireCompletedCounterpartyProfile(context, customer.id);
|
||||
const order = await context.prisma.order.create({
|
||||
data: {
|
||||
code: orderCode(),
|
||||
|
||||
@@ -65,6 +65,27 @@ type User {
|
||||
company: Company
|
||||
}
|
||||
|
||||
type CounterpartyProfile {
|
||||
id: ID!
|
||||
userId: ID!
|
||||
companyName: String!
|
||||
companyFullName: String!
|
||||
inn: String!
|
||||
kpp: String
|
||||
ogrn: String
|
||||
legalAddress: String!
|
||||
bankName: String!
|
||||
bik: String!
|
||||
correspondentAccount: String!
|
||||
checkingAccount: String!
|
||||
signerFullName: String!
|
||||
signerPosition: String!
|
||||
signerBasis: String!
|
||||
isComplete: Boolean!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type AuthCodeRequestResult {
|
||||
challengeToken: String!
|
||||
channel: LoginChannel!
|
||||
@@ -222,6 +243,7 @@ type ReferralStats {
|
||||
type Query {
|
||||
healthcheck: String!
|
||||
me: User
|
||||
myCounterpartyProfile: CounterpartyProfile
|
||||
myMessengerConnections: [MessengerConnection!]!
|
||||
myNotificationHistory(channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]!
|
||||
managerNotificationHistory(userId: ID!, channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]!
|
||||
@@ -272,6 +294,22 @@ input ConnectMessengerInput {
|
||||
channelId: String!
|
||||
}
|
||||
|
||||
input UpsertMyCounterpartyProfileInput {
|
||||
companyName: String!
|
||||
companyFullName: String!
|
||||
inn: String!
|
||||
kpp: String
|
||||
ogrn: String
|
||||
legalAddress: String!
|
||||
bankName: String!
|
||||
bik: String!
|
||||
correspondentAccount: String!
|
||||
checkingAccount: String!
|
||||
signerFullName: String!
|
||||
signerPosition: String!
|
||||
signerBasis: String!
|
||||
}
|
||||
|
||||
input ReadyOrderItemInput {
|
||||
productId: ID!
|
||||
quantity: Float!
|
||||
@@ -329,6 +367,7 @@ type Mutation {
|
||||
createInvitation(input: CreateInvitationInput!): Invitation!
|
||||
acceptInvitation(input: AcceptInvitationInput!): User!
|
||||
connectMessenger(input: ConnectMessengerInput!): MessengerConnection!
|
||||
upsertMyCounterpartyProfile(input: UpsertMyCounterpartyProfileInput!): CounterpartyProfile!
|
||||
sendTestMessengerMessage(type: MessengerType!, channelId: String, message: String): MessengerDispatchResult!
|
||||
|
||||
submitReadyOrder(input: SubmitReadyOrderInput!): Order!
|
||||
|
||||
Reference in New Issue
Block a user