Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nicoll/splits automation #1771

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 4 additions & 22 deletions apps/epic-web/schemas/documents/product.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,10 @@ export default defineType({
},
}),
defineField({
name: 'revenueSplits',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

went ahead and deleted this section

title: 'Revenue Splits',
type: 'array',
of: [{type: 'productRevenueSplit'}],
validation: (Rule) =>
Rule.custom((revenueSplits) => {
if (!revenueSplits || !Array.isArray(revenueSplits)) return true

const total = revenueSplits.reduce(
(sum, split) => sum + (split.percentage || 0),
0,
)

if (total > 1) {
return 'The total of all revenue splits cannot exceed 100% (1.0)'
}

return true
}),
options: {
layout: 'tags',
},
name: 'contributors',
type: 'contributors',
title: 'Contributors',
validation: (Rule) => Rule.required(),
}),
defineField({
name: 'state',
Expand Down
10 changes: 9 additions & 1 deletion apps/epic-web/src/inngest/functions/sanity/product/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export const loadSanityProduct = async (
},
image{
url
}
},
"instructor": contributors[0].contributor->{
userId,
name,
Comment on lines +36 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of this and we should probably not hard code a zero-index lookup here and consider multiple instructors immediately.

},
}`,
) => {
const sanityProductData = await sanityWriteClient.fetch(query, {id})
Expand Down Expand Up @@ -67,6 +71,10 @@ export const BaseSanityProductSchema = z.object({
})
.nullable()
.optional(),
instructor: z
.object({userId: z.string(), name: z.string()})
.optional()
.nullable(),
})

export type BaseSanityProduct = z.infer<typeof BaseSanityProductSchema>
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const sanityProductCreated = inngest.createFunction(
upgradableTo,
state,
type,
instructor,
} = sanityProduct

const productStatus = state === 'active' ? 1 : 0
Expand Down Expand Up @@ -82,6 +83,114 @@ export const sanityProductCreated = inngest.createFunction(
})
})

type SplitInfo = {
type: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type here is 'owner' | 'skill' | string

could use a discriminated union so that the userId would only be there if it was type owner`

percent: number
userId: string | undefined | null
}

type Splits = {
skill: SplitInfo
owner: SplitInfo
contributor?: SplitInfo
}

const defineSplits = await step.run(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define splits based on contributor and moduleType

'define splits',
async (): Promise<Splits> => {
const OWNER_ID = '4ef27e5f-00b4-4aa3-b3c4-4a58ae76f50b'
const isOwnerInstructor = instructor?.userId === OWNER_ID
const isSelfPaced = type === 'self-paced'

let splits: Splits = {
skill: {
type: 'skill',
percent: 0,
userId: null,
},
owner: {
type: 'owner',
percent: 0,
userId: OWNER_ID,
},
}

if (isOwnerInstructor) {
if (isSelfPaced) {
splits.skill.percent = 0.4
splits.owner.percent = 0.6
} else {
splits.skill.percent = 0.15
splits.owner.percent = 0.85
}
} else {
if (isSelfPaced) {
splits.skill.percent = 0.5
splits.owner.percent = 0.2
splits.contributor = {
type: 'contributor',
percent: 0.3,
userId: instructor?.userId,
}
} else {
splits.skill.percent = 0.15
splits.owner.percent = 0.15
splits.contributor = {
type: 'contributor',
percent: 0.7,
userId: instructor?.userId,
}
}
}
Comment on lines +118 to +144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of harsh, but I am concerned with it's hard-coding very specific business values and mutating an object through another object. To the extent that I think we might need to rethink the entire data model and implementation of the PR.

We can keep trying like this or write a plan, diagram the flows, etc before implementing

I'd rather have it just manually added to the database. 😬


return splits
},
)

const createSplits = await step.run(
'create splits in database',
async () => {
const skillSplit = await prisma.productRevenueSplit.create({
data: {
id: v4(),
type: defineSplits.skill.type,
productId: product.id,
percent: defineSplits.skill.percent,
userId: defineSplits.skill.userId,
},
})

const ownerSplit = await prisma.productRevenueSplit.create({
data: {
id: v4(),
type: defineSplits.owner.type,
productId: product.id,
percent: defineSplits.owner.percent,
userId: defineSplits.owner.userId,
},
})

let contributorSplit = null
if (defineSplits.contributor) {
contributorSplit = await prisma.productRevenueSplit.create({
data: {
id: v4(),
type: defineSplits.contributor.type,
productId: product.id,
percent: defineSplits.contributor.percent,
userId: defineSplits.contributor.userId,
},
})
}

return {
skillSplit,
ownerSplit,
contributorSplit,
}
},
)

const merchantAccount = await step.run('get merchant account', async () => {
return await prisma.merchantAccount.findFirst({
where: {
Expand Down Expand Up @@ -167,6 +276,7 @@ export const sanityProductCreated = inngest.createFunction(
price,
stripeProduct,
stripePrice,
createSplits,
}
} else {
throw new Error('No merchant account found')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,13 @@ export const sanityProductDeleted = inngest.createFunction(
}),
)
})

await step.run('delete current splits', async () => {
return await prisma.productRevenueSplit.deleteMany({
where: {
productId: product.id,
},
})
})
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const sanityProductUpdated = inngest.createFunction(
upgradableTo,
state,
type,
instructor,
} = sanityProduct

if (!productId) {
Expand Down Expand Up @@ -257,6 +258,169 @@ export const sanityProductUpdated = inngest.createFunction(
},
)

const currentSplits = await step.run('get current splits', async () => {
return await prisma.productRevenueSplit.findMany({
where: {
productId: product.id,
},
})
})

const splitsNeedUpdate = (
currentSplits: any[],
instructor: any,
type: string,
): boolean => {
const OWNER_ID = '4ef27e5f-00b4-4aa3-b3c4-4a58ae76f50b'

const currentContributorSplit = currentSplits.find(
(split) => split.type === 'contributor',
)

const isOwnerInstructor = instructor?.userId === OWNER_ID
const wasOwnerInstructor = !currentContributorSplit

if (isOwnerInstructor !== wasOwnerInstructor) {
return true
}

if (
currentContributorSplit &&
instructor?.userId !== currentContributorSplit.userId
) {
return true
}

const currentProductType =
currentSplits.length > 0 ? (currentSplits[0] as any).productType : null

if (currentProductType !== type) {
return true
}

return false
}

const needsSplitUpdate = await step.run(
'check if splits need update',
async () => {
return splitsNeedUpdate(currentSplits, instructor, type)
},
)

type SplitInfo = {
type: string
percent: number
userId: string | undefined | null
}

type Splits = {
skill: SplitInfo
owner: SplitInfo
contributor?: SplitInfo
}
Comment on lines +311 to +321
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super important to have types exist in 1 spot only


if (needsSplitUpdate) {
const defineSplits = await step.run(
'define splits',
async (): Promise<Splits> => {
const OWNER_ID = '4ef27e5f-00b4-4aa3-b3c4-4a58ae76f50b'
const isOwnerInstructor = instructor?.userId === OWNER_ID
const isSelfPaced = type === 'self-paced'

let splits: Splits = {
skill: {
type: 'skill',
percent: 0,
userId: null,
},
owner: {
type: 'owner',
percent: 0,
userId: OWNER_ID,
},
}

if (isOwnerInstructor) {
if (isSelfPaced) {
splits.skill.percent = 0.4
splits.owner.percent = 0.6
} else {
splits.skill.percent = 0.15
splits.owner.percent = 0.85
}
} else {
if (isSelfPaced) {
splits.skill.percent = 0.5
splits.owner.percent = 0.2
splits.contributor = {
type: 'contributor',
percent: 0.3,
userId: instructor?.userId,
}
} else {
splits.skill.percent = 0.15
splits.owner.percent = 0.15
splits.contributor = {
type: 'contributor',
percent: 0.7,
userId: instructor?.userId,
}
}
}

return splits
},
)
Comment on lines +344 to +374
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, looks like it's all repeated


await step.run('update splits in database', async () => {
await prisma.productRevenueSplit.deleteMany({
where: {
productId: product.id,
},
})

const skillSplit = await prisma.productRevenueSplit.create({
data: {
id: v4(),
type: defineSplits.skill.type,
productId: product.id,
percent: defineSplits.skill.percent,
userId: defineSplits.skill.userId,
},
})

const ownerSplit = await prisma.productRevenueSplit.create({
data: {
id: v4(),
type: defineSplits.owner.type,
productId: product.id,
percent: defineSplits.owner.percent,
userId: defineSplits.owner.userId,
},
})

let contributorSplit = null
if (defineSplits.contributor) {
contributorSplit = await prisma.productRevenueSplit.create({
data: {
id: v4(),
type: defineSplits.contributor.type,
productId: product.id,
percent: defineSplits.contributor.percent,
userId: defineSplits.contributor.userId,
},
})
}

return {
skillSplit,
ownerSplit,
contributorSplit,
}
})
}

return {updatedProduct, updatedStripeProduct}
},
)
Loading