814bdbbc2a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { Types } from "mongoose";
|
||
import { StockCountModel } from "@/models/stock-count";
|
||
import { StockMovementModel } from "@/models/stock-movement";
|
||
import { signedDelta, type StockMovementKind } from "@/shared/stock";
|
||
import type { Migration } from "./types";
|
||
|
||
type Move = {
|
||
_id: Types.ObjectId;
|
||
householdId: Types.ObjectId;
|
||
productTypeId: Types.ObjectId;
|
||
kind: StockMovementKind;
|
||
date: string;
|
||
quantity: number;
|
||
initial: boolean;
|
||
countId: Types.ObjectId | null;
|
||
createdBy: Types.ObjectId;
|
||
createdAt: Date;
|
||
};
|
||
|
||
/**
|
||
* Faz 11b: sayım oturumları. Faz 11a'da tek ürün sayımıyla oluşan (oturumsuz) sayım
|
||
* düzeltmeleri, o ana kadarki kayıtlardan beklenen miktar hesaplanarak hane + tarih + kişi
|
||
* başına bir sayım oturumuna bağlanır. Başlangıç miktarları sayım sayılmaz.
|
||
*/
|
||
export const migration: Migration = {
|
||
name: "012-stock-counts",
|
||
description: "stockCounts index'leri; oturumsuz sayım düzeltmelerini sayım oturumuna bağla",
|
||
async up() {
|
||
await StockCountModel.createIndexes();
|
||
const orphans = await StockMovementModel.find({
|
||
kind: "count_adjust",
|
||
initial: { $ne: true },
|
||
countId: null,
|
||
deletedAt: null,
|
||
}).lean<Move[]>();
|
||
if (orphans.length === 0) return;
|
||
const products = [...new Set(orphans.map((o) => String(o.productTypeId)))];
|
||
const all = await StockMovementModel.find({
|
||
productTypeId: { $in: products.map((id) => new Types.ObjectId(id)) },
|
||
deletedAt: null,
|
||
}).lean<Move[]>();
|
||
const byProduct = new Map<string, Move[]>();
|
||
for (const m of all) {
|
||
const list = byProduct.get(String(m.productTypeId)) ?? [];
|
||
list.push(m);
|
||
byProduct.set(String(m.productTypeId), list);
|
||
}
|
||
// Her düzeltme için beklenen = önceki hareketlerin toplamı (tarih, oluşturulma sırası).
|
||
const expectedOf = new Map<string, number>();
|
||
for (const list of byProduct.values()) {
|
||
list.sort((a, b) =>
|
||
a.date === b.date
|
||
? a.createdAt.getTime() - b.createdAt.getTime()
|
||
: a.date < b.date
|
||
? -1
|
||
: 1,
|
||
);
|
||
let running = 0;
|
||
for (const m of list) {
|
||
expectedOf.set(String(m._id), running);
|
||
running += signedDelta(m);
|
||
}
|
||
}
|
||
const sessions = new Map<string, Move[]>();
|
||
for (const o of orphans) {
|
||
const key = `${o.householdId}:${o.date}:${o.createdBy}`;
|
||
sessions.set(key, [...(sessions.get(key) ?? []), o]);
|
||
}
|
||
for (const moves of sessions.values()) {
|
||
const first = moves[0]!;
|
||
const count = await StockCountModel.create({
|
||
householdId: first.householdId,
|
||
date: first.date,
|
||
categoryId: null,
|
||
items: moves.map((m) => {
|
||
const expected = expectedOf.get(String(m._id)) ?? 0;
|
||
return {
|
||
productTypeId: m.productTypeId,
|
||
expected,
|
||
actual: expected + m.quantity,
|
||
wasted: 0,
|
||
};
|
||
}),
|
||
createdBy: first.createdBy,
|
||
});
|
||
await StockMovementModel.updateMany(
|
||
{ _id: { $in: moves.map((m) => m._id) } },
|
||
{ $set: { countId: count._id } },
|
||
);
|
||
}
|
||
},
|
||
};
|