- Introduced `FieldModule` to manage cycles and plans, including read and write controllers. - Created database migrations for `company_settings`, `cycles`, `cycle_weekdays`, `cycle_destinations`, `plans`, `plan_destinations`, `plan_invoices`, and `plan_packing_slips` tables, including constraints and unique indexes. - Developed service and repository layers for handling cycle and plan data operations. - Added unit tests for the cycles and plans services, repositories, and controllers to ensure functionality and correctness. - Updated application module to include the new `FieldModule` for better organization.
94 lines
2.3 KiB
TypeScript
94 lines
2.3 KiB
TypeScript
import type { WeekdayName } from './field-purpose';
|
|
import { isWeekdayName } from './field-purpose';
|
|
|
|
export type WeekdayTemplateInput = {
|
|
readonly startBranchId: string;
|
|
readonly endBranchId: string;
|
|
readonly customerIds: readonly string[];
|
|
};
|
|
|
|
export type WeekdaysInput = Partial<Record<WeekdayName, WeekdayTemplateInput>>;
|
|
|
|
export function isValidCycleNumber(raw: number): boolean {
|
|
return Number.isInteger(raw) && raw >= 1;
|
|
}
|
|
|
|
export function assertCompleteWeekday(
|
|
raw: WeekdayTemplateInput | undefined,
|
|
): WeekdayTemplateInput | undefined {
|
|
if (raw === undefined) {
|
|
return undefined;
|
|
}
|
|
if (
|
|
!raw.startBranchId ||
|
|
!raw.endBranchId ||
|
|
!Array.isArray(raw.customerIds) ||
|
|
raw.customerIds.length === 0
|
|
) {
|
|
throw new Error('incomplete');
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
export function parseWeekdaysInput(raw: unknown): WeekdaysInput {
|
|
if (raw === undefined || raw === null) {
|
|
return {};
|
|
}
|
|
if (typeof raw !== 'object' || Array.isArray(raw)) {
|
|
throw new Error('incomplete');
|
|
}
|
|
const record = raw as Record<string, WeekdayTemplateInput>;
|
|
const result: WeekdaysInput = {};
|
|
for (const [key, value] of Object.entries(record)) {
|
|
if (!isWeekdayName(key)) {
|
|
throw new Error('incomplete');
|
|
}
|
|
const complete = assertCompleteWeekday(value);
|
|
if (complete) {
|
|
result[key] = complete;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** RFC 4180-style record split that preserves commas inside quotes. */
|
|
export function parseCsvRecord(line: string): string[] {
|
|
const cells: string[] = [];
|
|
let current = '';
|
|
let inQuotes = false;
|
|
for (let i = 0; i < line.length; i++) {
|
|
const ch = line[i];
|
|
if (inQuotes) {
|
|
if (ch === '"') {
|
|
if (line[i + 1] === '"') {
|
|
current += '"';
|
|
i += 1;
|
|
} else {
|
|
inQuotes = false;
|
|
}
|
|
} else {
|
|
current += ch;
|
|
}
|
|
} else if (ch === '"') {
|
|
inQuotes = true;
|
|
} else if (ch === ',') {
|
|
cells.push(current.trim());
|
|
current = '';
|
|
} else {
|
|
current += ch;
|
|
}
|
|
}
|
|
cells.push(current.trim());
|
|
return cells;
|
|
}
|
|
|
|
export function isAllowedCsvUpload(file: {
|
|
mimetype: string;
|
|
originalname: string;
|
|
}): boolean {
|
|
return (
|
|
file.mimetype.includes('csv') ||
|
|
file.originalname.toLowerCase().endsWith('.csv')
|
|
);
|
|
}
|