Add field management module with database schema and validation
- 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.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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')
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user