updating cycle close request feature to get approval from national dashboard instead auto from system

This commit is contained in:
Alberto-Audrix committed 2026-09-09 14:18:39 +07:00
1 parent 3a868cf1db
commit e4dbfd0252
31 files changed
+1322 -182

No files matched your search

+4 -5
View File
@@ -7,7 +7,7 @@ import type {
ManualInput,
} from '../types/api.ts';
import type { DashboardReportPayload, DashboardReportType } from '../types/dashboardReport.ts';
import { cycleDayForDate } from './format.ts';
import { cycleDayForDate, cycleLastDayIndex, resolveCycleBoundDate } from './format.ts';
export type BuildInMemoryReportInput = {
cycle: Cycle;
@@ -48,9 +48,8 @@ function feedAccuracy(iot: number | null, manual: number | null): number | null
export function buildInMemoryReport(input: BuildInMemoryReportInput): DashboardReportPayload {
const { cycle } = input;
// Day numbering stays 0-based (day 0 = start_date), but the report must never
// run past the cycle end date. lastDay = start_date → end_date in days, which
// is total_days - 1 for a contiguous cycle.
const lastDay = cycleDayForDate(cycle.start_date, cycle.end_date);
// run past the approved end (or soft bound while open).
const lastDay = cycleLastDayIndex(cycle);
const endCycleUpTo = Math.min(cycle.total_days, lastDay);
const defaultUpTo =
input.reportType === 'end_cycle'
@@ -202,7 +201,7 @@ export function buildInMemoryReport(input: BuildInMemoryReportInput): DashboardR
return {
cycleId: String(cycle.id),
cycleStart: cycle.start_date,
cycleEnd: cycle.end_date,
cycleEnd: cycle.end_date ?? resolveCycleBoundDate(cycle),
kandangId: input.kandangId,
kandangName: input.kandangName,
area: input.area,
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ChickenCounting, Cycle, KPI, ManualInput } from '../types/api.ts';
import { addDaysIso, cycleDayForDate } from './format.ts';
import { addDaysIso, cycleDayForDate, cycleLastDayIndex } from './format.ts';
export const COUNTING_ACCURACY_WARNING_THRESHOLD = 95;
@@ -38,7 +38,7 @@ function stockAkhirByDate(
const manualByDate = new Map(manualInputs.map((m) => [m.date, m]));
const lastCycleDay = Math.max(0, (cycle.total_days || 1) - 1);
const endDay = cycleDayForDate(cycle.start_date, cycle.end_date);
const endDay = cycleLastDayIndex(cycle);
const maxDay = Math.min(lastCycleDay, endDay);
const result = new Map<string, number>();
+45 -6
View File
@@ -21,13 +21,15 @@ export function formatRatio(value: number, decimals = 3): string {
return truncated.toFixed(decimals);
}
export function formatDateId(iso: string): string {
export function formatDateId(iso: string | null | undefined): string {
if (!iso) return '—';
const date = new Date(`${iso}T00:00:00`);
if (Number.isNaN(date.getTime())) return iso;
return date.toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
}
export function formatDateRange(startIso: string, endIso: string): string {
export function formatDateRange(startIso: string, endIso?: string | null): string {
if (!endIso) return formatDateId(startIso);
return `${formatDateId(startIso)} – ${formatDateId(endIso)}`;
}
@@ -55,9 +57,46 @@ export function inclusiveDayCount(startIso: string, endIso: string): number {
return Math.max(1, cycleDayForDate(startIso, endIso) + 1);
}
/** Inclusive upper date for open cycles (null end_date) without producing NaN day math. */
export function resolveCycleBoundDate(cycle: {
start_date: string;
end_date?: string | null;
soft_end_date?: string | null;
visible_through_date?: string | null;
total_days?: number;
}): string {
if (cycle.end_date) return cycle.end_date;
if (cycle.soft_end_date) return cycle.soft_end_date;
if (cycle.visible_through_date) return cycle.visible_through_date;
if (cycle.total_days != null && cycle.total_days > 0) {
return addDaysIso(cycle.start_date, Math.max(cycle.total_days - 1, 0));
}
return todayIso();
}
/** 0-based last day index from start → bound date (safe when end_date is null). */
export function cycleLastDayIndex(cycle: {
start_date: string;
end_date?: string | null;
soft_end_date?: string | null;
visible_through_date?: string | null;
total_days?: number;
}): number {
return cycleDayForDate(cycle.start_date, resolveCycleBoundDate(cycle));
}
export type CycleStatusUi = 'active' | 'pending_close' | 'closed';
export function resolveCycleStatus(cycle: {
end_date: string;
visible_through_date: string;
}): 'active' | 'closed' {
return cycle.visible_through_date >= cycle.end_date ? 'closed' : 'active';
status?: string;
end_date?: string | null;
visible_through_date?: string;
}): CycleStatusUi {
if (cycle.status === 'pending_close' || cycle.status === 'closed' || cycle.status === 'active') {
return cycle.status;
}
if (cycle.end_date && cycle.visible_through_date && cycle.visible_through_date >= cycle.end_date) {
return 'closed';
}
return 'active';
}