@tasenor/common-plugins 1.9.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.eslintrc.js +4 -0
  2. package/.turbo/turbo-fix.log +4 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-version.log +5 -0
  5. package/LICENSE +21 -0
  6. package/README.md +15 -0
  7. package/data/FinnishBalanceSheetReport.mjs +9 -0
  8. package/data/FinnishBalanceSheetReportInvestment.mjs +11 -0
  9. package/data/FinnishBalanceSheetReportLite.mjs +9 -0
  10. package/data/FinnishIncomeStatementReport.mjs +9 -0
  11. package/data/FinnishIncomeStatementReportInvestment.mjs +11 -0
  12. package/data/FinnishIncomeStatementReportLite.mjs +9 -0
  13. package/data/FinnishInvestmentCompany.mjs +5 -0
  14. package/data/FinnishLimitedCompanyComplete.mjs +5 -0
  15. package/data/FinnishLimitedCompanyLite.mjs +9 -0
  16. package/data/IncomeAndExpenses.mjs +64 -0
  17. package/data/README.md +8 -0
  18. package/data/VATFinland.mjs +22 -0
  19. package/data/bin/build_all +11 -0
  20. package/data/lib/utils.mjs +314 -0
  21. package/data/src/Assets Tree - Definitions.tsv +101 -0
  22. package/data/src/Expense Tree - Definitions.tsv +99 -0
  23. package/data/src/Finland VAT - Definitions.tsv +22 -0
  24. package/data/src/FinnishBalanceSheetReport - balance-sheet-detailed-fi.tsv +215 -0
  25. package/data/src/FinnishBalanceSheetReport - balance-sheet-fi.tsv +93 -0
  26. package/data/src/FinnishBalanceSheetReportInvestment - balance-sheet-detailed-fi.tsv +100 -0
  27. package/data/src/FinnishBalanceSheetReportInvestment - balance-sheet-fi.tsv +52 -0
  28. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-en.tsv +21 -0
  29. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-fi.tsv +21 -0
  30. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-lite-en.tsv +21 -0
  31. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-lite-fi.tsv +21 -0
  32. package/data/src/FinnishIncomeStatementReport - income-statement-detailed-fi.tsv +118 -0
  33. package/data/src/FinnishIncomeStatementReport - income-statement-fi.tsv +45 -0
  34. package/data/src/FinnishIncomeStatementReportInvestment - income-statement-detailed-fi.tsv +96 -0
  35. package/data/src/FinnishIncomeStatementReportInvestment - income-statement-fi.tsv +39 -0
  36. package/data/src/FinnishIncomeStatementReportLite - income-statement-lite-en.tsv +23 -0
  37. package/data/src/FinnishIncomeStatementReportLite - income-statement-lite-fi.tsv +23 -0
  38. package/data/src/FinnishInvestmentCompany - fi-EUR.tsv +722 -0
  39. package/data/src/FinnishLimitedCompanyComplete - fi-EUR.tsv +1086 -0
  40. package/data/src/FinnishLimitedCompanyLite - en-EUR.tsv +97 -0
  41. package/data/src/FinnishLimitedCompanyLite - fi-EUR.tsv +99 -0
  42. package/data/src/Income Tree - Definitions.tsv +60 -0
  43. package/data/src/Tax Types - Definitions.tsv +11 -0
  44. package/package.json +51 -0
  45. package/src/CoinAPI/backend/index.ts +102 -0
  46. package/src/CoinbaseImport/backend/CoinbaseHandler.ts +35 -0
  47. package/src/CoinbaseImport/backend/index.ts +24 -0
  48. package/src/CoinbaseImport/backend/rules.json +64 -0
  49. package/src/DocumentCleaner/ui/index.tsx +165 -0
  50. package/src/Euro/ui/index.tsx +27 -0
  51. package/src/Finnish/ui/finnish.json +341 -0
  52. package/src/Finnish/ui/index.tsx +54 -0
  53. package/src/FinnishBalanceSheetReport/backend/balance-sheet-detailed-fi.tsv +215 -0
  54. package/src/FinnishBalanceSheetReport/backend/balance-sheet-fi.tsv +93 -0
  55. package/src/FinnishBalanceSheetReport/backend/index.ts +107 -0
  56. package/src/FinnishBalanceSheetReportInvestment/backend/balance-sheet-investment-detailed-fi.tsv +100 -0
  57. package/src/FinnishBalanceSheetReportInvestment/backend/balance-sheet-investment-fi.tsv +52 -0
  58. package/src/FinnishBalanceSheetReportInvestment/backend/index.ts +107 -0
  59. package/src/FinnishBalanceSheetReportLite/backend/balance-sheet-lite-en.tsv +21 -0
  60. package/src/FinnishBalanceSheetReportLite/backend/balance-sheet-lite-fi.tsv +21 -0
  61. package/src/FinnishBalanceSheetReportLite/backend/index.ts +121 -0
  62. package/src/FinnishIncomeStatementReport/backend/income-statement-detailed-fi.tsv +118 -0
  63. package/src/FinnishIncomeStatementReport/backend/income-statement-fi.tsv +45 -0
  64. package/src/FinnishIncomeStatementReport/backend/index.ts +212 -0
  65. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-detailed-fi.tsv +118 -0
  66. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-fi.tsv +45 -0
  67. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-investment-detailed-fi.tsv +96 -0
  68. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-investment-fi.tsv +39 -0
  69. package/src/FinnishIncomeStatementReportInvestment/backend/index.ts +212 -0
  70. package/src/FinnishIncomeStatementReportLite/backend/income-statement-lite-en.tsv +23 -0
  71. package/src/FinnishIncomeStatementReportLite/backend/income-statement-lite-fi.tsv +23 -0
  72. package/src/FinnishIncomeStatementReportLite/backend/index.ts +210 -0
  73. package/src/FinnishInvestmentCompany/backend/fi-EUR.tsv +722 -0
  74. package/src/FinnishInvestmentCompany/backend/index.ts +46 -0
  75. package/src/FinnishInvestmentCompany/ui/index.tsx +26 -0
  76. package/src/FinnishLimitedCompanyComplete/backend/fi-EUR.tsv +1086 -0
  77. package/src/FinnishLimitedCompanyComplete/backend/index.ts +46 -0
  78. package/src/FinnishLimitedCompanyComplete/ui/index.tsx +26 -0
  79. package/src/FinnishLimitedCompanyLite/backend/en-EUR.tsv +97 -0
  80. package/src/FinnishLimitedCompanyLite/backend/fi-EUR.tsv +99 -0
  81. package/src/FinnishLimitedCompanyLite/backend/index.ts +53 -0
  82. package/src/FinnishLimitedCompanyLite/ui/index.tsx +28 -0
  83. package/src/GitBackup/backend/index.ts +109 -0
  84. package/src/GitBackup/ui/index.tsx +127 -0
  85. package/src/IncomeAndExpenses/backend/assetCodes.json +126 -0
  86. package/src/IncomeAndExpenses/backend/expense.json +190 -0
  87. package/src/IncomeAndExpenses/backend/income.json +120 -0
  88. package/src/IncomeAndExpenses/backend/index.ts +354 -0
  89. package/src/IncomeAndExpenses/backend/taxTypes.json +12 -0
  90. package/src/JournalReport/backend/index.ts +157 -0
  91. package/src/KrakenImport/backend/KrakenHandler.ts +88 -0
  92. package/src/KrakenImport/backend/index.ts +24 -0
  93. package/src/KrakenImport/backend/rules.json +52 -0
  94. package/src/LedgerReport/backend/index.ts +161 -0
  95. package/src/LynxImport/backend/LynxHandler.ts +389 -0
  96. package/src/LynxImport/backend/index.ts +24 -0
  97. package/src/LynxImport/backend/rules.json +412 -0
  98. package/src/NordeaImport/backend/NordeaHandler.ts +44 -0
  99. package/src/NordeaImport/backend/index.ts +24 -0
  100. package/src/NordeaImport/backend/rules.json +4 -0
  101. package/src/NordnetImport/backend/NordnetHandler.ts +78 -0
  102. package/src/NordnetImport/backend/index.ts +24 -0
  103. package/src/NordnetImport/backend/rules.json +271 -0
  104. package/src/Rand/ui/index.tsx +27 -0
  105. package/src/RapidAPI/backend/index.ts +133 -0
  106. package/src/TITOImport/backend/TITOHandler.ts +268 -0
  107. package/src/TITOImport/backend/index.ts +24 -0
  108. package/src/TITOImport/backend/rules.json +4 -0
  109. package/src/TagEditor/ui/index.tsx +510 -0
  110. package/src/USDollar/ui/index.tsx +27 -0
  111. package/src/VAT/ui/index.tsx +572 -0
  112. package/src/VATFinland/backend/index.ts +22 -0
  113. package/src/VATFinland/backend/vat.json +23 -0
  114. package/tsconfig.json +13 -0
@@ -0,0 +1,572 @@
1
+ // eslint-disable-next-line no-use-before-define
2
+ import React from 'react'
3
+ import dayjs from 'dayjs'
4
+ import { Trans } from 'react-i18next'
5
+ import { AccountNumber, Currency, DocumentModelData, EntryModel, haveKnowledge, Tag, TagType } from '@dataplug/tasenor-common'
6
+ import { Localize, SubPanel, IconButton, IconSpacer, ToolPlugin, Money, TagChip, Dialog, Note } from '@dataplug/tasenor-common-ui'
7
+ import { Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'
8
+ import { makeObservable, observable, runInAction } from 'mobx'
9
+
10
+ class VAT extends ToolPlugin {
11
+
12
+ // Observable flag for VAT table dialog.
13
+ showVatTable = false
14
+ // A flag if all requirements are available.
15
+ requirements = false
16
+
17
+ static code = 'VAT'
18
+ static title = 'Value Added Tax'
19
+ static version = '1.0.45'
20
+ static icon = '<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path d="M7.5,4C5.57,4,4,5.57,4,7.5S5.57,11,7.5,11S11,9.43,11,7.5S9.43,4,7.5,4z M7.5,9C6.67,9,6,8.33,6,7.5S6.67,6,7.5,6 S9,6.67,9,7.5S8.33,9,7.5,9z M16.5,13c-1.93,0-3.5,1.57-3.5,3.5s1.57,3.5,3.5,3.5s3.5-1.57,3.5-3.5S18.43,13,16.5,13z M16.5,18 c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5S17.33,18,16.5,18z M5.41,20L4,18.59L18.59,4L20,5.41L5.41,20z"/></g></g></g></svg>'
21
+ static releaseDate = '2022-07-10'
22
+ static use = 'ui'
23
+ static type = 'tool'
24
+ static description = 'General purpose VAT handling. You can configure accounts that automatically inserts either purchase or sales VAT entry. In addition, a tool for paying (receiving) VAT is provided. You need also VAT data plugin for your area.'
25
+
26
+ constructor() {
27
+ super()
28
+
29
+ makeObservable(this, {
30
+ showVatTable: observable
31
+ })
32
+
33
+ this.languages = {
34
+ en: {
35
+ 'icon-summarize-vat-period': 'Collect VAT liabilities/receivables',
36
+ 'icon-view-vat-table': 'View defined VAT categories',
37
+
38
+ 'label-delayedPayableAccount': 'An account for payable VAT transferred from the last period',
39
+ 'label-delayedReceivableAccount': 'An account for receivable VAT transferred from the last period',
40
+ 'label-payableAccount': 'Currently collected VAT payable that is due',
41
+ 'label-purchasesAccount': 'An account for recording VAT from purchases',
42
+ 'label-receivableAccount': 'Currently collected VAT receivable that is due',
43
+ 'label-salesAccount': 'An account for recording VAT from sales',
44
+ },
45
+ fi: {
46
+ 'icon-summarize-vat-period': 'Kerää ALV velat/saatavat',
47
+ 'icon-view-vat-table': 'Selaa määriteltyjä ALV kategorioita',
48
+
49
+ 'label-delayedPayableAccount': 'Tili edelliseltä kaudelta siirretyille ALV maksuille',
50
+ 'label-delayedReceivableAccount': 'Tili edelliseltä kaudelta siirretyille ALV saataville',
51
+ 'label-payableAccount': 'Tämän hetkisten koostetutjen ALV maksujen tili',
52
+ 'label-purchasesAccount': 'Tili ostojen ALV:n kirjaamiseen',
53
+ 'label-receivableAccount': 'Tämän hetkisten koostetutjen ALV saatavien tili',
54
+ 'label-salesAccount': 'Tili myynin ALV:n kirjaamiseen',
55
+
56
+ 'Cumulated VAT by tags': 'Kertynyt ALV tägeittäin',
57
+ 'Cumulated VAT from purchases': 'Kertynyt ALV ostoista',
58
+ 'Cumulated VAT from sales': 'Kertynyt ALV myynnistä',
59
+ 'Current VAT payable': 'Nykyiset ALV velat',
60
+ 'Current VAT receivable': 'Nykyiset ALV saatavat',
61
+ 'Defined VAT Categories': 'Määritellyt ALV kategoriat',
62
+ 'Delayed VAT': 'Siirretty ALV',
63
+ 'Description missing': 'Kuvaus puuttuu',
64
+ 'Entries that has no tags': 'Tapahtumat, joissa ei ole yhtään tägiä',
65
+ 'Payable to add': 'Lisättävä velkaa',
66
+ 'Receivable to add': 'Lisättävä saatavia',
67
+ 'This database does not have configured VAT accounts.': 'Tässä tietokannassa ei ole konfiguroituna ALV-tilejä.',
68
+ 'Value Added Tax': 'Arvonlisävero',
69
+ VAT: 'ALV',
70
+ 'VAT %': 'ALV %',
71
+ 'VAT from purchases': 'ALV ostoista',
72
+ 'VAT from sales': 'ALV myynnistä',
73
+ 'VAT update': 'ALV päivitys',
74
+ 'No VAT data found. Please add VAT data plugin.': 'Tietoa ALV:stä ei löydy. Ole hyvä ja lisää lisäosa, josta ALV tiedot löytyvät.',
75
+ 'No income and expense knowledge base found. Please install a plugin for it.': 'Tulojen ja menojen tietämyskanta puuttuu. Ole hyvä ja lisää lisäosa, josta tiedot löytyvät.'
76
+ }
77
+ }
78
+ }
79
+
80
+ init(catalog) {
81
+ catalog.registerHook('editTransaction', (document, row, column, originalValue) => this.editTransaction(document, row, column, originalValue))
82
+ this.requirements = catalog.isAvailable('IncomeAndExpenses')
83
+ }
84
+
85
+ toolMenu() {
86
+ return [{ title: 'Value Added Tax', disabled: !this.store.periodId }]
87
+ }
88
+
89
+ toolTitle() {
90
+ return 'Value Added Tax'
91
+ }
92
+
93
+ /**
94
+ * Gather documents with non-reconciled VAT entries.
95
+ */
96
+ openVATDocuments() {
97
+ const VAT_SALES_ACCOUNT = this.settings.get('VAT.salesAccount') as AccountNumber
98
+ const VAT_PURCHASES_ACCOUNT = this.settings.get('VAT.purchasesAccount') as AccountNumber
99
+ return this.store.getDocuments([VAT_SALES_ACCOUNT, VAT_PURCHASES_ACCOUNT], (entry) => !entry.data || !entry.data.VAT || (!entry.data.VAT.reconciled && !entry.data.VAT.ignore))
100
+ }
101
+
102
+ /**
103
+ * Calculate sum of unprocessed payable and receivable for VAT.
104
+ * @return {Object} An object with `sales` and `purchases`.
105
+ */
106
+ VATSummary() {
107
+ let sales = 0
108
+ let purchases = 0
109
+
110
+ const VAT_SALES_ACCOUNT = this.settings.get('VAT.salesAccount')
111
+ const VAT_PURCHASES_ACCOUNT = this.settings.get('VAT.purchasesAccount')
112
+
113
+ this.openVATDocuments().forEach((doc) => {
114
+ doc.entries.forEach((entry) => {
115
+ const acc = entry.account.number
116
+ if (acc === VAT_SALES_ACCOUNT) {
117
+ sales += entry.total
118
+ }
119
+ if (acc === VAT_PURCHASES_ACCOUNT) {
120
+ purchases += entry.total
121
+ }
122
+ })
123
+ })
124
+ return { sales, purchases }
125
+ }
126
+
127
+ /**
128
+ * Icons and VAT table viewer.
129
+ * @param index
130
+ * @returns
131
+ */
132
+ toolTopPanel() {
133
+ const knowledge = haveKnowledge()
134
+ const table = this.requirements ? knowledge.vatTable() : []
135
+ const { store } = this
136
+ const VAT = store.period ? this.VATSummary() : { sales: 0, purchases: 0 }
137
+ return (
138
+ <>
139
+ <IconButton id="Summarize VAT" key="button-vat" disabled={!this.requirements || (!VAT.sales && !VAT.purchases)} onClick={() => this.createVATEntry()} title="summarize-vat-period" icon="summarize" />
140
+ <IconSpacer/>
141
+ <IconButton id="View VAT Table" title="view-vat-table" key="view-vat-table" icon="view" onClick={() => {
142
+ runInAction(() => {
143
+ this.showVatTable = true
144
+ })
145
+ }}/>
146
+ <Dialog
147
+ title={<Trans>Defined VAT Categories</Trans>}
148
+ isVisible={this.showVatTable}
149
+ okOnly
150
+ onClose={() => {
151
+ runInAction(() => {
152
+ this.showVatTable = false
153
+ })
154
+ }
155
+ }>
156
+ <TableContainer>
157
+ <Table className="TransactionTable" size="medium" padding="none">
158
+ <TableHead>
159
+ <TableRow>
160
+ <TableCell variant="head"><Trans>Category</Trans></TableCell>
161
+ <TableCell variant="head"><Trans>VAT</Trans></TableCell>
162
+ </TableRow>
163
+ </TableHead>
164
+ <TableBody>
165
+
166
+ {table.length < 1 && <TableRow><TableCell colSpan={2}><Trans>No VAT data found. Please add VAT data plugin.</Trans></TableCell></TableRow>}
167
+
168
+ {table.map(line => <TableRow key={line.id}>
169
+ <TableCell sx={{ paddingLeft: line.level * 2 }}>
170
+ <Trans>{line.name}</Trans>
171
+ </TableCell>
172
+ <TableCell>
173
+ {line.value}%
174
+ </TableCell>
175
+ </TableRow>)}
176
+ </TableBody>
177
+ </Table>
178
+ </TableContainer>
179
+ </Dialog>
180
+ </>
181
+ )
182
+ }
183
+
184
+ toolMainPanel() {
185
+ if (!this.requirements) {
186
+ return <Note>
187
+ <Trans>You must install Income and Expenses plugin.</Trans>
188
+ </Note>
189
+ }
190
+
191
+ const { store } = this
192
+ if (!store.period) {
193
+ return <></>
194
+ }
195
+
196
+ const VAT = this.VATSummary()
197
+
198
+ const VAT_SALES_ACCOUNT = this.settings.get('VAT.salesAccount') as AccountNumber
199
+ const VAT_PURCHASES_ACCOUNT = this.settings.get('VAT.purchasesAccount') as AccountNumber
200
+ const VAT_RECEIVABLE_ACCOUNT = this.settings.get('VAT.receivableAccount') as AccountNumber
201
+ const VAT_PAYABLE_ACCOUNT = this.settings.get('VAT.payableAccount') as AccountNumber
202
+ const VAT_DELAYED_RECEIVABLE_ACCOUNT = this.settings.get('VAT.delayedReceivableAccount') as AccountNumber
203
+ const VAT_DELAYED_PAYABLE_ACCOUNT = this.settings.get('VAT.delayedPayableAccount') as AccountNumber
204
+ const VAT_STATEMENT_TAG_TYPES = this.settings.get('VAT.statementTagTypes') as TagType[]
205
+
206
+ const vatSalesAccount = this.store.database.getAccountByNumber(VAT_SALES_ACCOUNT)
207
+ const vatPurchasesAccount = this.store.database.getAccountByNumber(VAT_PURCHASES_ACCOUNT)
208
+ const vatPayableAccount = this.store.database.getAccountByNumber(VAT_PAYABLE_ACCOUNT)
209
+ const vatReceivableAccount = this.store.database.getAccountByNumber(VAT_RECEIVABLE_ACCOUNT)
210
+ const vatDelayedPayableAccount = this.store.database.getAccountByNumber(VAT_DELAYED_PAYABLE_ACCOUNT)
211
+ const vatDelayedReceivableAccount = this.store.database.getAccountByNumber(VAT_DELAYED_RECEIVABLE_ACCOUNT)
212
+
213
+ if (!vatSalesAccount || !vatPurchasesAccount || !vatPayableAccount || !vatReceivableAccount || !vatDelayedReceivableAccount || !vatDelayedPayableAccount) {
214
+ return (
215
+ <div>
216
+ <SubPanel>
217
+ <Trans>This database does not have configured VAT accounts.</Trans>
218
+ </SubPanel>
219
+ </div>)
220
+ }
221
+
222
+ const payable = store.period.getBalanceByNumber(VAT_PAYABLE_ACCOUNT)
223
+ const receivable = store.period.getBalanceByNumber(VAT_RECEIVABLE_ACCOUNT)
224
+ const payableDelayed = store.period.getBalanceByNumber(VAT_DELAYED_PAYABLE_ACCOUNT)
225
+ const receivableDelayed = store.period.getBalanceByNumber(VAT_DELAYED_RECEIVABLE_ACCOUNT)
226
+ const openVATDocuments = store.period ? this.openVATDocuments() : []
227
+
228
+ // Split by tags.
229
+ const VAT_TAG_TYPES: TagType[] = VAT_STATEMENT_TAG_TYPES || []
230
+ const validTags = new Set(
231
+ Object.values(this.store.database.tagsByTag)
232
+ .filter(tag => VAT_TAG_TYPES.includes(tag.type as TagType))
233
+ .map(tag => tag.tag)
234
+ )
235
+
236
+ const vatByTag: Record<Tag, number> = {}
237
+ let vatByNoTag = null
238
+ let hasTags = false
239
+
240
+ const addVatByTags = (tags, amount) => {
241
+ tags = tags.filter(tag => validTags.has(tag))
242
+ if (!tags.length) {
243
+ vatByNoTag = (vatByNoTag || 0) + amount
244
+ return
245
+ }
246
+ hasTags = true
247
+ const share = (amount >= 0 ? Math.ceil(amount / tags.length) : Math.floor(amount / tags.length))
248
+ tags.forEach((tag) => {
249
+ const delta = (amount >= 0 ? Math.min(amount, share) : Math.max(amount, share))
250
+ vatByTag[tag] = (vatByTag[tag] || 0) + delta
251
+ amount -= delta
252
+ })
253
+ }
254
+
255
+ for (const doc of openVATDocuments) {
256
+ for (const entry of doc.entries) {
257
+ if (entry.account_id === vatSalesAccount.id || entry.account_id === vatPurchasesAccount.id) {
258
+ addVatByTags(entry.tagNames, entry.total)
259
+ }
260
+ }
261
+ }
262
+
263
+ const currency: Currency = this.settings.get('currency') as Currency
264
+
265
+ return (
266
+ <div>
267
+ <SubPanel>
268
+ <b>
269
+ <Link onClick={() => this.goto(vatReceivableAccount.getUrl())}>
270
+ <Trans>Current VAT receivable</Trans>: <Money currency={currency} cents={(receivable && receivable.total) || 0}></Money>
271
+ </Link>
272
+ &nbsp;
273
+ {
274
+ receivableDelayed && receivableDelayed.total !== 0 &&
275
+ <Link onClick={() => this.goto(vatReceivableAccount.getUrl())}>
276
+ (<Trans>Delayed VAT</Trans>: <Money currency={currency} cents={receivableDelayed.total || 0}></Money>)
277
+ </Link>
278
+ }
279
+ <br/>
280
+ <Link onClick={() => this.goto(vatPayableAccount.getUrl())}>
281
+ <Trans>Current VAT payable</Trans>: <Money currency={currency} cents={(payable && payable.total) || 0}></Money>
282
+ </Link>
283
+ &nbsp;
284
+ {
285
+ payableDelayed && payableDelayed.total !== 0 &&
286
+ <Link onClick={() => this.goto(vatDelayedPayableAccount.getUrl())}>
287
+ (<Trans>Delayed VAT</Trans>: <Money currency={currency} cents={payableDelayed.total || 0}></Money>)
288
+ </Link>
289
+ }
290
+ <br/>
291
+ <br/>
292
+ <Link onClick={() => this.goto(vatPurchasesAccount.getUrl())}>
293
+ <Trans>Cumulated VAT from purchases</Trans>: <Money currency={currency} cents={VAT.purchases}></Money>
294
+ </Link>
295
+ <br/>
296
+ <Link onClick={() => this.goto(vatSalesAccount.getUrl())}>
297
+ <Trans>Cumulated VAT from sales</Trans>: <Money currency={currency} cents={VAT.sales}></Money>
298
+ </Link>
299
+ <br/>
300
+ <Trans>{VAT.sales + VAT.purchases < 0 ? 'Payable to add' : 'Receivable to add'}</Trans>: <Money currency={currency} cents={VAT.sales + VAT.purchases}></Money><br/>
301
+ </b>
302
+ </SubPanel>
303
+ { hasTags &&
304
+ <SubPanel>
305
+ <b><Trans>Cumulated VAT by tags</Trans>:</b>
306
+ <table>
307
+ <tbody>
308
+ {Object.entries(vatByTag).map(([tag, amount]) => <tr key={tag}>
309
+ <td width="48px"><TagChip tag={this.store.database.getTag(tag as Tag)} /></td>
310
+ <td width="48px">{tag}</td>
311
+ <td>{this.store.database.getTag(tag as Tag).name}</td>
312
+ <td><Money currency={currency} cents={amount}></Money></td>
313
+ </tr>)}
314
+ {vatByNoTag && <tr>
315
+ <td></td>
316
+ <td></td>
317
+ <td><Trans>Entries that has no tags</Trans></td>
318
+ <td><Money currency={currency} cents={vatByNoTag}></Money></td>
319
+ </tr>}
320
+ </tbody>
321
+ </table>
322
+ </SubPanel>
323
+ }
324
+ { openVATDocuments.length > 0 &&
325
+ <SubPanel>
326
+ {openVATDocuments.map((doc) => {
327
+ return <div key={doc.id}>
328
+ <Localize date={doc.date} />
329
+ <br/>
330
+ { doc.entries.map((entry) => {
331
+ return <div key={entry.id}>
332
+ <b> {entry.account.toString()} </b>
333
+ {entry.text || <span style={{ color: 'red' }}><Trans>Description missing</Trans></span>}
334
+ <span> </span><Money currency={currency} cents={entry.total}></Money>
335
+ </div>
336
+ })
337
+ }
338
+ <br />
339
+ </div>
340
+ })}
341
+ </SubPanel>
342
+ }
343
+ </div>
344
+ )
345
+ }
346
+
347
+ /**
348
+ * Combine unprocessed VAT to new payable or receivable entry.
349
+ */
350
+ async createVATEntry() {
351
+
352
+ const VAT_SALES_ACCOUNT: AccountNumber = this.settings.get('VAT.salesAccount') as AccountNumber
353
+ const VAT_PURCHASES_ACCOUNT: AccountNumber = this.settings.get('VAT.purchasesAccount') as AccountNumber
354
+ const VAT_RECEIVABLE_ACCOUNT: AccountNumber = this.settings.get('VAT.receivableAccount') as AccountNumber
355
+ const VAT_PAYABLE_ACCOUNT: AccountNumber = this.settings.get('VAT.payableAccount') as AccountNumber
356
+ const VAT_DELAYED_RECEIVABLE_ACCOUNT: AccountNumber = this.settings.get('VAT.delayedReceivableAccount') as AccountNumber
357
+ const VAT_DELAYED_PAYABLE_ACCOUNT: AccountNumber = this.settings.get('VAT.delayedPayableAccount') as AccountNumber
358
+
359
+ // Collect entries.
360
+ let sales = 0
361
+ let purchases = 0
362
+ const entries: EntryModel[] = []
363
+ for (const doc of this.openVATDocuments()) {
364
+ for (const entry of doc.entries) {
365
+ const acc = entry.account.number
366
+ if (acc === VAT_SALES_ACCOUNT) {
367
+ sales += entry.total
368
+ entries.push(entry)
369
+ }
370
+ if (acc === VAT_PURCHASES_ACCOUNT) {
371
+ purchases += entry.total
372
+ entries.push(entry)
373
+ }
374
+ }
375
+ }
376
+
377
+ if (!this.store.period) {
378
+ throw new Error('Canont create VAT entries without period.')
379
+ }
380
+
381
+ // Create new VAT payable/receivable.
382
+ let date = dayjs().format('YYYY-MM-DD')
383
+ let isDelayed = false
384
+ if (date > this.store.period.end_date) {
385
+ isDelayed = true
386
+ date = this.store.period.end_date
387
+ }
388
+
389
+ const doc: DocumentModelData = {
390
+ period_id: this.store.period.id,
391
+ date,
392
+ entries: []
393
+ }
394
+
395
+ // Just to keep compiler happy.
396
+ if (!doc.entries) {
397
+ return
398
+ }
399
+
400
+ if (sales) {
401
+ doc.entries.push({
402
+ number: VAT_SALES_ACCOUNT,
403
+ amount: -sales,
404
+ data: { VAT: { ignore: true } },
405
+ description: this.t('VAT update')
406
+ })
407
+ }
408
+ if (purchases) {
409
+ doc.entries.push({
410
+ number: VAT_PURCHASES_ACCOUNT,
411
+ amount: -purchases,
412
+ data: { VAT: { ignore: true } },
413
+ description: this.t('VAT update')
414
+ })
415
+ }
416
+ // Add it to the receivable or to the payable VAT.
417
+ if (sales + purchases < 0) {
418
+ doc.entries.push({
419
+ number: isDelayed ? VAT_DELAYED_PAYABLE_ACCOUNT : VAT_PAYABLE_ACCOUNT,
420
+ amount: sales + purchases,
421
+ data: { VAT: { ignore: true } },
422
+ description: this.t('VAT update')
423
+ })
424
+ }
425
+ if (sales + purchases > 0) {
426
+ doc.entries.push({
427
+ number: isDelayed ? VAT_DELAYED_RECEIVABLE_ACCOUNT : VAT_RECEIVABLE_ACCOUNT,
428
+ amount: sales + purchases,
429
+ data: { VAT: { ignore: true } },
430
+ description: this.t('VAT update')
431
+ })
432
+ }
433
+
434
+ await this.store.period.createDocument(doc)
435
+
436
+ // Mark entries as reconciled.
437
+ runInAction(async () => {
438
+ for (const entry of entries) {
439
+ if (entry.data) {
440
+ entry.data.VAT = entry.data.VAT || {}
441
+ entry.data.VAT.reconciled = true
442
+ }
443
+ await entry.save()
444
+ }
445
+ })
446
+
447
+ await this.store.fetchBalances()
448
+ }
449
+
450
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
451
+ async editTransaction(document, row, column, originalValue) {
452
+
453
+ // If editing date, no interest.
454
+ if (row === null) {
455
+ return
456
+ }
457
+
458
+ // Dig some usable references.
459
+ const entry = document.entries[row]
460
+ const date = document.date || new Date()
461
+ const account = entry.account
462
+ const knowledge = haveKnowledge()
463
+
464
+ // Helper to create VAT entry, if needed VAT percentage found.
465
+ const checkAndAddVat = async (vatAccountNumber, isExpense) => {
466
+ if (!account || !vatAccountNumber) {
467
+ return
468
+ }
469
+
470
+ const vatPercentage = knowledge.vat(account.data.code, date)
471
+ const text = `${this.t('VAT')} ${vatPercentage}%`
472
+ const vatAccount = this.store.database.getAccountByNumber(vatAccountNumber)
473
+
474
+ if (vatPercentage) {
475
+ if (document.entries.filter(e => e.account_id === vatAccount.id).length === 0) {
476
+ await runInAction(async () => {
477
+ const vatAmount = Math.round(entry.amount - entry.amount / (1 + vatPercentage / 100))
478
+ const vat = {
479
+ id: vatAccount.id,
480
+ amount: isExpense ? vatAmount : -vatAmount,
481
+ description: `${entry.description} ${text}`.trim()
482
+ }
483
+ entry.amount -= vatAmount
484
+ await entry.save()
485
+ await document.createEntry(vat)
486
+ })
487
+ return new Set([vatAccountNumber])
488
+ }
489
+ } else {
490
+ // Check that the reason is not related to missing plugin.
491
+ const { income, vat } = knowledge.count()
492
+ if (!vat) {
493
+ this.store.addError('No VAT data found. Please add VAT data plugin.')
494
+ }
495
+ if (!income) {
496
+ this.store.addError('No income and expense knowledge base found. Please install a plugin for it.')
497
+ }
498
+ }
499
+ }
500
+
501
+ // Automatically add VAT entry for purchases.
502
+ if (column === 2) {
503
+ if (account.type === 'EXPENSE') {
504
+ return await checkAndAddVat(this.getSetting('purchasesAccount'), true)
505
+ }
506
+ // Automatically add VAT entry for sales.
507
+ } else if (column === 3) {
508
+ if (account.type === 'REVENUE') {
509
+ return await checkAndAddVat(this.getSetting('salesAccount'), false)
510
+ }
511
+ }
512
+ }
513
+
514
+ getSettings() {
515
+ // Should actually also remove defaults given elsewhere
516
+ return {
517
+ type: 'flat',
518
+ elements: [
519
+ {
520
+ type: 'account',
521
+ name: 'delayedPayableAccount',
522
+ filter: {
523
+ type: 'LIABILITY'
524
+ }
525
+ },
526
+ {
527
+ type: 'account',
528
+ name: 'delayedReceivableAccount',
529
+ filter: {
530
+ type: 'ASSET'
531
+ }
532
+ },
533
+ {
534
+ type: 'account',
535
+ name: 'payableAccount',
536
+ filter: {
537
+ type: 'LIABILITY'
538
+ }
539
+ },
540
+ {
541
+ type: 'account',
542
+ name: 'purchasesAccount',
543
+ filter: {
544
+ type: 'LIABILITY'
545
+ }
546
+ },
547
+ {
548
+ type: 'account',
549
+ name: 'receivableAccount',
550
+ filter: {
551
+ type: 'ASSET'
552
+ }
553
+ },
554
+ {
555
+ type: 'account',
556
+ name: 'salesAccount',
557
+ filter: {
558
+ type: 'LIABILITY'
559
+ }
560
+ },
561
+ {
562
+ type: 'button',
563
+ label: 'Save',
564
+ actions: {
565
+ onClick: { type: 'saveSettings', plugin: 'VAT' }
566
+ }
567
+ }
568
+ ]
569
+ }
570
+ }
571
+ }
572
+ export default VAT
@@ -0,0 +1,22 @@
1
+ import { DataPlugin } from '@dataplug/tasenor-common-node'
2
+ import { PluginCode, Version } from '@dataplug/tasenor-common'
3
+
4
+ class VATFinland extends DataPlugin {
5
+ constructor() {
6
+ super('vat')
7
+
8
+ this.code = 'VATFinland'as PluginCode
9
+ this.title = 'VAT Data for Finland'
10
+ this.version = '1.0.12' as Version
11
+ this.icon = '<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path d="M7.5,4C5.57,4,4,5.57,4,7.5S5.57,11,7.5,11S11,9.43,11,7.5S9.43,4,7.5,4z M7.5,9C6.67,9,6,8.33,6,7.5S6.67,6,7.5,6 S9,6.67,9,7.5S8.33,9,7.5,9z M16.5,13c-1.93,0-3.5,1.57-3.5,3.5s1.57,3.5,3.5,3.5s3.5-1.57,3.5-3.5S18.43,13,16.5,13z M16.5,18 c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5S17.33,18,16.5,18z M5.41,20L4,18.59L18.59,4L20,5.41L5.41,20z"/></g></g></g></svg>'
12
+ this.releaseDate = '2022-07-23'
13
+ this.use = 'backend'
14
+ this.type = 'data'
15
+ this.description = 'This plugin provides data needed for VAT handling in Finland.'
16
+
17
+ this.languages = {
18
+ }
19
+ }
20
+ }
21
+
22
+ export default VATFinland
@@ -0,0 +1,23 @@
1
+ [
2
+ {
3
+ "from": "2013-01-01",
4
+ "to": null,
5
+ "percentage": {
6
+ "INCOME": 24,
7
+ "EXPENSE": 24,
8
+ "PERSONNEL_MEALS": 14,
9
+ "TICKET": 10,
10
+ "TAXI": 10,
11
+ "ACCOMMODATION": 10,
12
+ "BOOK": 10,
13
+ "NEWSPAPERS": 10,
14
+ "POSTAGE_STAMPS": 0,
15
+ "BANKING_FEE": 0,
16
+ "CAPITAL": 0,
17
+ "SPECIAL_EXPENSE_CASES": 0,
18
+ "SPECIAL_INCOME_CASES": 0,
19
+ "INVEST": 0,
20
+ "NON_EU_SALES": 0
21
+ }
22
+ }
23
+ ]
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "tsconfig/tsx.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ },
6
+ "include": [
7
+ "./src/**/*",
8
+ "./tests/specs/*"
9
+ ],
10
+ "exclude": [
11
+ "./node_modules"
12
+ ]
13
+ }