201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
import {
|
||
ICommerceService,
|
||
PriceList,
|
||
Payment,
|
||
Shipment,
|
||
ShipmentItem,
|
||
Return,
|
||
ReturnItem,
|
||
} from '../contracts/commerce';
|
||
import { IRepository } from '../contracts/repository';
|
||
import { Order } from '../../entities/commerce/order';
|
||
import { Product, ProductCategory } from '../../entities/commerce/product';
|
||
import { MoneyAmount } from '../../entities/types';
|
||
import { BaseMockRepository } from './baseRepository';
|
||
import { ulid } from '../../shared/lib/ulid';
|
||
import { getServices } from './index';
|
||
|
||
export class MockCommerceService implements ICommerceService {
|
||
products: IRepository<Product>;
|
||
categories: IRepository<ProductCategory>;
|
||
priceLists: IRepository<PriceList>;
|
||
orders: IRepository<Order>;
|
||
payments: IRepository<Payment>;
|
||
shipments: IRepository<Shipment>;
|
||
returns: IRepository<Return>;
|
||
|
||
constructor() {
|
||
this.products = new BaseMockRepository<Product>('product');
|
||
this.categories = new BaseMockRepository<ProductCategory>('product_category');
|
||
this.priceLists = new BaseMockRepository<PriceList>('price_list');
|
||
this.orders = new BaseMockRepository<Order>('order');
|
||
this.payments = new BaseMockRepository<Payment>('payment');
|
||
this.shipments = new BaseMockRepository<Shipment>('shipment');
|
||
this.returns = new BaseMockRepository<Return>('return');
|
||
}
|
||
|
||
async updateOrderStatus(orderId: string, status: Order['status']): Promise<Order> {
|
||
return this.orders.update(orderId, { status });
|
||
}
|
||
|
||
async confirmOrder(orderId: string, warehouseId = 'wh_main'): Promise<Order> {
|
||
const order = await this.orders.getById(orderId);
|
||
if (!order) {
|
||
throw new Error(`Заказ с ID ${orderId} не найден.`);
|
||
}
|
||
|
||
// Reserve stock in inventory
|
||
const inventory = getServices().inventory;
|
||
if (inventory && order.items) {
|
||
for (const item of order.items) {
|
||
await inventory.reserveStock(orderId, warehouseId, item.productId, item.quantity);
|
||
}
|
||
}
|
||
|
||
return this.orders.update(orderId, { status: 'confirmed' });
|
||
}
|
||
|
||
async cancelOrder(orderId: string): Promise<Order> {
|
||
const order = await this.orders.getById(orderId);
|
||
if (!order) {
|
||
throw new Error(`Заказ с ID ${orderId} не найден.`);
|
||
}
|
||
|
||
// Release any active warehouse reservations
|
||
const inventory = getServices().inventory;
|
||
if (inventory) {
|
||
await inventory.cancelOrderReservations(orderId);
|
||
}
|
||
|
||
return this.orders.update(orderId, { status: 'cancelled' });
|
||
}
|
||
|
||
async createPayment(
|
||
orderId: string,
|
||
amount: MoneyAmount,
|
||
method: Payment['method'],
|
||
autoRecordFinance = false
|
||
): Promise<Payment> {
|
||
const order = await this.orders.getById(orderId);
|
||
if (!order) {
|
||
throw new Error(`Заказ с ID ${orderId} не найден.`);
|
||
}
|
||
|
||
const now = new Date().toISOString();
|
||
const payment = await this.payments.create({
|
||
orderId,
|
||
orderNumber: order.orderNumber,
|
||
amount,
|
||
method,
|
||
status: 'completed',
|
||
paidAt: now,
|
||
transactionId: `TXN-${ulid().slice(0, 10)}`,
|
||
});
|
||
|
||
// Update order payment status
|
||
await this.orders.update(orderId, { paymentStatus: 'paid' });
|
||
|
||
// Automatically record finance transaction if requested
|
||
if (autoRecordFinance) {
|
||
const finance = getServices().finance;
|
||
if (finance) {
|
||
await finance.createTransactionFromPayment(payment.id, orderId, amount);
|
||
}
|
||
}
|
||
|
||
return payment;
|
||
}
|
||
|
||
async createShipment(
|
||
orderId: string,
|
||
warehouseId: string,
|
||
items?: ShipmentItem[]
|
||
): Promise<Shipment> {
|
||
const order = await this.orders.getById(orderId);
|
||
if (!order) {
|
||
throw new Error(`Заказ с ID ${orderId} не найден.`);
|
||
}
|
||
|
||
const shipmentItems: ShipmentItem[] =
|
||
items ||
|
||
order.items.map((i) => ({
|
||
productId: i.productId,
|
||
sku: i.sku,
|
||
name: i.name,
|
||
quantity: i.quantity,
|
||
}));
|
||
|
||
const shipment = await this.shipments.create({
|
||
orderId,
|
||
orderNumber: order.orderNumber,
|
||
warehouseId,
|
||
status: 'ready',
|
||
trackingNumber: `TRK-${ulid().slice(0, 8)}`,
|
||
carrier: 'Krona Экспресс',
|
||
dispatchedAt: new Date().toISOString(),
|
||
items: shipmentItems,
|
||
});
|
||
|
||
await this.orders.update(orderId, { fulfillmentStatus: 'fulfilled', status: 'shipped' });
|
||
|
||
// Deduct reserved stock and physical stock in inventory
|
||
const inventory = getServices().inventory;
|
||
if (inventory) {
|
||
await inventory.fulfillOrderReservations(orderId, warehouseId, shipmentItems);
|
||
}
|
||
|
||
return shipment;
|
||
}
|
||
|
||
async processReturn(
|
||
orderId: string,
|
||
reason: string,
|
||
items: ReturnItem[],
|
||
warehouseId = 'wh_main',
|
||
autoRecordFinance = false
|
||
): Promise<Return> {
|
||
const order = await this.orders.getById(orderId);
|
||
if (!order) {
|
||
throw new Error(`Заказ с ID ${orderId} не найден.`);
|
||
}
|
||
|
||
const totalMinor = items.reduce((sum, item) => sum + item.refundAmount.minor, 0);
|
||
|
||
const ret = await this.returns.create({
|
||
orderId,
|
||
orderNumber: order.orderNumber,
|
||
status: 'refunded',
|
||
reason,
|
||
totalRefund: { minor: totalMinor, currency: order.total.currency },
|
||
items,
|
||
});
|
||
|
||
await this.orders.update(orderId, {
|
||
fulfillmentStatus: 'returned',
|
||
paymentStatus: 'refunded',
|
||
});
|
||
|
||
// Restore physical stock in inventory
|
||
const inventory = getServices().inventory;
|
||
if (inventory) {
|
||
for (const item of items) {
|
||
await inventory.adjustStock(
|
||
warehouseId,
|
||
item.productId,
|
||
item.quantity,
|
||
`Возврат по заказу #${order.orderNumber}: ${reason}`
|
||
);
|
||
}
|
||
}
|
||
|
||
// Automatically record refund transaction in finance if requested
|
||
if (autoRecordFinance) {
|
||
const finance = getServices().finance;
|
||
if (finance) {
|
||
await finance.createTransactionFromReturn(ret.id, orderId, ret.totalRefund);
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
}
|