Files
trackgo-be/src/modules/sales/sales-payments/sales-payments.service.spec.ts
T
shancheas afed5ff0f5 Enhance sales document flow with new service and related functionalities
- Introduced `SalesDocumentFlowService` to manage the lifecycle of sales documents, including packing slips and invoices.
- Implemented methods for processing orders, generating invoices, and handling packing slips based on order status.
- Updated `SalesOrdersService`, `PackingSlipsService`, and `SalesInvoicesService` to integrate with the new document flow service.
- Added new methods for marking drafts processed and checking if invoices are on sales plans.
- Enhanced existing services and repositories to support new functionalities, including status transitions and related document management.
- Updated DTOs to include new fields for packing slip and invoice IDs in sales order responses.
- Added unit tests for the new service and updated existing tests to cover new functionalities.
2026-08-31 09:35:18 +07:00

145 lines
4.6 KiB
TypeScript

import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { DateTime } from '../../../common/value-objects/date-time/date-time';
import { Decimal } from '../../../common/value-objects/decimal/decimal';
import { Status } from '../../../common/value-objects/status/status';
import { SalesInvoicesService } from '../sales-invoices/sales-invoices.service';
import { SALES_PAYMENT_STATUSES } from '../shared/sales-fields';
import type { SalesPayment } from './sales-payment';
import { SalesPaymentsRepository } from './sales-payments.repository';
import { SalesPaymentsService } from './sales-payments.service';
describe('SalesPaymentsService', () => {
let service: SalesPaymentsService;
const repository: jest.Mocked<
Pick<
SalesPaymentsRepository,
| 'list'
| 'findById'
| 'create'
| 'createMany'
| 'update'
| 'updateStatus'
| 'bulkUpdateStatus'
| 'delete'
| 'bulkDelete'
>
> = {
list: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
createMany: jest.fn(),
update: jest.fn(),
updateStatus: jest.fn(),
bulkUpdateStatus: jest.fn(),
delete: jest.fn(),
bulkDelete: jest.fn(),
};
const salesInvoicesService = {
findById: jest.fn(),
getTotals: jest.fn(),
applyPaymentEffects: jest.fn(),
};
const now = DateTime.fromUnixMs(1_700_000_000_000);
const sample: SalesPayment = {
id: 'sp-1',
code: 'SP-20260824-0001',
date: now,
notes: null,
invoices: [
{
id: 'alloc-1',
invoiceId: 'si-1',
invoice: { id: 'si-1', code: 'SI-1' },
amount: Decimal.create('10000'),
},
],
images: [],
status: Status.create('draft', SALES_PAYMENT_STATUSES),
createdAt: now,
updatedAt: now,
createdBy: 'user-1',
updatedBy: 'user-1',
createdByUser: { id: 'user-1', username: 'admin' },
updatedByUser: { id: 'user-1', username: 'admin' },
};
beforeEach(async () => {
jest.clearAllMocks();
salesInvoicesService.findById.mockResolvedValue({ id: 'si-1' });
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
SalesPaymentsService,
{ provide: SalesPaymentsRepository, useValue: repository },
{ provide: SalesInvoicesService, useValue: salesInvoicesService },
],
}).compile();
service = moduleRef.get(SalesPaymentsService);
});
it('create persists invoice allocations', async () => {
repository.create.mockResolvedValue(sample);
const result = await service.create({
date: '2026-08-24T10:00:00+07:00',
invoices: [{ invoiceId: 'si-1', amount: '10000' }],
userId: 'user-1',
});
expect(result.code).toBe('SP-20260824-0001');
expect(repository.create).toHaveBeenCalled();
const [arg] = repository.create.mock.calls[0];
expect(arg.invoices[0]?.invoiceId).toBe('si-1');
expect(arg.invoices[0]?.amount.value).toBe('10000.0000');
});
it('update rejects status on PATCH', async () => {
await expect(
service.update('sp-1', { status: 'approved', userId: 'user-1' }),
).rejects.toBeInstanceOf(BadRequestException);
});
it('findById throws when missing', async () => {
repository.findById.mockResolvedValue(null);
await expect(service.findById('missing')).rejects.toBeInstanceOf(
NotFoundException,
);
});
it('approving a payment recomputes referenced invoices', async () => {
repository.findById.mockResolvedValue({
...sample,
status: Status.create('pending', SALES_PAYMENT_STATUSES),
});
salesInvoicesService.getTotals.mockResolvedValue({
total: Decimal.create('25000'),
paid: Decimal.create('0'),
balance: Decimal.create('25000'),
});
repository.updateStatus.mockResolvedValue({
...sample,
status: Status.create('approved', SALES_PAYMENT_STATUSES),
});
await service.updateStatus('sp-1', 'approved', 'user-1');
expect(salesInvoicesService.applyPaymentEffects).toHaveBeenCalledWith(
'si-1',
'user-1',
);
});
it('rejects approve when allocation would exceed the invoice total', async () => {
repository.findById.mockResolvedValue({
...sample,
status: Status.create('pending', SALES_PAYMENT_STATUSES),
});
salesInvoicesService.getTotals.mockResolvedValue({
total: Decimal.create('5000'),
paid: Decimal.create('0'),
balance: Decimal.create('5000'),
});
await expect(
service.updateStatus('sp-1', 'approved', 'user-1'),
).rejects.toBeInstanceOf(BadRequestException);
expect(repository.updateStatus).not.toHaveBeenCalled();
});
});