fix: formatted transaction type

This commit is contained in:
Ahmed Bouhuolia
2025-06-15 15:22:19 +02:00
parent bcae2dae03
commit bbf9ef9bc2
29 changed files with 248 additions and 67 deletions

View File

@@ -0,0 +1,24 @@
{
"sale_invoice": "Sale invoice",
"sale_receipt": "Sale receipt",
"payment_received": "Payment received",
"bill": "Bill",
"bill_payment": "Payment made",
"vendor_opening_balance": "Vendor opening balance",
"customer_opening_balance": "Customer opening balance",
"inventory_adjustment": "Inventory adjustment",
"manual_journal": "Manual journal",
"expense": "Expense",
"owner_contribution": "Owner contribution",
"transfer_to_account": "Transfer to account",
"transfer_from_account": "Transfer from account",
"other_income": "Other income",
"other_expense": "Other expense",
"owner_drawing": "Owner drawing",
"invoice_write_off": "Invoice write-off",
"credit_note": "Credit Note",
"vendor_credit": "Vendor Credit",
"refund_credit_note": "Refund Credit Note",
"refund_vendor_credit": "Refund Vendor Credit",
"landed_cost": "Landed Cost"
}

View File

@@ -8,6 +8,7 @@ import { ServiceErrorFilter } from './common/filters/service-error.filter';
import { ValidationPipe } from './common/pipes/ClassValidation.pipe';
import { ToJsonInterceptor } from './common/interceptors/to-json.interceptor';
global.__public_dirname = path.join(__dirname, '..', 'public');
global.__static_dirname = path.join(__dirname, '../static');
global.__views_dirname = path.join(global.__static_dirname, '/views');
global.__images_dirname = path.join(global.__static_dirname, '/images');

View File

@@ -4,6 +4,7 @@ import { unitOfTime } from 'moment';
import { isEmpty, castArray } from 'lodash';
import { BaseModel } from '@/models/Model';
import { Account } from './Account.model';
import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils';
// import { getTransactionTypeLabel } from '@/utils/transactions-types';
export class AccountTransaction extends BaseModel {
@@ -19,7 +20,6 @@ export class AccountTransaction extends BaseModel {
public readonly date: Date | string;
public readonly transactionType: string;
public readonly currencyCode: string;
public readonly referenceTypeFormatted: string;
public readonly transactionNumber!: string;
public readonly referenceNumber!: string;
public readonly note!: string;
@@ -72,13 +72,13 @@ export class AccountTransaction extends BaseModel {
return this.debit * this.exchangeRate;
}
// /**
// * Retrieve formatted reference type.
// * @return {string}
// */
// get referenceTypeFormatted() {
// return getTransactionTypeLabel(this.referenceType, this.transactionType);
// }
/**
* Retrieve formatted reference type.
* @return {string}
*/
get referenceTypeFormatted() {
return getTransactionTypeLabel(this.referenceType, this.transactionType);
}
/**
* Model modifiers.

View File

@@ -3,6 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { join } from 'path';
import { ServeStaticModule } from '@nestjs/serve-static';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import {
AcceptLanguageResolver,
@@ -96,6 +97,10 @@ import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '../../..', 'public'),
serveRoot: '/public',
}),
ConfigModule.forRoot({
envFilePath: '.env',
load: config,
@@ -220,7 +225,7 @@ import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module
CurrenciesModule,
MiscellaneousModule,
UsersModule,
ContactsModule
ContactsModule,
],
controllers: [AppController],
providers: [

View File

@@ -34,7 +34,10 @@ export class AuthController {
@UseGuards(LocalAuthGuard)
@ApiOperation({ summary: 'Sign in a user' })
@ApiBody({ type: AuthSigninDto })
async signin(@Request() req: Request & { user: SystemUser }, @Body() signinDto: AuthSigninDto) {
async signin(
@Request() req: Request & { user: SystemUser },
@Body() signinDto: AuthSigninDto,
) {
const { user } = req;
const tenant = await this.tenantModel.query().findById(user.tenantId);
@@ -68,7 +71,6 @@ export class AuthController {
return this.authApp.signUpConfirm(email, token);
}
@Post('/send_reset_password')
@ApiOperation({ summary: 'Send reset password email' })
@ApiBody({

View File

@@ -1,10 +1,16 @@
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class AuthSigninDto {
@ApiProperty({ example: 'password123', description: 'User password' })
@IsNotEmpty()
@IsString()
password: string;
@ApiProperty({
example: 'user@example.com',
description: 'User email address',
})
@IsNotEmpty()
@IsString()
email: string;

View File

@@ -1,19 +1,27 @@
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class AuthSignupDto {
@ApiProperty({ example: 'John', description: 'User first name' })
@IsNotEmpty()
@IsString()
firstName: string;
@ApiProperty({ example: 'Doe', description: 'User last name' })
@IsNotEmpty()
@IsString()
lastName: string;
@ApiProperty({
example: 'john.doe@example.com',
description: 'User email address',
})
@IsNotEmpty()
@IsString()
@IsEmail()
email: string;
@ApiProperty({ example: 'password123', description: 'User password' })
@IsNotEmpty()
@IsString()
password: string;

View File

@@ -1,26 +1,31 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class PlaidItemDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '123', description: 'The public token' })
publicToken: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '123', description: 'The institution ID' })
institutionId: string;
}
export class PlaidWebhookDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '123', description: 'The Plaid item ID' })
itemId: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '123', description: 'The Plaid webhook type' })
webhookType: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '123', description: 'The Plaid webhook code' })
webhookCode: string;
}

View File

@@ -1,5 +1,4 @@
import { ACCOUNT_TYPE } from "@/constants/accounts";
import { ACCOUNT_TYPE } from '@/constants/accounts';
export const ERRORS = {
CASHFLOW_TRANSACTION_TYPE_INVALID: 'CASHFLOW_TRANSACTION_TYPE_INVALID',
@@ -111,35 +110,34 @@ export const BankTransactionsSampleData = [
},
];
export const CashflowTransactionTypes = {
OtherIncome: 'Other income',
OtherExpense: 'Other expense',
OwnerDrawing: 'Owner drawing',
OwnerContribution: 'Owner contribution',
TransferToAccount: 'Transfer to account',
TransferFromAccount: 'Transfer from account',
OtherIncome: 'transaction_type.other_income',
OtherExpense: 'transaction_type.other_expense',
OwnerDrawing: 'transaction_type.owner_drawing',
OwnerContribution: 'transaction_type.owner_contribution',
TransferToAccount: 'transaction_type.transfer_to_account',
TransferFromAccount: 'transaction_type.transfer_from_account',
};
export const TransactionTypes = {
SaleInvoice: 'Sale invoice',
SaleReceipt: 'Sale receipt',
PaymentReceive: 'Payment received',
Bill: 'Bill',
BillPayment: 'Payment made',
VendorOpeningBalance: 'Vendor opening balance',
CustomerOpeningBalance: 'Customer opening balance',
InventoryAdjustment: 'Inventory adjustment',
ManualJournal: 'Manual journal',
Journal: 'Manual journal',
Expense: 'Expense',
OwnerContribution: 'Owner contribution',
TransferToAccount: 'Transfer to account',
TransferFromAccount: 'Transfer from account',
OtherIncome: 'Other income',
OtherExpense: 'Other expense',
OwnerDrawing: 'Owner drawing',
InvoiceWriteOff: 'Invoice write-off',
SaleInvoice: 'transaction_type.sale_invoice',
SaleReceipt: 'transaction_type.sale_receipt',
PaymentReceive: 'transaction_type.payment_received',
Bill: 'transaction_type.bill',
BillPayment: 'transaction_type.payment_made',
VendorOpeningBalance: 'transaction_type.vendor_opening_balance',
CustomerOpeningBalance: 'transaction_type.customer_opening_balance',
InventoryAdjustment: 'transaction_type.inventory_adjustment',
ManualJournal: 'transaction_type.manual_journal',
Journal: 'transaction_type.manual_journal',
Expense: 'transaction_type.expense',
OwnerContribution: 'transaction_type.owner_contribution',
TransferToAccount: 'transaction_type.transfer_to_account',
TransferFromAccount: 'transaction_type.transfer_from_account',
OtherIncome: 'transaction_type.other_income',
OtherExpense: 'transaction_type.other_expense',
OwnerDrawing: 'transaction_type.owner_drawing',
InvoiceWriteOff: 'transaction_type.invoice_write_off',
CreditNote: 'transaction_type.credit_note',
VendorCredit: 'transaction_type.vendor_credit',
RefundCreditNote: 'transaction_type.refund_credit_note',

View File

@@ -3,11 +3,13 @@ import { getBankAccountTransactionsDefaultQuery } from './_utils';
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
import { GetBankAccountTransactions } from './GetBankAccountTransactions';
import { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto';
import { I18nService } from 'nestjs-i18n';
@Injectable()
export class GetBankAccountTransactionsService {
constructor(
private readonly getBankAccountTransactionsRepository: GetBankAccountTransactionsRepository,
private readonly i18nService: I18nService
) {}
/**
@@ -30,6 +32,7 @@ export class GetBankAccountTransactionsService {
const report = new GetBankAccountTransactions(
this.getBankAccountTransactionsRepository,
parsedQuery,
this.i18nService
);
const transactions = report.reportData();
const pagination = this.getBankAccountTransactionsRepository.pagination;

View File

@@ -11,11 +11,13 @@ import { FinancialSheet } from '@/modules/FinancialStatements/common/FinancialSh
import { formatBankTransactionsStatus } from './_utils';
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
import { runningBalance } from '@/utils/running-balance';
import { I18nService } from 'nestjs-i18n';
export class GetBankAccountTransactions extends FinancialSheet {
private runningBalance: any;
private query: ICashflowAccountTransactionsQuery;
private repo: GetBankAccountTransactionsRepository;
private i18n: I18nService;
/**
* Constructor method.
@@ -26,11 +28,14 @@ export class GetBankAccountTransactions extends FinancialSheet {
constructor(
repo: GetBankAccountTransactionsRepository,
query: ICashflowAccountTransactionsQuery,
i18n: I18nService,
) {
super();
this.repo = repo;
this.query = query;
this.i18n = i18n;
this.runningBalance = runningBalance(this.repo.openingBalance);
}
@@ -104,7 +109,7 @@ export class GetBankAccountTransactions extends FinancialSheet {
referenceId: transaction.referenceId,
referenceType: transaction.referenceType,
formattedTransactionType: transaction.referenceTypeFormatted,
formattedTransactionType: this.i18n.t(transaction.referenceTypeFormatted),
transactionNumber: transaction.transactionNumber,
referenceNumber: transaction.referenceNumber,

View File

@@ -1,6 +1,7 @@
import { ToNumber } from '@/common/decorators/Validators';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
import {
ArrayMinSize,
IsArray,
@@ -23,93 +24,187 @@ enum DiscountType {
}
export class BillEntryDto extends ItemEntryDto {
@ApiProperty({
description: 'Flag indicating whether the entry contributes to landed cost',
example: true,
required: false,
})
@IsOptional()
@IsBoolean()
landedCost?: boolean;
}
class AttachmentDto {
@ApiProperty({
description: 'Storage key of the attachment file',
example: 'attachments/bills/receipt.pdf',
})
@IsString()
@IsNotEmpty()
key: string;
}
export class CommandBillDto {
@ApiProperty({
description: 'Unique bill number',
example: 'BILL-0001',
required: false,
})
@IsOptional()
@IsString()
billNumber: string;
@ApiProperty({
description: 'Reference number',
example: 'REF-12345',
required: false,
})
@IsOptional()
@IsString()
referenceNo?: string;
@ApiProperty({
description: 'Date the bill was issued',
example: '2025-06-01',
})
@IsNotEmpty()
@IsDateString()
billDate: Date;
@ApiProperty({
description: 'Date the bill is due',
example: '2025-07-01',
required: false,
})
@IsOptional()
@IsDateString()
dueDate?: Date;
@ApiProperty({
description: 'Vendor identifier',
example: 10,
})
@IsInt()
@IsNotEmpty()
vendorId: number;
@ApiProperty({
description: 'Exchange rate applied to bill amounts',
example: 3.67,
required: false,
})
@IsOptional()
@ToNumber()
@IsNumber()
@IsPositive()
exchangeRate?: number;
@ApiProperty({
description: 'Warehouse identifier',
example: 4,
required: false,
})
@IsOptional()
@ToNumber()
@IsInt()
warehouseId?: number;
@ApiProperty({
description: 'Branch identifier',
example: 2,
required: false,
})
@IsOptional()
@ToNumber()
@IsInt()
branchId?: number;
@ApiProperty({
description: 'Project identifier',
example: 5,
required: false,
})
@IsOptional()
@ToNumber()
@IsInt()
projectId?: number;
@ApiProperty({
description: 'Additional notes about the bill',
example: 'Payment due next month',
required: false,
})
@IsOptional()
@IsString()
note?: string;
@ApiProperty({
description: 'Indicates if the bill is open',
example: true,
required: false,
})
@IsBoolean()
@IsOptional()
open: boolean = false;
@ApiProperty({
description: 'Indicates if tax is inclusive in prices',
example: false,
required: false,
})
@IsBoolean()
@IsOptional()
isInclusiveTax: boolean = false;
@ApiProperty({
description: 'Bill line items',
type: () => BillEntryDto,
isArray: true,
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => BillEntryDto)
@ArrayMinSize(1)
entries: BillEntryDto[];
@ApiProperty({
description: 'File attachments associated with the bill',
type: () => AttachmentDto,
isArray: true,
required: false,
})
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AttachmentDto)
attachments?: AttachmentDto[];
@ApiProperty({
description: 'Type of discount applied',
example: DiscountType.Amount,
enum: DiscountType,
required: false,
})
@IsEnum(DiscountType)
@IsOptional()
discountType: DiscountType = DiscountType.Amount;
@ApiProperty({
description: 'Discount value',
example: 100,
required: false,
})
@IsOptional()
@ToNumber()
@IsNumber()
@IsPositive()
discount?: number;
@ApiProperty({
description: 'Adjustment value',
example: 50,
required: false,
})
@IsOptional()
@ToNumber()
@IsNumber()

View File

@@ -9,5 +9,5 @@ export const getPdfFilesStorageDir = (filename: string) => {
export const getPdfFilePath = (filename: string) => {
const storageDir = getPdfFilesStorageDir(filename);
return path.join(global.__static_dirname, storageDir);
return path.join(global.__public_dirname, storageDir);
};

View File

@@ -6,7 +6,7 @@ import { RefundCreditNote } from './models/RefundCreditNote';
import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto';
@Controller('credit-notes')
@ApiTags('credit-notes-refunds')
@ApiTags('Credit Note Refunds')
export class CreditNoteRefundsController {
constructor(
private readonly creditNotesRefundsApplication: CreditNotesRefundsApplication,

View File

@@ -14,7 +14,7 @@ import { ICreditNotesQueryDTO } from './types/CreditNotes.types';
import { CreateCreditNoteDto, EditCreditNoteDto } from './dtos/CreditNote.dto';
@Controller('credit-notes')
@ApiTags('credit-notes')
@ApiTags('Credit Notes')
export class CreditNotesController {
/**
* @param {CreditNoteApplication} creditNoteApplication - The credit note application service.

View File

@@ -1,16 +1,20 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty } from "class-validator";
import { IsString } from "class-validator";
export class CreateCurrencyDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ example: 'USD', description: 'The currency name' })
currencyName: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: 'USD', description: 'The currency code' })
currencyCode: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '$', description: 'The currency sign' })
currencySign: string;
}

View File

@@ -1,12 +1,15 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty } from "class-validator";
import { IsString } from "class-validator";
export class EditCurrencyDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ example: 'USD', description: 'The currency name' })
currencyName: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: '$', description: 'The currency sign' })
currencySign: string;
}

View File

@@ -24,31 +24,52 @@ class AttachmentDto {
export class ExpenseCategoryDto {
@IsInt()
@IsNotEmpty()
@ApiProperty({ example: 1, description: 'The index of the expense category' })
index: number;
@IsNotEmpty()
@ToNumber()
@IsInt()
@ApiProperty({
example: 1,
description: 'The expense account id of the expense category',
})
expenseAccountId: number;
@ToNumber()
@IsNumber()
@IsOptional()
@ApiProperty({
example: 100,
description: 'The amount of the expense category',
})
amount?: number;
@IsString()
@MaxLength(255)
@IsOptional()
@ApiProperty({
example: 'This is a description',
description: 'The description of the expense category',
})
description?: string;
@IsBoolean()
@Transform(({ value }) => parseBoolean(value, false))
@IsOptional()
@ApiProperty({
example: true,
description: 'The landed cost of the expense category',
})
landedCost?: boolean;
@ToNumber()
@IsInt()
@IsOptional()
@ApiProperty({
example: 1,
description: 'The project id of the expense category',
})
projectId?: number;
}

View File

@@ -38,7 +38,7 @@ export class TableSheetPdf {
// Generate HTML content from the template
const htmlContent = await this.templateInjectable.render(
'modules/financial-sheet',
'financial-sheet',
{
table: { rows, columns },
sheetName,

View File

@@ -1,8 +1,10 @@
import { Injectable } from '@nestjs/common';
import { TableSheetPdf } from '../../common/TableSheetPdf';
import { ICashFlowStatementQuery } from './Cashflow.types';
import { CashflowTableInjectable } from './CashflowTableInjectable';
import { HtmlTableCustomCss } from './constants';
@Injectable()
export class CashflowTablePdfInjectable {
constructor(
private readonly cashflowTable: CashflowTableInjectable,

View File

@@ -49,7 +49,7 @@ export class ProfitLossSheetController {
);
res.send(sheet);
// Retrieves the json format.
} else if (acceptHeader.includes(AcceptType.ApplicationJson)) {
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const pdfContent = await this.profitLossSheetApp.pdf(query);
res.set({

View File

@@ -7,7 +7,6 @@ import {
Req,
Res,
} from '@nestjs/common';
import { ISalesByItemsReportQuery } from './SalesByItems.types';
import { AcceptType } from '@/constants/accept-type';
import { SalesByItemsApplication } from './SalesByItemsApplication';
import { Response } from 'express';
@@ -21,7 +20,10 @@ export class SalesByItemsController {
@Get()
@ApiResponse({ status: 200, description: 'Sales by items report' })
@ApiOperation({ summary: 'Get sales by items report' })
@ApiOperation({
summary: 'Sales by items report',
description: 'Retrieves the sales by items report.',
})
public async salesByitems(
@Query() filter: SalesByItemsQueryDto,
@Res({ passthrough: true }) res: Response,

View File

@@ -46,8 +46,7 @@ export class InventoryAdjustmentsGLEntries {
/**
* Reverts the adjustment transactions GL entries.
* @param {number} tenantId
* @param {number} inventoryAdjustmentId
* @param {number} inventoryAdjustmentId
* @returns {Promise<void>}
*/
public revertAdjustmentGLEntries = (
@@ -63,7 +62,6 @@ export class InventoryAdjustmentsGLEntries {
/**
* Rewrite inventory adjustment GL entries.
* @param {number} tenantId
* @param {number} inventoryAdjustmentId
* @param {Knex.Transaction} trx
*/

View File

@@ -21,13 +21,12 @@ import {
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { IItemsFilter } from './types/Items.types';
import { CreateItemDto, EditItemDto } from './dtos/Item.dto';
import { GetItemsQueryDto } from './dtos/GetItemsQuery.dto';
@Controller('/items')
@UseGuards(SubscriptionGuard)
@ApiTags('items')
@ApiTags('Items')
export class ItemsController extends TenantController {
constructor(private readonly itemsApplication: ItemsApplicationService) {
super();
@@ -118,6 +117,12 @@ export class ItemsController extends TenantController {
description: 'The item has been successfully updated.',
})
@ApiResponse({ status: 404, description: 'The item not found.' })
@ApiParam({
name: 'id',
required: true,
type: Number,
description: 'The item id',
})
async editItem(
@Param('id') id: string,
@Body() editItemDto: EditItemDto,

View File

@@ -14,10 +14,7 @@ export class CommandTaxRateDto {
*/
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The name of the tax rate.',
example: 'VAT',
})
@ApiProperty({ description: 'The name of the tax rate.', example: 'VAT' })
name: string;
/**
@@ -25,10 +22,7 @@ export class CommandTaxRateDto {
*/
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The code of the tax rate.',
example: 'VAT',
})
@ApiProperty({ description: 'The code of the tax rate.', example: 'VAT' })
code: string;
/**

View File

@@ -2,6 +2,6 @@ import * as path from 'path';
import * as pug from 'pug';
export function templateRender(filePath: string, options: Record<string, any>) {
const basePath = path.join(global.__resources_dir, '/views');
return pug.renderFile(`${basePath}/${filePath}.pug`, options);
const templatePath = path.join(global.__views_dirname, `${filePath}.pug`);
return pug.renderFile(templatePath, options);
}

View File

@@ -37,7 +37,7 @@ export function useBalanceSheet(query, props) {
*/
export const useBalanceSheetXlsxExport = (query, args) => {
return useDownloadFile({
url: '/reports/balance_sheet',
url: '/reports/balance-sheet',
config: {
headers: {
accept: 'application/xlsx',
@@ -57,7 +57,7 @@ export const useBalanceSheetXlsxExport = (query, args) => {
*/
export const useBalanceSheetCsvExport = (query, args) => {
return useDownloadFile({
url: '/reports/balance_sheet',
url: '/reports/balance-sheet',
config: {
headers: {
accept: 'application/csv',
@@ -76,7 +76,7 @@ export const useBalanceSheetCsvExport = (query, args) => {
*/
export function useBalanceSheetPdf(query = {}) {
return useRequestPdf({
url: `/reports/balance_sheet`,
url: `/reports/balance-sheet`,
params: query,
});
}

View File

@@ -75,7 +75,7 @@ export function useCashflowTransaction(id, props) {
[t.CASH_FLOW_TRANSACTIONS, id],
{ method: 'get', url: `banking/transactions/${id}` },
{
select: (res) => res.data.cashflow_transaction,
select: (res) => res.data,
defaultData: [],
...props,
},

View File

@@ -42,7 +42,7 @@ export function useEditItem(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(([id, values]) => apiRequest.post(`items/${id}`, values), {
return useMutation(([id, values]) => apiRequest.put(`items/${id}`, values), {
onSuccess: (res, [id, values]) => {
// Invalidate specific item.
queryClient.invalidateQueries([t.ITEM, id]);