krona-front/src/services/mock/commerceService.ts

201 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}