diff --git a/prompts/BudgetPrompt.ts b/prompts/BudgetPrompt.ts new file mode 100644 index 0000000..228ada5 --- /dev/null +++ b/prompts/BudgetPrompt.ts @@ -0,0 +1,52 @@ +import { prompt } from "../deps.ts"; +import { writeJsonFile } from "../repositories/FileRepository.ts"; +import ExactBudgetService from "../services/ExactBudgetService.ts"; +import BasePrompt from "./BasePrompt.ts"; +import DebouncedInput from "./DebouncedInput.ts"; + +const enum Prompts { + BUDGET = "budget", +} + +export default class BudgetPrompt extends BasePrompt { + constructor() { + super(); + this.ensureApi(); + } + + async run() { + return prompt([ + { + name: Prompts.BUDGET, + message: "Please select the ledger account to get transactions from:", + type: DebouncedInput, + list: true, + info: true, + suggestions: (await this.exactRepo.getBudgetScenario()).map((b) => + b.Description + ), + debounceTime: 1000, + update: async (input: string) => { + return (await this.exactRepo.getBudgetScenario(input)).map((b) => + b.Description + ); + }, + after: async ({ budget }, next) => { + if (!budget) { + return next(Prompts.BUDGET); + } + + const budgetService = new ExactBudgetService(this.exactRepo); + const budgetData = await budgetService.getBudgetScenario(budget); + + await writeJsonFile( + `budget_${budget}`, + Array.from(budgetData.values()), + ); + + return next(); + }, + }, + ]); + } +} diff --git a/prompts/prompt_main.ts b/prompts/prompt_main.ts index 1c7d859..58cb8c7 100644 --- a/prompts/prompt_main.ts +++ b/prompts/prompt_main.ts @@ -8,6 +8,7 @@ import ExactApiSingleton from "../singletons/ExactApiSingleton.ts"; import SettingRepository from "../repositories/SettingRepository.ts"; import { ExactOnlineServiceError } from "../classes/ExactApi.ts"; import TransactionsPrompt from "./TransactionsPrompt.ts"; +import BudgetPrompt from "./BudgetPrompt.ts"; const enum Prompts { ACTION = "action", @@ -18,6 +19,7 @@ const enum Options { QUERY = "Execute an API query", REPORT_DATA = "Get data for report", TRANSACTIONS = "Get all transactions", + BUDGET = "Get a budget scenario", DIVISION = "Set Exact Online division", SETUP = "Exact Online setup", EXIT = "Exit", @@ -87,6 +89,11 @@ export async function run() { value: Options.REPORT_DATA, disabled: !exactRepo, }, + { + name: Options.BUDGET, + value: Options.BUDGET, + disabled: !exactRepo, + }, { name: Options.DIVISION, value: Options.DIVISION, @@ -106,6 +113,9 @@ export async function run() { case Options.REPORT_DATA: await new ReportDataPrompt().run(); return next(Prompts.ACTION); + case Options.BUDGET: + await new BudgetPrompt().run(); + return next(Prompts.ACTION); case Options.DIVISION: // Cannot select division option when repo is undefined. await selectDivision(exactRepo!); diff --git a/services/ExactBudgetService.ts b/services/ExactBudgetService.ts new file mode 100644 index 0000000..0460b13 --- /dev/null +++ b/services/ExactBudgetService.ts @@ -0,0 +1,46 @@ +import ExactRepository from "../repositories/ExactRepository.ts"; +import { BudgetScenarioValue } from "../repositories/exact_models.d.ts"; + +export default class ExactBudgetService { + #exactRepo; + + constructor(exactRepo: ExactRepository) { + this.#exactRepo = exactRepo; + } + + async getBudgetScenario(budgetDescription: string) { + console.log(`Getting budget '${budgetDescription}'.`); + const budgets = await this.#exactRepo.getBudgetScenario( + budgetDescription, + 1, + ); + + if (!budgets.length) { + throw new TypeError(`Failed to get budget for '${budgetDescription}'.`); + } + + return aggregateBudgetScenarioValues( + await this.#exactRepo.getBudgetScenarioValues(budgets[0].ID), + ); + } +} + +export function aggregateBudgetScenarioValues(budgets: BudgetScenarioValue[]) { + const map: Map> = + new Map(); + + for (const budget of budgets) { + const total = map.get(budget.GLAccountCode); + + if (!total) { + // deno-lint-ignore no-unused-vars + const { ReportingPeriod, ...total } = budget; + map.set(budget.GLAccountCode, { ...total }); + continue; + } + + total.AmountDC += budget.AmountDC; + } + + return map; +}