merge: integrate prod-v8 (inter-project transfers + multi-project allocation)
Conflict resolved in ChangeInventoryPage.tsx (canSubmit): kept both the WITHDRAWAL branch (prod-v9: delivered-to validation) and the PROJECT_TRANSFER branch (prod-v8: inter-project transfer validation). All other files auto-merged; both feature sets fully preserved.
This commit is contained in:
+83
-13
@@ -30,6 +30,10 @@ import type {
|
||||
Supplier,
|
||||
TransactionType,
|
||||
Warehouse,
|
||||
ProjectTransfer,
|
||||
ProjectTransferCreateIn,
|
||||
ProjectTransferUpdateIn,
|
||||
ProjectTransferReceiveIn,
|
||||
} from "./types";
|
||||
|
||||
const BASE_URL = (import.meta.env?.VITE_API_BASE_URL as string | undefined)?.trim() || "/api/v1";
|
||||
@@ -239,20 +243,32 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
}
|
||||
|
||||
async function getData<T>(path: string): Promise<T> {
|
||||
const res = await request<{ data: T }>(path);
|
||||
return res.data;
|
||||
const res = await request<any>(path);
|
||||
if (res && typeof res === "object" && "data" in res) {
|
||||
return res.data as T;
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
async function postData<T>(path: string, body: unknown): Promise<T> {
|
||||
const res = await request<{ data: T }>(path, { method: "POST", body: JSON.stringify(body) });
|
||||
return res.data;
|
||||
const res = await request<any>(path, { method: "POST", body: JSON.stringify(body) });
|
||||
if (res && typeof res === "object" && "data" in res) {
|
||||
return res.data as T;
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
async function patchData<T>(path: string, body: unknown): Promise<T> {
|
||||
const res = await request<{ data: T }>(path, { method: "PATCH", body: JSON.stringify(body) });
|
||||
return res.data;
|
||||
const res = await request<any>(path, { method: "PATCH", body: JSON.stringify(body) });
|
||||
if (res && typeof res === "object" && "data" in res) {
|
||||
return res.data as T;
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
async function deleteData<T>(path: string): Promise<T> {
|
||||
const res = await request<{ data: T }>(path, { method: "DELETE" });
|
||||
return res.data;
|
||||
const res = await request<any>(path, { method: "DELETE" });
|
||||
if (res && typeof res === "object" && "data" in res) {
|
||||
return res.data as T;
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
|
||||
// آپلود چندبخشی (multipart) — بدون هدر Content-Type دستی تا مرورگر
|
||||
@@ -927,13 +943,28 @@ export const api = {
|
||||
|
||||
if (becomingAccepted) {
|
||||
const acceptedTs = (patch.termsheets || []).filter((t) => t.status === "accepted");
|
||||
// پروژهی واحدِ انتخابشده برای هر قلم (item_decisions.project_id)
|
||||
// پروژهی واحدِ انتخابشده یا تخصیصهای چندپروژهای برای هر قلم
|
||||
const targetProjByLine = new Map<string, string>();
|
||||
const allocationsByLine = new Map<string, { project_id: string; quantity: number }[]>();
|
||||
for (const li of patch.line_items || []) {
|
||||
const tp = li.target_project;
|
||||
if (tp && tp.name) {
|
||||
const pid = looksLikeUuid(tp.id) ? tp.id : await projectIdByNameOrCreate(tp.name);
|
||||
if (pid) targetProjByLine.set(li.id, pid);
|
||||
if (li.allocations && li.allocations.length > 0) {
|
||||
const resolvedAllocs: { project_id: string; quantity: number }[] = [];
|
||||
for (const a of li.allocations) {
|
||||
const pid = looksLikeUuid(a.project_id) ? a.project_id! : await projectIdByNameOrCreate(a.project_name);
|
||||
if (pid && a.quantity > 0) {
|
||||
resolvedAllocs.push({ project_id: pid, quantity: Number(a.quantity) });
|
||||
}
|
||||
}
|
||||
if (resolvedAllocs.length) {
|
||||
allocationsByLine.set(li.id, resolvedAllocs);
|
||||
targetProjByLine.set(li.id, resolvedAllocs[0].project_id);
|
||||
}
|
||||
} else {
|
||||
const tp = li.target_project;
|
||||
if (tp && tp.name) {
|
||||
const pid = looksLikeUuid(tp.id) ? tp.id : await projectIdByNameOrCreate(tp.name);
|
||||
if (pid) targetProjByLine.set(li.id, pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,11 +984,13 @@ export const api = {
|
||||
const idx = li.accepted_ts_id ? acceptedTs.findIndex((t) => t.id === li.accepted_ts_id) : -1;
|
||||
const chosen = !li.rejected && idx >= 0;
|
||||
const chosenProjId = targetProjByLine.get(li.id);
|
||||
const lineAllocs = allocationsByLine.get(li.id);
|
||||
return {
|
||||
line_item_id: li.id,
|
||||
status: chosen ? "chosen" : "rejected",
|
||||
...(chosen ? { accepted_termsheet_index: idx } : {}),
|
||||
...(chosen && chosenProjId ? { project_id: chosenProjId } : {}),
|
||||
...(chosen && lineAllocs && lineAllocs.length ? { allocations: lineAllocs } : {}),
|
||||
...(li.unit_price != null ? { adjusted_unit_price: li.unit_price } : {}),
|
||||
};
|
||||
}),
|
||||
@@ -1729,4 +1762,41 @@ export const apiV26 = {
|
||||
attributes: [],
|
||||
}),
|
||||
},
|
||||
|
||||
projectTransfers: {
|
||||
list: async (projectId?: string | null, direction: "outgoing" | "incoming" | "all" = "all", status?: string): Promise<ProjectTransfer[]> => {
|
||||
const q = new URLSearchParams();
|
||||
if (projectId) q.set("project_id", projectId);
|
||||
if (direction) q.set("direction", direction);
|
||||
if (status) q.set("status", status);
|
||||
const qs = q.toString() ? `?${q.toString()}` : "";
|
||||
try {
|
||||
const res = await getData<ProjectTransfer[]>(`/inventory/project-transfers${qs}`);
|
||||
return Array.isArray(res) ? res : [];
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch project transfers:", err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
get: async (id: string, projectId?: string | null): Promise<ProjectTransfer> =>
|
||||
getData<ProjectTransfer>(projectId ? `/inventory/project-transfers/${id}?project_id=${projectId}` : `/inventory/project-transfers/${id}`),
|
||||
create: async (body: ProjectTransferCreateIn, projectId?: string | null): Promise<ProjectTransfer> =>
|
||||
postData<ProjectTransfer>(projectId ? `/inventory/project-transfers?project_id=${projectId}` : `/inventory/project-transfers`, body),
|
||||
update: async (id: string, patch: ProjectTransferUpdateIn, projectId?: string | null): Promise<ProjectTransfer> =>
|
||||
patchData<ProjectTransfer>(projectId ? `/inventory/project-transfers/${id}?project_id=${projectId}` : `/inventory/project-transfers/${id}`, patch),
|
||||
delete: async (id: string, projectId?: string | null): Promise<{ ok: boolean; message: string }> =>
|
||||
deleteData(projectId ? `/inventory/project-transfers/${id}?project_id=${projectId}` : `/inventory/project-transfers/${id}`),
|
||||
onTheWay: async (projectId?: string | null): Promise<ProjectTransfer[]> => {
|
||||
try {
|
||||
const res = await getData<ProjectTransfer[]>(projectId ? `/pm/transfers/on-the-way?project_id=${projectId}` : `/pm/transfers/on-the-way`);
|
||||
return Array.isArray(res) ? res : [];
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch on-the-way transfers:", err);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
receive: async (id: string, body: ProjectTransferReceiveIn, projectId?: string | null): Promise<ProjectTransfer> =>
|
||||
postData<ProjectTransfer>(projectId ? `/inventory/project-transfers/${id}/receive?project_id=${projectId}` : `/inventory/project-transfers/${id}/receive`, body),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user