Skip to content

Commit

Permalink
Merge pull request #224 from cabcookie:customer-financials
Browse files Browse the repository at this point in the history
Customer-financials
  • Loading branch information
cabcookie authored Oct 31, 2024
2 parents 2155c7c + 3424ee1 commit 59ac70b
Show file tree
Hide file tree
Showing 84 changed files with 10,573 additions and 5,747 deletions.
24 changes: 21 additions & 3 deletions amplify/data/account-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,31 @@ const accountSchema = {
territory: a.belongsTo("Territory", "territoryId"),
})
.authorization((allow) => [allow.owner()]),
PayerAccount: a
AccountPayerAccount: a
.model({
owner: a
.string()
.authorization((allow) => [allow.owner().to(["read", "delete"])]),
awsAccountNumber: a.id().required(),
accountId: a.id().required(),
account: a.belongsTo("Account", "accountId"),
awsAccountNumberId: a.id().required(),
awsAccountNumber: a.belongsTo("PayerAccount", "awsAccountNumberId"),
})
.secondaryIndexes((index) => [index("awsAccountNumberId")])
.authorization((allow) => [allow.owner()]),
PayerAccount: a
.model({
owner: a
.string()
.authorization((allow) => [allow.owner().to(["read", "delete"])]),
awsAccountNumber: a.id().required(),
accounts: a.hasMany("AccountPayerAccount", "awsAccountNumberId"),
isViaReseller: a.boolean(),
resellerId: a.id(),
reseller: a.belongsTo("Account", "resellerId"),
mainContactId: a.id(),
mainContact: a.belongsTo("Person", "mainContactId"),
financials: a.hasMany("PayerAccountMrr", "awsAccountNumber"),
})
.identifier(["awsAccountNumber"])
.authorization((allow) => [allow.owner()]),
Expand All @@ -62,7 +79,8 @@ const accountSchema = {
projects: a.hasMany("AccountProjects", "accountId"),
accountSubsidiariesId: a.id(),
controller: a.belongsTo("Account", "accountSubsidiariesId"),
payerAccounts: a.hasMany("PayerAccount", "accountId"),
payerAccounts: a.hasMany("AccountPayerAccount", "accountId"),
resellingAccounts: a.hasMany("PayerAccount", "resellerId"),
people: a.hasMany("PersonAccount", "accountId"),
partnerProjects: a.hasMany("Projects", "partnerId"),
})
Expand Down
38 changes: 34 additions & 4 deletions amplify/data/analytics-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,48 @@ import { a } from "@aws-amplify/backend";

const analyticsSchema = {
AnalyticsImportStatus: a.enum(["WIP", "DONE"]),
MrrImportData: a
MrrDataUpload: a
.model({
owner: a
.string()
.authorization((allow) => [allow.owner().to(["read", "delete"])]),
s3Key: a.string().required(),
status: a.ref("AnalyticsImportStatus").required(),
latestMonths: a.hasMany("Month", "latestUploadId"),
payerMrrs: a.hasMany("PayerAccountMrr", "uploadId"),
createdAt: a.datetime().required(),
})
.secondaryIndexes((index) => [
index("status").sortKeys(["createdAt"]).queryField("listByImportStatus"),
])
.secondaryIndexes((index) => [index("status").sortKeys(["createdAt"])])
.authorization((allow) => [allow.owner()]),
Month: a
.model({
owner: a
.string()
.authorization((allow) => [allow.owner().to(["read", "delete"])]),
month: a.string().required(),
latestUploadId: a.id().required(),
latestUpload: a.belongsTo("MrrDataUpload", "latestUploadId"),
payerMrrs: a.hasMany("PayerAccountMrr", "monthId"),
})
.secondaryIndexes((index) => [index("latestUploadId").sortKeys(["month"])])
.authorization((allow) => [allow.owner()]),
PayerAccountMrr: a
.model({
owner: a
.string()
.authorization((allow) => [allow.owner().to(["read", "delete"])]),
uploadId: a.id().required(),
upload: a.belongsTo("MrrDataUpload", "uploadId"),
monthId: a.id().required(),
month: a.belongsTo("Month", "monthId"),
companyName: a.string().required(),
awsAccountNumber: a.id().required(),
payerAccount: a.belongsTo("PayerAccount", "awsAccountNumber"),
isEstimated: a.boolean().required(),
isReseller: a.boolean().required(),
mrr: a.integer(),
})
.secondaryIndexes((index) => [index("uploadId")])
.authorization((allow) => [allow.owner()]),
};

Expand Down
1 change: 1 addition & 0 deletions amplify/data/person-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const personSchmema = {
dateOfDeath: a.date(),
meetings: a.hasMany("MeetingParticipant", "personId"),
accounts: a.hasMany("PersonAccount", "personId"),
payerAccounts: a.hasMany("PayerAccount", "mainContactId"),
details: a.hasMany("PersonDetail", "personId"),
learnings: a.hasMany("PersonLearning", "personId"),
profile: a.hasOne("User", "personId"),
Expand Down
65 changes: 42 additions & 23 deletions api/ContextAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@ import {
calcOrder,
getQuotaFromTerritoryOrSubsidaries,
} from "@/helpers/accounts";
import {
createPayerAccountLink,
deletePayerAccountLink,
getOrCreatePayerAccount,
} from "@/helpers/payers/api-actions";
import { transformNotesVersion } from "@/helpers/ui-notes-writer";
import { JSONContent } from "@tiptap/core";
import { SelectionSet, generateClient } from "aws-amplify/data";
import { filter, flow, join, map, sortBy, sum } from "lodash/fp";
import {
filter,
find,
flow,
identity,
join,
map,
sortBy,
sum,
} from "lodash/fp";
import { FC, ReactNode, createContext, useContext } from "react";
import useSWR from "swr";
import { handleApiErrors } from "./globals";
Expand All @@ -29,7 +43,7 @@ interface AccountsContextType {
createAccount: (
accountName: string
) => Promise<Schema["Account"]["type"] | undefined>;
getAccountById: (accountId: string) => Account | undefined;
getAccountById: (accountId: string | undefined) => Account | undefined;
updateAccount: (props: UpdateAccountProps) => Promise<string | undefined>;
assignController: (
accountId: string,
Expand All @@ -47,7 +61,10 @@ interface AccountsContextType {
accountId: string,
payer: string
) => Promise<string | undefined>;
deletePayerAccount: (payer: string) => Promise<string | undefined>;
deletePayerAccount: (
accountId: string,
payer: string
) => Promise<string | undefined>;
getPipelineByControllerId: (controllerId: string) => number;
getAccountNamesByIds: (accountIds: string[]) => string;
}
Expand Down Expand Up @@ -94,7 +111,7 @@ const selectionSet = [
"projects.projects.crmProjects.crmProject.totalContractVolume",
"projects.projects.crmProjects.crmProject.isMarketplace",
"projects.projects.crmProjects.crmProject.stage",
"payerAccounts.awsAccountNumber",
"payerAccounts.awsAccountNumberId",
] as const;

type AccountData = SelectionSet<Schema["Account"]["type"], typeof selectionSet>;
Expand Down Expand Up @@ -140,7 +157,7 @@ const mapAccount: (
projects,
createdAt: new Date(createdAt),
territoryIds: territories.map((t) => t.territory.id),
payerAccounts: payerAccounts.map((p) => p.awsAccountNumber),
payerAccounts: payerAccounts.map((p) => p.awsAccountNumberId),
});

const addOrderNumberToAccounts = (
Expand Down Expand Up @@ -177,7 +194,7 @@ const addOrderNumberToAccounts = (
[]
);

export const fetchAccounts = async () => {
const fetchAccounts = async () => {
const { data, errors } = await client.models.Account.list({
limit: 500,
selectionSet,
Expand Down Expand Up @@ -241,8 +258,11 @@ export const AccountsContextProvider: FC<AccountsContextProviderProps> = ({
return data || undefined;
};

const getAccountById = (accountId: string) =>
accounts?.find((account) => account.id === accountId);
const getAccountById = (accountId: string | undefined) =>
flow(
identity<Account[] | undefined>,
find(({ id }) => id === accountId)
)(accounts);

const updateAccount = async ({
id,
Expand Down Expand Up @@ -394,41 +414,40 @@ export const AccountsContextProvider: FC<AccountsContextProviderProps> = ({
: { ...a, payerAccounts: [...a.payerAccounts, payer] }
);
if (updated) mutate(updated, false);
const { data, errors } = await client.models.PayerAccount.create({
const payerAccountId = await getOrCreatePayerAccount(payer);
if (!payerAccountId) return;
const resultAccountId = await createPayerAccountLink(
accountId,
awsAccountNumber: payer,
});
if (errors) handleApiErrors(errors, "Creating payer failed");
payerAccountId
);
if (!resultAccountId) return;
if (updated) mutate(updated);
if (!data) return;
toast({
title: "Payer created",
description: `Successfully created payer ${payer} for account ${
accounts?.find((a) => a.id === accountId)?.name
}.`,
});
return data.awsAccountNumber;
return resultAccountId;
};

const deletePayerAccount = async (payer: string) => {
const account = accounts?.find((a) => a.payerAccounts.includes(payer));
const deletePayerAccount = async (accountId: string, payer: string) => {
const account = accounts?.find((a) => a.id === accountId);
if (!account) return;
const updated: Account[] | undefined = accounts?.map((a) =>
!a.payerAccounts.includes(payer)
? a
: { ...a, payerAccounts: a.payerAccounts.filter((p) => p !== payer) }
);
if (updated) mutate(updated, false);
const { data, errors } = await client.models.PayerAccount.delete({
awsAccountNumber: payer,
});
if (errors) handleApiErrors(errors, "Deleting payer failed");
const data = await deletePayerAccountLink(accountId, payer);
if (updated) mutate(updated);
if (!data) return;
toast({
title: "Payer deleted",
description: `Successfully deleted payer ${payer} for account ${account?.name}.`,
title: "Removed payer",
description: `Successfully removed payer ${payer} from account ${account?.name}.`,
});
return data.awsAccountNumber;
return data.awsAccountNumberId;
};

const getPipelineByControllerId = (controllerId: string): number =>
Expand Down
2 changes: 1 addition & 1 deletion api/useActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type TempIdMapping = {
id: string;
};

export type ProjectLinkData = {
type ProjectLinkData = {
id: string;
projectsId: string;
};
Expand Down
110 changes: 110 additions & 0 deletions api/useMrr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { type Schema } from "@/amplify/data/resource";
import { MrrImportData } from "@/api/useMrrImport";
import { MrrFilters } from "@/components/analytics/useMrrFilter";
import { getMinMonth, mapPayerMrrs } from "@/helpers/analytics/analytics";
import {
DoneMonthMrrData,
getMonthMrr,
getMonths,
} from "@/helpers/analytics/api-actions";
import { SelectionSet, generateClient } from "aws-amplify/data";
import { flatMap, flow, get, identity } from "lodash/fp";
import useSWR, { KeyedMutator } from "swr";
import { handleApiErrors } from "./globals";
const client = generateClient<Schema>();

const wipSelectionSet = [
"latestMonths.month",
"latestMonths.payerMrrs.id",
"latestMonths.payerMrrs.companyName",
"latestMonths.payerMrrs.awsAccountNumber",
"latestMonths.payerMrrs.payerAccount.accounts.accountId",
"latestMonths.payerMrrs.payerAccount.resellerId",
"latestMonths.payerMrrs.isEstimated",
"latestMonths.payerMrrs.isReseller",
"latestMonths.payerMrrs.mrr",
] as const;

export type MrrWipData = SelectionSet<
Schema["MrrDataUpload"]["type"],
typeof wipSelectionSet
>;

export type Mrr = {
id: string;
month: string;
companyName: string;
awsAccountNumber: string;
payerAccountAccountIds?: string[];
isEstimated: boolean;
isReseller: boolean;
resellerId?: string;
mrr: number;
lastYearMrr?: number;
lastPeriodMrr?: number;
};

const mapMrr = ({
month,
payerMrrs,
}: MrrWipData["latestMonths"][number]): Mrr[] =>
payerMrrs.map(mapPayerMrrs(month));

const fetchMrrWip = async () => {
const { data, errors } =
await client.models.MrrDataUpload.listMrrDataUploadByStatusAndCreatedAt(
{ status: "WIP" },
{
sortDirection: "DESC",
limit: 1,
selectionSet: wipSelectionSet,
}
);
if (errors) {
handleApiErrors(errors, "Loading Mrr (WIP) failed");
throw errors;
}
try {
return flow(
identity<MrrWipData[] | undefined>,
get(0),
get("latestMonths"),
flatMap(mapMrr)
)(data);
} catch (error) {
console.error("fetchMrr", error);
throw error;
}
};

const fetchMrrDone = (noOfMonths?: MrrFilters) => async () => {
if (!noOfMonths) return;
const noOfMonthsInt = parseInt(noOfMonths);
const startMonth = getMinMonth(noOfMonthsInt);
const months = await getMonths(startMonth);
if (!months) return;
const data = await Promise.all(months.map(getMonthMrr));
return flow(identity<DoneMonthMrrData[]>, flatMap(mapMrr))(data);
};

const fetchMrr =
(status: MrrImportData["status"], noOfMonths?: MrrFilters) => async () => {
const fetcher = status === "WIP" ? fetchMrrWip : fetchMrrDone(noOfMonths);
const data = await fetcher();
return data;
};

const useMrr = (status: MrrImportData["status"], noOfMonths?: MrrFilters) => {
const {
data: mrr,
isLoading,
error,
mutate,
} = useSWR(`/api/mrr/${status}/${noOfMonths}`, fetchMrr(status, noOfMonths));

return { mrr, isLoading, error, mutate };
};

export type MrrMutator = KeyedMutator<Mrr[] | undefined>;

export default useMrr;
Loading

0 comments on commit 59ac70b

Please sign in to comment.