locationService.ts 6.13 KB
import { createApiClient } from "../lib/apiClient";
import { authorizedPostBlobDownload, authorizedPostMultipartJson } from "../lib/batchFileHttp";
import {
  normalizeLocationFromApi,
  type LocationCreateInput,
  type LocationDto,
  type LocationGetListInput,
  type LocationUpdateInput,
  type PagedResultDto,
} from "../types/location";

const api = createApiClient({
  // 如果项目后续接入登录,可在这里加 token 获取逻辑
  getToken: () => {
    try {
      return localStorage.getItem("access_token") ?? localStorage.getItem("token") ?? null;
    } catch {
      return null;
    }
  },
});

/**
 * ABP Conventional Controller 默认路由通常为:
 * - GET /api/app/location
 * - POST /api/app/location
 * 这里按该约定实现;如果后端实际路由不同,以 Swagger 为准调整 path。
 */
function trimOrNullHours(v: string | null | undefined): string | null {
  const t = (v ?? "").trim();
  return t ? t : null;
}

export async function getLocations(input: LocationGetListInput, signal?: AbortSignal): Promise<PagedResultDto<LocationDto>> {
  const raw = await api.requestJson<PagedResultDto<LocationDto> & { items?: unknown[]; Items?: unknown[] }>({
    path: "/location",
    method: "GET",
    query: {
      SkipCount: input.skipCount,
      MaxResultCount: input.maxResultCount,
      Sorting: input.sorting,
      Keyword: input.keyword,
      Partner: input.partner,
      GroupName: input.groupName,
      State: input.state,
    },
    signal,
  });
  const itemsRaw = raw.items ?? raw.Items ?? [];
  const items = (Array.isArray(itemsRaw) ? itemsRaw : []).map((x) => normalizeLocationFromApi(x));
  return { ...raw, items, totalCount: raw.totalCount ?? (raw as { TotalCount?: number }).TotalCount ?? items.length };
}

export async function createLocation(input: LocationCreateInput): Promise<LocationDto> {
  const raw = await api.requestJson<unknown>({
    path: "/location",
    method: "POST",
    body: {
      partner: input.partner,
      groupName: input.groupName,
      locationCode: input.locationCode,
      locationName: input.locationName,
      street: input.street,
      city: input.city,
      stateCode: input.stateCode,
      country: input.country,
      zipCode: input.zipCode,
      phone: input.phone,
      email: input.email,
      latitude: input.latitude,
      longitude: input.longitude,
      state: input.state ?? true,
      operatingHours: trimOrNullHours(input.operatingHours ?? undefined),
    },
  });
  return normalizeLocationFromApi(raw);
}

/**
 * ABP Conventional Controller 的 UpdateAsync 常见路由为:
 * - PUT /api/app/location/{id}
 * 以 Swagger 为准;若后端使用其它形式(如 /location?id=xxx),在此调整即可。
 */
export async function updateLocation(id: string, input: LocationUpdateInput): Promise<LocationDto> {
  const raw = await api.requestJson<unknown>({
    path: `/location/${encodeURIComponent(id)}`,
    method: "PUT",
    body: {
      partner: input.partner,
      groupName: input.groupName,
      locationCode: input.locationCode,
      locationName: input.locationName,
      street: input.street,
      city: input.city,
      stateCode: input.stateCode,
      country: input.country,
      zipCode: input.zipCode,
      phone: input.phone,
      email: input.email,
      latitude: input.latitude,
      longitude: input.longitude,
      state: input.state ?? true,
      operatingHours: trimOrNullHours(input.operatingHours ?? undefined),
    },
  });
  return normalizeLocationFromApi(raw);
}

/**
 * ABP Conventional Controller 的 DeleteAsync 常见路由为:
 * - DELETE /api/app/location/{id}
 * 以 Swagger 为准;若后端路由不同,在此调整 path。
 */
export async function deleteLocation(id: string): Promise<void> {
  await api.requestJson<unknown>({
    path: `/location/${encodeURIComponent(id)}`,
    method: "DELETE",
  });
}

/** 与列表筛选一致,用于模板下载外的 Excel 全量导出(不含分页)。 */
export type LocationExportQueryInput = {
  sorting?: string;
  keyword?: string;
  partner?: string;
  groupName?: string;
  state?: boolean;
};

export type LocationBatchImportResultDto = {
  successCount: number;
  failCount: number;
  skippedEmptyRows?: number;
  errors?: Array<{
    rowNumber?: number;
    locationCode?: string;
    message?: string;
  }>;
};

export type LocationBulkUpdateItemVo = {
  id: string;
  partner?: string | null;
  groupName?: string | null;
  locationName: string;
  street?: string | null;
  city?: string | null;
  stateCode?: string | null;
  country?: string | null;
  zipCode?: string | null;
  phone?: string | null;
  email?: string | null;
  latitude?: number | null;
  longitude?: number | null;
  state?: boolean;
};

export type LocationBulkUpdateResultDto = {
  successCount: number;
  failCount: number;
  errors?: Array<{ rowNumber?: number; id?: string; message?: string }>;
};

export async function downloadLocationImportTemplate(signal?: AbortSignal): Promise<void> {
  await authorizedPostBlobDownload({
    path: "/location/download-location-import-template",
    defaultFileName: "Location-Manager-template.xlsx",
    signal,
  });
}

export async function exportLocationsExcel(input: LocationExportQueryInput, signal?: AbortSignal): Promise<void> {
  await authorizedPostBlobDownload({
    path: "/location/export-locations-excel",
    query: {
      Sorting: input.sorting,
      Keyword: input.keyword,
      Partner: input.partner,
      GroupName: input.groupName,
      State: input.state,
    },
    defaultFileName: "locations-export.xlsx",
    signal,
  });
}

export async function importLocationsBatch(file: File, signal?: AbortSignal): Promise<LocationBatchImportResultDto> {
  return authorizedPostMultipartJson<LocationBatchImportResultDto>({
    path: "/location/import-locations-batch",
    fieldName: "file",
    file,
    signal,
  });
}

export async function updateLocationsBulk(
  body: { items: LocationBulkUpdateItemVo[] },
): Promise<LocationBulkUpdateResultDto> {
  // ABP:`UpdateLocationsBulkAsync` → `locations-bulk`(勿写 `update-locations-bulk`)
  return api.requestJson<LocationBulkUpdateResultDto>({
    path: "/location/locations-bulk",
    method: "PUT",
    body,
  });
}