feat(inventory)!: lock warehouses permanently to their parent project
Warehouses now belong strictly and exclusively to the project they were created under; cross-project reassignment is removed end-to-end.
Backend: PATCH /inventory/warehouses/{id} no longer applies project_id — any attempt to move a warehouse to a different project returns 400 with «پروژهی انبار قابل تغییر نیست — انبار برای همیشه به پروژهی والدش تعلق دارد» (sending the current project stays a harmless no-op). The WarehouseUpdate schema documents project_id as accept-only-to-reject; creation still requires project_id and delete is unchanged.
Frontend: the Organization page's per-warehouse project dropdown («انتقال به پروژه دیگر») and its moveWarehouse handler are removed — warehouses render statically inside their project card with only the dashboard link and delete; the warehouses.update API type no longer accepts project_id. WarehousesPage already had no move UI (rename/describe only).
Verified end-to-end against the running stack: rename 200; same-project project_id no-op 200; cross-project reassignment 400 with the guard message; warehouse remains in its original project; test data cleaned up. python compile + api/frontend builds clean.
This commit is contained in:
@@ -1675,7 +1675,8 @@ export const apiV26 = {
|
||||
const d = await postData<Parameters<typeof rawWarehouse>[0]>(`/inventory/warehouses${projectQuery()}`, body);
|
||||
return rawWarehouse(d);
|
||||
},
|
||||
update: async (id: string, body: { name?: string; description?: string | null; project_id?: string }): Promise<Warehouse> => {
|
||||
// project_id عمداً در تایپ نیست — انبار برای همیشه به پروژهی والدش تعلق دارد
|
||||
update: async (id: string, body: { name?: string; description?: string | null }): Promise<Warehouse> => {
|
||||
const d = await patchData<Parameters<typeof rawWarehouse>[0]>(`/inventory/warehouses/${id}${projectQuery()}`, body);
|
||||
return rawWarehouse(d);
|
||||
},
|
||||
|
||||
@@ -148,16 +148,8 @@ export function OrganizationPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const moveWarehouse = async (id: string, projectId: string) => {
|
||||
try {
|
||||
await apiV26.warehouses.update(id, { project_id: projectId });
|
||||
toast.success("انبار منتقل شد ✓");
|
||||
await data.refresh();
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : "خطا در انتقال انبار");
|
||||
}
|
||||
};
|
||||
|
||||
// انبار برای همیشه به پروژهی والدش تعلق دارد — انتقال به پروژهی دیگر وجود ندارد؛
|
||||
// انبار فقط با حذف و ساخت دوباره زیر پروژهی موردنظر جابهجا میشود.
|
||||
const removeWarehouse = async (id: string) => {
|
||||
if (!confirm("انبار حذف شود؟ (انبارهای دارای سابقه تراکنش قابل حذف نیستند)")) return;
|
||||
try {
|
||||
@@ -329,16 +321,6 @@ export function OrganizationPage() {
|
||||
>
|
||||
📊 داشبورد
|
||||
</button>
|
||||
<select
|
||||
className="h-7 rounded-lg border border-line bg-panel px-2 text-[11px]"
|
||||
value={w.project_id}
|
||||
onChange={(e) => void moveWarehouse(w.id, e.target.value)}
|
||||
title="انتقال به پروژه دیگر"
|
||||
>
|
||||
{data.lookup.projects.map((p2) => (
|
||||
<option key={p2.id} value={p2.id}>{p2.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<button className="text-muted hover:text-red" onClick={() => void removeWarehouse(w.id)} title="حذف انبار">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user