diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/core/package.json b/packages/core/package.json index 7311a50159..e384d93b36 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,7 +33,8 @@ "migrateFields": "node_modules/.bin/ts-node src/commands/migrateFields.ts", "migrateClientPortalUsers": "node_modules/.bin/ts-node src/commands/migrateClientPortalUsers.ts", "migrateUserMovements": "node_modules/.bin/ts-node src/commands/migrateUserMovements.ts", - "updateUserNames": "node_modules/.bin/ts-node src/commands/updateUserNames.ts" + "updateUserNames": "node_modules/.bin/ts-node src/commands/updateUserNames.ts", + "migratePayment": "node_modules/.bin/ts-node src/commands/migratePayment.ts" }, "dependencies": { "agentkeepalive": "^4.3.0", @@ -61,7 +62,7 @@ "ts-node": "10.9.1", "validator": "^9.0.0", "xlsx-populate": "^1.20.1", - "jimp":"^0.22.10", + "jimp": "^0.22.10", "tus-js-client": "^3.1.1" } } diff --git a/packages/core/src/commands/migratePayment.ts b/packages/core/src/commands/migratePayment.ts new file mode 100644 index 0000000000..28db778cbd --- /dev/null +++ b/packages/core/src/commands/migratePayment.ts @@ -0,0 +1,51 @@ +import * as dotenv from 'dotenv'; + +dotenv.config(); +import { Collection, Db, MongoClient } from 'mongodb'; + +const { MONGO_URL } = process.env; + +if (!MONGO_URL) { + throw new Error(`Environment variable MONGO_URL not set.`); +} + +const client = new MongoClient(MONGO_URL); + +let db: Db; + +let DealCollections: Collection; +const command = async () => { + await client.connect(); + db = client.db() as Db; + DealCollections = db.collection('deals'); + + const deals = await DealCollections.find({}).toArray(); + + // tslint:disable-next-line:no-shadowed-variable + for (const deal of deals) { + const { _id, ...item } = deal; + const paymentsData = item.paymentsData; + + // tslint:disable-next-line:prefer-const + let updatedPaymentsData = { ...paymentsData }; // Create a copy of paymentsData + + // tslint:disable-next-line:forin + for (const key in paymentsData) { + updatedPaymentsData[key] = { + ...paymentsData[key], // Copy the existing properties of + title: key, // Add the title property with the key as its value + type: key + }; + } + + await DealCollections.updateOne( + { _id }, + { $set: { paymentsData: updatedPaymentsData } } + ); + } + + console.log(`Process finished at: ${new Date()}`); + process.exit(); +}; + +command(); diff --git a/packages/plugin-cards-api/src/configs.ts b/packages/plugin-cards-api/src/configs.ts index e20ff18d44..e5ba4dddab 100644 --- a/packages/plugin-cards-api/src/configs.ts +++ b/packages/plugin-cards-api/src/configs.ts @@ -23,6 +23,7 @@ import exporter from './exporter'; import cronjobs from './cronjobs/common'; import dashboards from './dashboards'; import { NOTIFICATION_MODULES } from './constants'; +import payment from './payment'; export let mainDb; export let graphqlPubsub; @@ -59,7 +60,8 @@ export default { permissions, documents, dashboards, - notificationModules: NOTIFICATION_MODULES + notificationModules: NOTIFICATION_MODULES, + payment }, apolloServerContext: async (context, req, res) => { diff --git a/packages/plugin-cards-api/src/connectionResolver.ts b/packages/plugin-cards-api/src/connectionResolver.ts index fcb3b6c111..d35345cd55 100644 --- a/packages/plugin-cards-api/src/connectionResolver.ts +++ b/packages/plugin-cards-api/src/connectionResolver.ts @@ -48,12 +48,14 @@ import { IPipelineTemplateDocument } from './models/definitions/pipelineTemplate import { createGenerateModels } from '@erxes/api-utils/src/core'; import { ICostModel } from './models/Costs'; import { ICostDocument } from './models/definitions/costs'; - +import { IPaymentTypeDocument } from './models/definitions/payments'; +import { IPaymentTypeModel, loadPaymentTypeClass } from './models/Payments'; export interface IModels { Boards: IBoardModel; Pipelines: IPipelineModel; Stages: IStageModel; Costs: ICostModel; + PaymentTypes: IPaymentTypeModel; Deals: IDealModel; Purchases: IPurchaseModel; Tasks: ITaskModel; @@ -88,7 +90,10 @@ export const loadClasses = ( 'expenses', loadCostClass(models, subdomain) ); - + models.PaymentTypes = db.model( + 'payment_types', + loadPaymentTypeClass(models, subdomain) + ); models.Pipelines = db.model( 'pipelines', loadPipelineClass(models, subdomain) diff --git a/packages/plugin-cards-api/src/graphql/resolvers/mutations/deals.ts b/packages/plugin-cards-api/src/graphql/resolvers/mutations/deals.ts index e5d5a99fb5..379de37239 100644 --- a/packages/plugin-cards-api/src/graphql/resolvers/mutations/deals.ts +++ b/packages/plugin-cards-api/src/graphql/resolvers/mutations/deals.ts @@ -15,7 +15,7 @@ import { import { IContext } from '../../../connectionResolver'; import { sendProductsMessage } from '../../../messageBroker'; import { graphqlPubsub } from '../../../configs'; - +import { IPaymentType } from '../../../models/definitions/payments'; interface IDealsEdit extends IDeal { _id: string; } @@ -24,6 +24,64 @@ const dealMutations = { /** * Creates a new deal */ + + async paymentTypes( + _root, + doc: IPaymentType, + { models, user, subdomain }: IContext + ) { + try { + const oldPaymentTypes = await models.PaymentTypes.find({ + status: 'active' + }).lean(); + + let bulkOps: Array<{ + updateOne: { + filter: { _id: string }; + update: any; + upsert?: boolean; + }; + }> = []; + + if (doc._id && doc._id === oldPaymentTypes[0]._id) { + bulkOps.push({ + updateOne: { + filter: { _id: doc._id }, + update: { + erxesAppToken: doc.erxesAppToken, + paymentIds: doc.paymentIds || [], + paymentTypes: doc.paymentTypes, + status: 'active', + createdBy: user._id, + createdAt: new Date() + }, + upsert: true + } + }); + } else if (doc._id === undefined) { + bulkOps.push({ + updateOne: { + filter: { _id: Math.random().toString() }, + update: { + erxesAppToken: doc.erxesAppToken, + paymentIds: doc.paymentIds || [], + paymentTypes: doc.paymentTypes, + status: 'active', + createdBy: user._id, + createdAt: new Date() + }, + upsert: true + } + }); + } + await models.PaymentTypes.bulkWrite(bulkOps); + return await models.PaymentTypes.find({ status: 'active' }).lean(); + } catch (error) { + console.error('Error in paymentTypes:', error); + throw error; + } + }, + async dealsAdd( _root, doc: IDeal & { proccessId: string; aboveItemId: string }, @@ -48,7 +106,6 @@ const dealMutations = { { user, models, subdomain }: IContext ) { const oldDeal = await models.Deals.getDeal(_id); - if (doc.assignedUserIds) { const { removedUserIds } = checkUserIds( oldDeal.assignedUserIds, @@ -158,7 +215,7 @@ const dealMutations = { proccessId, 'deal', user, - ['productsData', 'paymentsData'], + ['productsData', 'paymentsData', 'mobileAmounts'], models.Deals.createDeal ); }, diff --git a/packages/plugin-cards-api/src/graphql/resolvers/queries/deals.ts b/packages/plugin-cards-api/src/graphql/resolvers/queries/deals.ts index a5106c1243..d5559ef394 100644 --- a/packages/plugin-cards-api/src/graphql/resolvers/queries/deals.ts +++ b/packages/plugin-cards-api/src/graphql/resolvers/queries/deals.ts @@ -27,6 +27,9 @@ const dealQueries = { /** * Deals list */ + async paymentTypes(_root, _args, { models, subdomain }: IContext) { + return await models.PaymentTypes.find({ status: 'active' }).lean(); + }, async deals( _root, args: IDealListParams, diff --git a/packages/plugin-cards-api/src/graphql/schema/deal.ts b/packages/plugin-cards-api/src/graphql/schema/deal.ts index f4a706f321..4566bf94fb 100644 --- a/packages/plugin-cards-api/src/graphql/schema/deal.ts +++ b/packages/plugin-cards-api/src/graphql/schema/deal.ts @@ -35,6 +35,8 @@ export const types = ({ contacts, tags }) => ` products: JSON productsData: JSON paymentsData: JSON + mobileAmounts: JSON + paymentTypeData: JSON ${commonTypes} } @@ -42,7 +44,12 @@ export const types = ({ contacts, tags }) => ` amount: Float name: String } - +type PaymentType { + _id: String + paymentIds: [String] + paymentTypes: [JSON] + erxesAppToken: String +} type TotalForType { _id: String name: String @@ -53,14 +60,26 @@ export const types = ({ contacts, tags }) => ` productId : String quantity: Int } - + input PaymentObjectInput { + _id: String + paymentIds: [String] + paymentTypes: [JSON] + erxesAppToken: String + } `; const dealMutationParams = ` paymentsData: JSON, productsData: JSON, + mobileAmounts: JSON, + paymentTypeData: JSON, +`; +const paymentCommonFields = ` + _id: String + erxesAppToken: String + paymentIds: [String] + paymentTypes: [JSON] `; - const commonQueryParams = ` _ids: [String] date: ItemDate @@ -144,6 +163,9 @@ export const queries = ` ${commonQueryParams} ${conformityQueryFields} ): [TotalForType] + paymentTypes: [JSON] + paymentTotalCount:JSON + paymentDetail(_id: String!): JSON `; export const mutations = ` @@ -157,4 +179,6 @@ export const mutations = ` dealsCreateProductsData(proccessId: String, dealId: String, docs: JSON): JSON dealsEditProductData(proccessId: String, dealId: String, dataId: String, doc: JSON): JSON dealsDeleteProductData(proccessId: String, dealId: String, dataId: String): JSON + paymentTypes(${paymentCommonFields}) : PaymentType + `; diff --git a/packages/plugin-cards-api/src/models/Payments.ts b/packages/plugin-cards-api/src/models/Payments.ts new file mode 100644 index 0000000000..502fcad25f --- /dev/null +++ b/packages/plugin-cards-api/src/models/Payments.ts @@ -0,0 +1,67 @@ +import { Model } from 'mongoose'; +import { IModels } from '../connectionResolver'; +import { + IPaymentType, + IPaymentTypeDocument, + paymentTypeSchema +} from './definitions/payments'; + +export interface IPaymentTypeModel extends Model { + getPaymentType(_id: string): Promise; + createPaymentType(user, doc: IPaymentType): Promise; + updatePaymentType( + _id: string, + doc: IPaymentType + ): Promise; + removePaymentType(_id: string): void; +} + +export const loadPaymentTypeClass = (models: IModels, subdomain: string) => { + class PaymentType { + public static async createPaymentType(user, doc: IPaymentType) { + try { + return models.PaymentTypes.create({ + ...doc, + userId: user._id + }); + } catch (e) { + throw new Error( + `Can not create POS integration. Error message: ${e.message}` + ); + } + } + + public static async getPaymentType(_id: string) { + // tslint:disable-next-line:no-shadowed-variable + const PaymentType = await models.PaymentTypes.findOne({ _id }); + + if (!PaymentType) { + throw new Error('PaymentType not found'); + } + return PaymentType; + } + + public static async updatePaymentType(_id: string, doc: IPaymentType) { + await models.PaymentTypes.updateOne( + { _id }, + { $set: doc }, + { runValidators: true } + ); + + return models.PaymentTypes.findOne({ _id }); + } + + public static async removePaymentType(_id: string) { + const data = await models.PaymentTypes.getPaymentType(_id); + + if (!data) { + throw new Error(`not found with id ${_id}`); + } + return models.PaymentTypes.remove({ _id }); + } + } + + paymentTypeSchema.loadClass(PaymentType); + + return paymentTypeSchema; +}; diff --git a/packages/plugin-cards-api/src/models/definitions/deals.ts b/packages/plugin-cards-api/src/models/definitions/deals.ts index e84030602a..44f113fd89 100644 --- a/packages/plugin-cards-api/src/models/definitions/deals.ts +++ b/packages/plugin-cards-api/src/models/definitions/deals.ts @@ -29,12 +29,20 @@ interface IPaymentsData { [key: string]: { currency?: string; amount?: number; + type?: string; + title?: string; + icon?: string; + config?: string; }; } - +export interface IMobileAmounts { + _id?: string; + amount: number; +} export interface IDeal extends IItemCommonFields { productsData?: IProductData[]; paymentsData?: IPaymentsData[]; + mobileAmounts?: IMobileAmounts[]; } export interface IDealDocument extends IDeal, Document { @@ -72,5 +80,10 @@ export const dealSchema = new Schema({ ...commonItemFieldsSchema, productsData: field({ type: [productDataSchema], label: 'Products' }), - paymentsData: field({ type: Object, optional: true, label: 'Payments' }) + paymentsData: field({ type: Object, optional: true, label: 'Payments' }), + mobileAmounts: field({ + type: Object, + optional: true, + label: 'Mobile Amounts' + }) }); diff --git a/packages/plugin-cards-api/src/models/definitions/payments.ts b/packages/plugin-cards-api/src/models/definitions/payments.ts new file mode 100644 index 0000000000..d642c8b81a --- /dev/null +++ b/packages/plugin-cards-api/src/models/definitions/payments.ts @@ -0,0 +1,26 @@ +import { Document, Schema } from 'mongoose'; +import { field, schemaWrapper } from './utils'; + +export interface IPaymentType { + _id?: string; + status: string; + paymentIds?: string[]; + paymentTypes?: any[]; + erxesAppToken?: string; +} + +export interface IPaymentTypeDocument extends IPaymentType, Document { + _id: string; + status: string; + createdBy: string; + createdAt: Date; +} +export const paymentTypeSchema = new Schema({ + _id: field({ pkey: true }), + paymentIds: field({ type: [String], label: 'Online Payments' }), + paymentTypes: field({ type: [Object], label: 'Other Payments' }), + erxesAppToken: field({ type: String, label: 'Erxes app token' }), + status: field({ type: String, label: 'Status' }), + createdBy: field({ type: String, label: 'Created by' }), + createdAt: field({ type: Date, label: 'Created at' }) +}); diff --git a/packages/plugin-cards-api/src/payment.ts b/packages/plugin-cards-api/src/payment.ts new file mode 100644 index 0000000000..b58df41106 --- /dev/null +++ b/packages/plugin-cards-api/src/payment.ts @@ -0,0 +1,50 @@ +import { monitorEventLoopDelay } from 'perf_hooks'; +import { generateModels } from './connectionResolver'; +import * as moment from 'moment'; + +export default { + callback: async ({ subdomain, data }) => { + const models = await generateModels(subdomain); + const paymentParams = data; + + if ( + paymentParams.contentType !== 'cards:deal' || + paymentParams.status !== 'paid' + ) { + return; + } + console.log(paymentParams, 'paymentParams'); + const orderSelector = { + _id: paymentParams.contentTypeId + }; + + for (const key in paymentParams) { + if (paymentParams.hasOwnProperty(key)) { + const { contentTypeId, amount, _id } = paymentParams; + if (orderSelector._id !== contentTypeId) { + continue; + } + + console.log(paymentParams, 'paymentParams'); + await models.Deals.updateOne(orderSelector, { + $addToSet: { + mobileAmounts: { + _id, + amount + } + } + }); + } + } + const order = await models.Deals.findOne(orderSelector).lean(); + const sumMobileAmount = (order.mobileAmounts || []).reduce( + (sum, i) => sum + i.amount, + 0 + ); + await models.Deals.updateOne(orderSelector, { + $set: { mobileAmount: sumMobileAmount } + }); + console.log(sumMobileAmount, 'sumMobileAmount'); + return sumMobileAmount; + } +}; diff --git a/packages/plugin-cards-ui/src/settings/boards/components/PaymentForm.tsx b/packages/plugin-cards-ui/src/settings/boards/components/PaymentForm.tsx new file mode 100644 index 0000000000..d9900c64ca --- /dev/null +++ b/packages/plugin-cards-ui/src/settings/boards/components/PaymentForm.tsx @@ -0,0 +1,285 @@ +import { + Button, + ControlLabel, + FormControl, + FormGroup, + Icon, + Tip, + __ +} from '@erxes/ui/src'; +import { LeftItem } from '@erxes/ui/src/components/step/styles'; +import { FormColumn, FormWrapper } from '@erxes/ui/src/styles/main'; +import { isEnabled, loadDynamicComponent } from '@erxes/ui/src/utils/core'; +import React from 'react'; +import Select from 'react-select-plus'; +import styled from 'styled-components'; +import { PAYMENT_TYPE_ICONS } from '../constants'; +import { Block, Description, FlexColumn, FlexItem } from '../styles'; +import { IPaymentType } from '../types'; + +export const SelectValue = styled.div` + display: flex; + justify-content: left; + align-items: baseline; + margin-left: -7px; + padding-left: 25px; +`; + +const content = (option): React.ReactNode => ( + <> + +   {option.label} + +); + +type Props = { + onChange: (name: 'payment', value: any) => void; + payment: any; + paymentSave: (value: IPaymentType) => void; +}; + +type State = {}; + +class PaymentForm extends React.Component { + constructor(props: Props) { + super(props); + + this.state = {}; + } + handleSubmit = () => { + const { payment, paymentSave } = this.props; + paymentSave(payment); + }; + + onChangeFunction = (name: any, value: any) => { + this.props.onChange(name, value); + }; + + onChangePayments = ids => { + const { payment } = this.props; + this.onChangeFunction('payment', { ...payment, paymentIds: ids }); + }; + + onChangeInput = e => { + const { payment } = this.props; + this.onChangeFunction('payment', { + ...payment, + [e.target.id]: (e.currentTarget as HTMLInputElement).value + }); + }; + + onClickAddPayments = () => { + const { payment, onChange } = this.props; + const paymentTypes = [...(payment.paymentTypes || [])]; + paymentTypes.push({ + _id: Math.random().toString(), + type: '', + title: '', + icon: '' + }); + + onChange('payment', { ...payment, paymentTypes }); + }; + + selectItemRenderer = (option): React.ReactNode => { + return {content(option)}; + }; + + renderPaymentType(paymentType: any) { + const { payment, onChange } = this.props; + + const editPayment = (name, value) => { + let paymentTypes = [...(payment.paymentTypes || [])]; + paymentTypes = (paymentTypes || []).map(p => + p._id === paymentType._id ? { ...p, [name]: value } : p + ); + onChange('payment', { ...payment, paymentTypes }); + }; + + const onChangeInput = e => { + const name = e.target.name; + const value = e.target.value; + editPayment(name, value); + }; + + const onChangeSelect = option => { + editPayment('icon', option.value); + }; + + const removePayment = () => { + const paymentTypes = + (payment.paymentTypes || []).filter(m => m._id !== paymentType._id) || + []; + onChange('payment', { ...payment, paymentTypes }); + }; + + const getTipText = type => { + if (type === 'golomtCard') return 'continue'; + if (type === 'TDBCard') return 'must config: "{port: 8078}"'; + if (type === 'khaanCard') + return 'check localhost:27028 and contact databank'; + return ''; + }; + + return ( +
+ + + + + + + + + + + + + + + {type.type === 'mobile' && this.props.mobileAmounts === null ? ( + + ) : ( + type.type !== 'mobile' && ( +