Skip to content

Commit

Permalink
fix: automatically update pending expenses when vendor updates payout…
Browse files Browse the repository at this point in the history
… method
  • Loading branch information
kewitz committed Dec 10, 2024
1 parent 20ef10e commit 4bc3813
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 3 deletions.
20 changes: 18 additions & 2 deletions server/graphql/v2/mutation/VendorMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { v4 as uuid } from 'uuid';
import ActivityTypes from '../../../constants/activities';
import { CollectiveType } from '../../../constants/collectives';
import { getDiffBetweenInstances } from '../../../lib/data';
import models, { Activity, LegalDocument } from '../../../models';
import models, { Activity, LegalDocument, Op } from '../../../models';
import { ExpenseStatus } from '../../../models/Expense';
import { checkRemoteUserCanUseHost } from '../../common/scope-check';
import { BadRequest, NotFound, Unauthorized, ValidationFailed } from '../../errors';
import { idDecode, IDENTIFIER_TYPES } from '../identifiers';
Expand Down Expand Up @@ -193,24 +194,39 @@ const vendorMutations = {
}

if (args.vendor.payoutMethod) {
let payoutMethod;
const existingPayoutMethods = await vendor.getPayoutMethods({ where: { isSaved: true } });
if (!args.vendor.payoutMethod.id) {
if (!isEmpty(existingPayoutMethods)) {
existingPayoutMethods.map(pm => pm.update({ isSaved: false }));
}

await models.PayoutMethod.create({
payoutMethod = await models.PayoutMethod.create({
...pick(args.vendor.payoutMethod, ['name', 'data', 'type']),
CollectiveId: vendor.id,
CreatedByUserId: req.remoteUser.id,
isSaved: true,
});
} else {
const payoutMethodId = idDecode(args.vendor.payoutMethod.id, IDENTIFIER_TYPES.PAYOUT_METHOD);
payoutMethod = await models.PayoutMethod.findByPk(payoutMethodId);
await Promise.all(
existingPayoutMethods.filter(pm => pm.id !== payoutMethodId).map(pm => pm.update({ isSaved: false })),
);
}

// Since vendors can only have a single payout method, we update all expenses to use the new one
await models.Expense.update(
{ PayoutMethodId: payoutMethod.id },
{
where: {
FromCollectiveId: vendor.id,
status: {
[Op.in]: [ExpenseStatus.APPROVED, ExpenseStatus.DRAFT, ExpenseStatus.ERROR, ExpenseStatus.PENDING],
},
},
},
);
}
return vendor;
},
Expand Down
58 changes: 57 additions & 1 deletion test/server/graphql/v2/mutation/VendorMutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import gql from 'fake-tag';
import { CollectiveType } from '../../../../../server/constants/collectives';
import models from '../../../../../server/models';
import { LEGAL_DOCUMENT_TYPE } from '../../../../../server/models/LegalDocument';
import { fakeCollective, fakeHost, fakeTransaction, fakeUser } from '../../../../test-helpers/fake-data';
import {
fakeCollective,
fakeExpense,
fakeHost,
fakePayoutMethod,
fakeTransaction,
fakeUser,
} from '../../../../test-helpers/fake-data';
import { getMockFileUpload, graphqlQueryV2 } from '../../../../utils';

describe('server/graphql/v2/mutation/VendorMutations', () => {
Expand Down Expand Up @@ -228,6 +235,55 @@ describe('server/graphql/v2/mutation/VendorMutations', () => {
const location = await vendor.getLocation();
expect(location.address).to.equal('Zorg Avenue, 1');
});

it('invalidates existing Payout Method and updates existing Expenses', async () => {
const vendor = await fakeCollective({
type: CollectiveType.VENDOR,
ParentCollectiveId: host.id,
data: { vendorInfo: vendorData.vendorInfo },
});
const existingPayoutMethod = await fakePayoutMethod({ CollectiveId: vendor.id, isSaved: true });
const existingExpense = await fakeExpense({
FromCollectiveId: vendor.id,
PayoutMethodId: existingPayoutMethod.id,
status: 'PENDING',
});
const existingPaidExpense = await fakeExpense({
FromCollectiveId: vendor.id,
PayoutMethodId: existingPayoutMethod.id,
status: 'PAID',
});
const newVendorData = {
legacyId: vendor.id,
payoutMethod: {
type: 'PAYPAL',
name: 'Zorg Inc',
data: { email: 'zorg@zorg.com' },
isSaved: true,
},
};
const result = await graphqlQueryV2(
editVendorMutation,
{
vendor: newVendorData,
},
hostAdminUser,
);
result.errors && console.error(result.errors);
expect(result.errors).to.not.exist;

await existingPayoutMethod.reload();
expect(existingPayoutMethod.isSaved).to.be.false;

await existingExpense.reload();
expect(existingExpense.PayoutMethodId).to.not.equal(existingPayoutMethod.id);

await existingPaidExpense.reload();
expect(existingPaidExpense.PayoutMethodId).to.equal(existingPayoutMethod.id);

await models.PayoutMethod.destroy({ where: { CollectiveId: vendor.id }, force: true });
await models.Expense.destroy({ where: { FromCollectiveId: vendor.id }, force: true });
});
});

describe('deleteVendor', () => {
Expand Down

0 comments on commit 4bc3813

Please sign in to comment.