Skip to content

Commit

Permalink
AccountingCategory: add support for orders (#9695)
Browse files Browse the repository at this point in the history
* feat(AccountingCategory): add support for orders

* Add created at to accounting category (#9702)

* Add created at to accounting category schema

* Handle enum array type with sequelize

* Use abstrat type to workaround sequelize pq enum naming

* Make unique expense types in setter

* prettier

---------

Co-authored-by: Henrique <[email protected]>
  • Loading branch information
Betree and hdiniz authored Jan 22, 2024
1 parent cca1ad7 commit d1863d9
Show file tree
Hide file tree
Showing 23 changed files with 534 additions and 92 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
// Reference accounting category from the `Orders` table
await queryInterface.addColumn('OrderHistories', 'AccountingCategoryId', {
type: Sequelize.INTEGER,
allowNull: true,
});

await queryInterface.addColumn('Orders', 'AccountingCategoryId', {
type: Sequelize.INTEGER,
references: { key: 'id', model: 'AccountingCategories' },
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
allowNull: true,
});

// Add a kind column to accounting categories
await queryInterface.addColumn('AccountingCategories', 'kind', {
type: Sequelize.ENUM('ADDED_FUNDS', 'CONTRIBUTION', 'EXPENSE'),
allowNull: true,
});

// Set default kind to expense, since we only had that until now
await queryInterface.sequelize.query(`
UPDATE "AccountingCategories"
SET "kind" = 'EXPENSE'
WHERE "kind" IS NULL
`);
},

async down(queryInterface) {
await queryInterface.removeColumn('OrderHistories', 'AccountingCategoryId');
await queryInterface.removeColumn('Orders', 'AccountingCategoryId');
await queryInterface.removeColumn('AccountingCategories', 'kind');
},
};
4 changes: 3 additions & 1 deletion server/graphql/common/expenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,9 @@ const checkCanUseAccountingCategory = (
if (!accountingCategory) {
return;
} else if (accountingCategory.CollectiveId !== host?.id) {
throw new ValidationFailed('This accounting category is not allowed for this expense');
throw new ValidationFailed('This accounting category is not allowed for this host');
} else if (accountingCategory.kind && accountingCategory.kind !== 'EXPENSE') {
throw new ValidationFailed('This accounting category is not allowed for expenses');
}
};

Expand Down
27 changes: 25 additions & 2 deletions server/graphql/common/orders.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { isNil } from 'lodash';
import { isNil, lowerCase } from 'lodash';
import { InferCreationAttributes } from 'sequelize';

import status from '../../constants/order-status';
import { purgeCacheForCollective } from '../../lib/cache';
import * as libPayments from '../../lib/payments';
import models, { Collective, Tier, User } from '../../models';
import { pluralize } from '../../lib/utils';
import models, { AccountingCategory, Collective, Tier, User } from '../../models';
import { OrderModelInterface } from '../../models/Order';
import { ValidationFailed } from '../errors';
import { getOrderTaxInfoFromTaxInput } from '../v1/mutations/orders';
import { TaxInput } from '../v2/input/TaxInput';

Expand All @@ -21,6 +23,24 @@ type AddFundsInput = {
tier: Tier;
invoiceTemplate: string;
tax: TaxInput;
accountingCategory?: AccountingCategory;
};

/*
* Throws if the accounting category is not allowed for this order/host
*/
export const checkCanUseAccountingCategoryForOrder = (
accountingCategory: AccountingCategory | undefined | null,
host: Collective | undefined,
kind: 'ADDED_FUNDS' | 'CONTRIBUTION',
): void => {
if (!accountingCategory) {
return;
} else if (accountingCategory.CollectiveId !== host?.id) {
throw new ValidationFailed('This accounting category is not allowed for this host');
} else if (accountingCategory.kind && accountingCategory.kind !== kind) {
throw new ValidationFailed(`This accounting category is not allowed for ${pluralize(lowerCase(kind), 2)}`);
}
};

export async function addFunds(order: AddFundsInput, remoteUser: User) {
Expand Down Expand Up @@ -51,6 +71,8 @@ export async function addFunds(order: AddFundsInput, remoteUser: User) {

if (order.tier && order.tier.CollectiveId !== order.collective.id) {
throw new Error(`Tier #${order.tier.id} is not part of collective #${order.collective.id}`);
} else if (order.accountingCategory) {
checkCanUseAccountingCategoryForOrder(order.accountingCategory, host, 'ADDED_FUNDS');
}

const orderData: Partial<InferCreationAttributes<OrderModelInterface>> = {
Expand All @@ -62,6 +84,7 @@ export async function addFunds(order: AddFundsInput, remoteUser: User) {
description: order.description,
status: status.NEW,
TierId: order.tier?.id || null,
AccountingCategoryId: order.accountingCategory?.id || null,
data: {
hostFeePercent: order.hostFeePercent,
},
Expand Down
145 changes: 103 additions & 42 deletions server/graphql/schemaV2.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,11 @@ type Order {
"""
permissions: OrderPermissions!

"""
The accounting category attached to this order
"""
accountingCategory: AccountingCategory

"""
The list of activities (ie. approved, edited, etc) for this Order ordered by date ascending
"""
Expand Down Expand Up @@ -1578,6 +1583,12 @@ type Expense {
"""
requestedByAccount: Account
quote: ExpenseQuote
validateTransferRequirements(
"""
Details of the transfer
"""
details: JSON
): [TransferWiseRequiredField]
recurringExpense: RecurringExpense

"""
Expand Down Expand Up @@ -1651,6 +1662,11 @@ type AccountingCategory {
If meant for expenses, the types of expenses this category applies to
"""
expensesTypes: [ExpenseType]

"""
The kind of transactions this category applies to
"""
kind: AccountingCategoryKind
}

"""
Expand Down Expand Up @@ -2372,7 +2388,12 @@ type Host implements Account & AccountWithContributions {
"""
List of accounting categories for this host
"""
accountingCategories: AccountingCategoryCollection!
accountingCategories(
"""
Filter accounting categories by kind
"""
kind: [AccountingCategoryKind!]
): AccountingCategoryCollection!
hostFeePercent: Float
totalHostedCollectives: Int @deprecated(reason: "2023-03-20: Renamed to totalHostedAccounts")
totalHostedAccounts: Int
Expand Down Expand Up @@ -5286,6 +5307,12 @@ type AccountingCategoryCollection implements Collection {
nodes: [AccountingCategory!]!
}

enum AccountingCategoryKind {
ADDED_FUNDS
CONTRIBUTION
EXPENSE
}

"""
The name of the current plan and its characteristics.
"""
Expand Down Expand Up @@ -6044,6 +6071,37 @@ type ExpenseQuote {
estimatedDeliveryAt: DateTime
}

type TransferWiseRequiredField {
type: String
title: String
fields: [TransferWiseField]
}

type TransferWiseField {
name: String
group: [TransferWiseFieldGroup]
}

type TransferWiseFieldGroup {
key: String
name: String
type: String
required: Boolean
refreshRequirementsOnChange: Boolean
displayFormat: String
example: String
minLength: Int
maxLength: Int
validationRegexp: String
validationAsync: String
valuesAllowed: [TransferWiseFieldVatvkluesAllowed]
}

type TransferWiseFieldVatvkluesAllowed {
key: String
name: String
}

"""
A recurring expense object
"""
Expand Down Expand Up @@ -10559,37 +10617,6 @@ type TransferWise {
amountBatched: Amount
}

type TransferWiseRequiredField {
type: String
title: String
fields: [TransferWiseField]
}

type TransferWiseField {
name: String
group: [TransferWiseFieldGroup]
}

type TransferWiseFieldGroup {
key: String
name: String
type: String
required: Boolean
refreshRequirementsOnChange: Boolean
displayFormat: String
example: String
minLength: Int
maxLength: Int
validationRegexp: String
validationAsync: String
valuesAllowed: [TransferWiseFieldVatvkluesAllowed]
}

type TransferWiseFieldVatvkluesAllowed {
key: String
name: String
}

"""
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
Expand Down Expand Up @@ -15359,6 +15386,11 @@ type Mutation {
The tax to apply to the order
"""
tax: TaxInput

"""
The accounting category of this order
"""
accountingCategory: AccountingCategoryReferenceInput
): Order!

"""
Expand Down Expand Up @@ -16957,6 +16989,16 @@ enum TaxType {
GST
}

"""
Reference to an accounting category
"""
input AccountingCategoryReferenceInput {
"""
The ID of the accounting category
"""
id: NonEmptyString!
}

input CollectiveCreateInput {
name: String!
slug: String!
Expand Down Expand Up @@ -17492,16 +17534,6 @@ input ExpenseTaxInput {
idNumber: String
}

"""
Reference to an accounting category
"""
input AccountingCategoryReferenceInput {
"""
The ID of the accounting category
"""
id: NonEmptyString!
}

input RecurringExpenseInput {
"""
The interval in which this recurring expense is created
Expand Down Expand Up @@ -17761,6 +17793,11 @@ input ProcessExpensePaymentParams {
Who is responsible for paying any due fees.
"""
feesPayer: FeesPayer = COLLECTIVE

"""
Transfer details for fulfilling the expense
"""
transfer: ProcessExpenseTransferParams
}

enum MarkAsUnPaidExpenseStatus {
Expand All @@ -17769,6 +17806,20 @@ enum MarkAsUnPaidExpenseStatus {
ERROR
}

input ProcessExpenseTransferParams {
"""
Wise transfer details
"""
details: WiseTransferDetails
}

input WiseTransferDetails {
reference: String
transferPurpose: String
sourceOfFunds: String
transferNature: String
}

input ExpenseInviteDraftInput {
"""
Main title of the expense
Expand Down Expand Up @@ -18367,6 +18418,11 @@ input PendingOrderCreateInput {
Custom Host fee percent for this order
"""
hostFeePercent: Float

"""
The accounting category of this order
"""
accountingCategory: AccountingCategoryReferenceInput
}

"""
Expand Down Expand Up @@ -18442,6 +18498,11 @@ input PendingOrderEditInput {
Custom Host fee percent for this order
"""
hostFeePercent: Float

"""
The accounting category of this order
"""
accountingCategory: AccountingCategoryReferenceInput
}

type CreditCardWithStripeError {
Expand Down
10 changes: 10 additions & 0 deletions server/graphql/v2/enum/AccountingCategoryKind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { GraphQLEnumType } from 'graphql';

import { AccountingCategoryKindList } from '../../../models/AccountingCategory';

export const GraphQLAccountingCategoryKind = new GraphQLEnumType({
name: 'AccountingCategoryKind',
values: AccountingCategoryKindList.reduce((values, key) => {
return { ...values, [key]: { value: key } };
}, {}),
});
Loading

0 comments on commit d1863d9

Please sign in to comment.