1import { NextRequest, NextResponse } from 'next/server';
2import { fieldpineServerApi } from '@/lib/server/fieldpineApi';
3import { getRequestContext, validateApiAccess } from '@/lib/server/sessionUtils';
6 * Goods Movement Report
7 * BUCK: retailmax.elink.goodsmovement.codedetail
8 * Returns stock transfers, adjustments, write-offs
10export async function GET(request: NextRequest) {
11 console.log('[Goods Movement] START - Route handler called');
13 // 1. Get and validate session
14 const context = await getRequestContext(request);
15 const validation = validateApiAccess(context, 'elink');
16 if (!validation.valid || !context) {
17 return NextResponse.json(
18 { error: validation.error || 'Access denied', errorCode: validation.errorCode },
23 const { searchParams } = new URL(request.url);
26 const startDate = searchParams.get('startDate');
27 const endDate = searchParams.get('endDate');
28 const limit = searchParams.get('limit') || '200';
30 // Movement type: transfer, adjustment, writeoff, stocktake
31 const movementType = searchParams.get('type');
34 const locationId = searchParams.get('locationId');
37 const productId = searchParams.get('productId');
39 // Build BUCK parameters
40 const buckParams: Record<string, string | string[]> = {
41 '3': 'retailmax.elink.goodsmovement.codedetail',
43 '99': Math.random().toString() // Cache buster
46 // Build filters array
47 const filters: string[] = [];
51 filters.push(`f102,4,${startDate}`); // Movement date >=
54 filters.push(`f102,1,${endDate}`); // Movement date <
57 // Add location filter
59 filters.push(`f131,2,${locationId}`);
64 filters.push(`f200,2,${productId}`);
67 // Add movement type filter
70 switch (movementType) {
87 filters.push(`f110,2,${typeCode}`);
90 // Add filters to BUCK params
91 if (filters.length > 0) {
92 buckParams['9'] = filters;
95 // Use store-specific URL for API calls
96 const result = await fieldpineServerApi.buckApiCall(buckParams, context.session.apiKey, context.store.url);
98 console.log('[Goods Movement] BUCK response:', { hasResult: !!result, resultType: typeof result });
100 // Process BUCK response
101 if (result?.DATS && Array.isArray(result.DATS)) {
102 const movements = result.DATS.map((item: any) => ({
103 movementId: item?.f100,
104 movementDate: item?.f102,
105 movementType: item?.f110,
106 movementTypeName: item?.f111,
107 productId: item?.f200,
108 productName: item?.f201,
110 quantity: item?.f210,
111 unitCost: item?.f211,
112 totalCost: item?.f212,
113 fromLocationId: item?.f131,
114 fromLocationName: item?.f132,
115 toLocationId: item?.f133,
116 toLocationName: item?.f134,
117 reasonCode: item?.f120,
118 reasonDescription: item?.f121,
120 userName: item?.f141,
122 referenceNumber: item?.f160
125 // Calculate summary by type
126 const summary = movements.reduce((acc: Record<string, any>, m: any) => {
127 const type = m.movementTypeName || 'Unknown';
129 acc[type] = { count: 0, totalUnits: 0, totalValue: 0 };
132 acc[type].totalUnits += Math.abs(m.quantity || 0);
133 acc[type].totalValue += Math.abs(m.totalCost || 0);
135 }, {} as Record<string, any>);
137 return NextResponse.json({
140 count: movements.length,
146 return NextResponse.json({
154 console.error('[Goods Movement] Error:', error);
155 return NextResponse.json(
156 { error: 'Failed to fetch goods movements' },