diff --git a/backend/README_TAX.md b/backend/README_TAX.md new file mode 100644 index 0000000..e6e3ebf --- /dev/null +++ b/backend/README_TAX.md @@ -0,0 +1,23 @@ +# Falukant Tax Migration & Configuration + +This project now supports a per-region sales tax (`tax_percent`) for Falukant. + +Migration +- A SQL migration was added: `backend/migrations/20260101000000-add-tax-percent-to-region.cjs`. +- It adds `tax_percent` numeric NOT NULL DEFAULT 7 to `falukant_data.region`. + +Runtime configuration +- If you want taxes to be forwarded to a treasury account, set environment variable `TREASURY_FALUKANT_USER_ID` to a valid `falukant_user.id`. +- If `TREASURY_FALUKANT_USER_ID` is not set, taxes will be calculated and currently not forwarded to any account. + +Implementation notes +- Backend service `sellProduct` and `sellAllProducts` now compute tax per-region and credit net to seller and tax to treasury (if configured). +- Tax arithmetic uses rounding to 2 decimals. The current implementation performs two separate DB calls (seller, treasury). For strict ledger atomicity consider implementing DB-side booking. + +Cumulative tax behavior +- The system now sums `tax_percent` from the sale region and all ancestor regions (recursive up the region tree). This allows defining different tax rates on up to 6 region levels and summing them for final tax percent. +- To avoid reducing seller net by taxes, sale prices are inflated by factor = 1 / (1 - cumulativeTax/100). This way the seller receives the original net and the tax is collected separately. + +Testing +- After running the migration, test with a small sale and verify `falukant_log.moneyflow` entries for seller and treasury. + diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index a07f01d..848b726 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -145,6 +145,7 @@ class FalukantController { this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds)); this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId)); + this.getBranchTaxes = this._wrapWithUser((userId, req) => this.service.getBranchTaxes(userId, req.params.branchId)); this.getProductPriceInRegion = this._wrapWithUser((userId, req) => { const productId = parseInt(req.query.productId, 10); const regionId = parseInt(req.query.regionId, 10); diff --git a/backend/migrations/20260101000000-add-tax-percent-to-region.cjs b/backend/migrations/20260101000000-add-tax-percent-to-region.cjs new file mode 100644 index 0000000..671d545 --- /dev/null +++ b/backend/migrations/20260101000000-add-tax-percent-to-region.cjs @@ -0,0 +1,17 @@ +"use strict"; + +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.sequelize.query(` + ALTER TABLE IF EXISTS falukant_data.region + ADD COLUMN IF NOT EXISTS tax_percent numeric NOT NULL DEFAULT 7; + `); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.sequelize.query(` + ALTER TABLE IF EXISTS falukant_data.region + DROP COLUMN IF EXISTS tax_percent; + `); + } +}; diff --git a/backend/migrations/20260101000002-add-neutral-sell-costs-to-product.cjs b/backend/migrations/20260101000002-add-neutral-sell-costs-to-product.cjs new file mode 100644 index 0000000..23a3d7f --- /dev/null +++ b/backend/migrations/20260101000002-add-neutral-sell-costs-to-product.cjs @@ -0,0 +1,50 @@ +"use strict"; + +module.exports = { + async up(queryInterface, Sequelize) { + // 1) add backup column for original sell_cost (idempotent) + await queryInterface.sequelize.query(` + ALTER TABLE IF EXISTS falukant_type.product + ADD COLUMN IF NOT EXISTS original_sell_cost numeric; + `); + + // 2) if original_sell_cost is not set, copy current sell_cost into it + await queryInterface.sequelize.query(` + UPDATE falukant_type.product + SET original_sell_cost = sell_cost + WHERE original_sell_cost IS NULL; + `); + + // 3) compute max cumulative tax across regions and increase sell_cost accordingly + // We use the maximum cumulative tax (worst-case) so sellers are neutral across regions. + // Formula: neutral_sell = CEIL(original_sell_cost * (1 / (1 - max_total/100))) + await queryInterface.sequelize.query(` + WITH RECURSIVE ancestors AS ( + SELECT id AS start_id, id, parent_id, tax_percent FROM falukant_data.region + UNION ALL + SELECT a.start_id, r.id, r.parent_id, r.tax_percent + FROM falukant_data.region r + JOIN ancestors a ON r.id = a.parent_id + ), totals AS ( + SELECT start_id, COALESCE(SUM(tax_percent), 0) AS total FROM ancestors GROUP BY start_id + ), mm AS ( + SELECT COALESCE(MAX(total),0) AS max_total FROM totals + ) + UPDATE falukant_type.product + SET sell_cost = CEIL(original_sell_cost * (CASE WHEN (1 - mm.max_total/100) <= 0 THEN 1 ELSE (1 / (1 - mm.max_total/100)) END)) + FROM mm + WHERE original_sell_cost IS NOT NULL; + `); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.sequelize.query(` + ALTER TABLE IF EXISTS falukant_type.product + DROP COLUMN IF EXISTS sell_cost_min_neutral; + `); + await queryInterface.sequelize.query(` + ALTER TABLE IF EXISTS falukant_type.product + DROP COLUMN IF EXISTS sell_cost_max_neutral; + `); + } +}; diff --git a/backend/models/falukant/data/region.js b/backend/models/falukant/data/region.js index 133fbe3..48842ed 100644 --- a/backend/models/falukant/data/region.js +++ b/backend/models/falukant/data/region.js @@ -30,6 +30,13 @@ RegionData.init({ allowNull: true, defaultValue: {} } + , + taxPercent: { + type: DataTypes.DECIMAL, + allowNull: false, + defaultValue: 7, + field: 'tax_percent' + } }, { sequelize, modelName: 'RegionData', diff --git a/backend/models/falukant/type/product.js b/backend/models/falukant/type/product.js index 6ba2f1b..7393139 100644 --- a/backend/models/falukant/type/product.js +++ b/backend/models/falukant/type/product.js @@ -16,6 +16,17 @@ ProductType.init({ sellCost: { type: DataTypes.INTEGER, allowNull: false} + , + sellCostMinNeutral: { + type: DataTypes.DECIMAL, + allowNull: true, + field: 'sell_cost_min_neutral' + }, + sellCostMaxNeutral: { + type: DataTypes.DECIMAL, + allowNull: true, + field: 'sell_cost_max_neutral' + } }, { sequelize, modelName: 'ProductType', diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 802b535..1d36092 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -73,6 +73,7 @@ router.post('/politics/open', falukantController.applyForElections); router.get('/cities', falukantController.getRegions); router.get('/products/price-in-region', falukantController.getProductPriceInRegion); router.get('/products/prices-in-cities', falukantController.getProductPricesInCities); +router.get('/branches/:branchId/taxes', falukantController.getBranchTaxes); router.get('/vehicles/types', falukantController.getVehicleTypes); router.post('/vehicles', falukantController.buyVehicles); router.get('/vehicles', falukantController.getVehicles); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 03136e1..ed03446 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -110,6 +110,29 @@ async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPe return min + (max - min) * (knowledgeFactor / 100); } + // Sum tax_percent for a region and its ancestors (upwards). Returns numeric percent (e.g. 7.5) + async function getCumulativeTaxPercent(regionId) { + if (!regionId) return 0; + const rows = await sequelize.query( + `WITH RECURSIVE ancestors AS ( + SELECT id, parent_id, tax_percent + FROM falukant_data.region r + WHERE id = :id + UNION ALL + SELECT reg.id, reg.parent_id, reg.tax_percent + FROM falukant_data.region reg + JOIN ancestors a ON reg.id = a.parent_id + ) + SELECT COALESCE(SUM(tax_percent),0) AS total FROM ancestors;`, + { + replacements: { id: regionId }, + type: sequelize.QueryTypes.SELECT + } + ); + const val = rows?.[0]?.total ?? 0; + return parseFloat(val) || 0; + } + function calculateMarriageCost(titleOfNobility, age) { const minTitle = 1; const adjustedTitle = titleOfNobility - minTitle + 1; @@ -1502,10 +1525,28 @@ class FalukantService extends BaseService { if (available < quantity) throw new Error('Not enough inventory available'); const item = inventory[0].productType; const knowledgeVal = item.knowledges?.[0]?.knowledge || 0; - const pricePerUnit = await calcRegionalSellPrice(item, knowledgeVal, branch.regionId); - const revenue = quantity * pricePerUnit; - const moneyResult = await updateFalukantUserMoney(user.id, revenue, 'Product sale', user.id); - if (!moneyResult.success) throw new Error('Failed to update money'); + const pricePerUnit = await calcRegionalSellPrice(item, knowledgeVal, branch.regionId); + + // compute cumulative tax (region + ancestors) and inflate price so seller net is unchanged + const cumulativeTax = await getCumulativeTaxPercent(branch.regionId); + const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100)); + const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100; + const revenue = quantity * adjustedPricePerUnit; + + // compute tax and net + const taxValue = Math.round((revenue * cumulativeTax / 100) * 100) / 100; + const net = Math.round((revenue - taxValue) * 100) / 100; + + // Book net to seller + const moneyResult = await updateFalukantUserMoney(user.id, net, `Product sale (net)`, user.id); + if (!moneyResult.success) throw new Error('Failed to update money for seller'); + + // Book tax to treasury (if configured) + const treasuryId = process.env.TREASURY_FALUKANT_USER_ID; + if (treasuryId && taxValue > 0) { + const taxResult = await updateFalukantUserMoney(parseInt(treasuryId, 10), taxValue, `Sales tax (${cumulativeTax}%)`, user.id); + if (!taxResult.success) throw new Error('Failed to update money for treasury'); + } let remaining = quantity; for (const inv of inventory) { if (inv.quantity <= remaining) { @@ -1573,16 +1614,41 @@ class FalukantService extends BaseService { const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0; const regionId = item.stock.branch.regionId; const pricePerUnit = await calcRegionalSellPrice(item.productType, knowledgeVal, regionId); - total += item.quantity * pricePerUnit; + const cumulativeTax = await getCumulativeTaxPercent(regionId); + const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100)); + const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100; + total += item.quantity * adjustedPricePerUnit; await this.addSellItem(item.stock.branch.id, falukantUser.id, item.productType.id, item.quantity); } + // compute tax per region (using cumulative tax per region) and aggregate + let totalTax = 0; + for (const item of inventory) { + const regionId = item.stock.branch.regionId; + const region = await RegionData.findOne({ where: { id: regionId } }); + const cumulativeTax = await getCumulativeTaxPercent(regionId); + const pricePerUnit = await calcRegionalSellPrice(item.productType, item.productType.knowledges?.[0]?.knowledge || 0, regionId); + const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100)); + const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100; + const itemRevenue = item.quantity * adjustedPricePerUnit; + const itemTax = Math.round((itemRevenue * cumulativeTax / 100) * 100) / 100; + totalTax += itemTax; + } + + const totalNet = Math.round((total - totalTax) * 100) / 100; + const moneyResult = await updateFalukantUserMoney( falukantUser.id, - total, - 'Sell all products', + totalNet, + 'Sell all products (net)', falukantUser.id ); - if (!moneyResult.success) throw new Error('Failed to update money'); + if (!moneyResult.success) throw new Error('Failed to update money for seller'); + + const treasuryId = process.env.TREASURY_FALUKANT_USER_ID; + if (treasuryId && totalTax > 0) { + const taxResult = await updateFalukantUserMoney(parseInt(treasuryId, 10), Math.round(totalTax * 100) / 100, `Sales tax (aggregate)`, falukantUser.id); + if (!taxResult.success) throw new Error('Failed to update money for treasury'); + } for (const item of inventory) { await Inventory.destroy({ where: { id: item.id } }); } @@ -1617,6 +1683,32 @@ class FalukantService extends BaseService { } } + // Return tax summary for a branch: total cumulative tax and breakdown per region (region -> parent chain) + async getBranchTaxes(hashedUserId, branchId) { + const user = await getFalukantUserOrFail(hashedUserId); + const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id }, include: [{ model: RegionData, as: 'region', attributes: ['id','name','parentId','taxPercent'] }] }); + if (!branch) throw new Error('Branch not found'); + const regionId = branch.regionId; + + // gather region + ancestors with taxPercent + const rows = await sequelize.query(` + WITH RECURSIVE ancestors AS ( + SELECT id, parent_id, tax_percent, name FROM falukant_data.region WHERE id = :id + UNION ALL + SELECT r.id, r.parent_id, r.tax_percent, r.name FROM falukant_data.region r JOIN ancestors a ON r.id = a.parent_id + ) SELECT id, name, parent_id, COALESCE(tax_percent,0) AS tax_percent FROM ancestors; + `, { replacements: { id: regionId }, type: sequelize.QueryTypes.SELECT }); + + // compute total + const total = rows.reduce((sum, r) => sum + parseFloat(r.tax_percent || 0), 0); + + return { + regionId, + total: Math.round(total * 100) / 100, + breakdown: rows.map(r => ({ id: r.id, name: r.name, parentId: r.parent_id, taxPercent: parseFloat(r.tax_percent || 0) })) + }; + } + async moneyHistory(hashedUserId, page = 1, filter = '') { const u = await getFalukantUserOrFail(hashedUserId); const limit = 25, offset = (page - 1) * limit; diff --git a/backend/sql/update_product_sell_costs.sql b/backend/sql/update_product_sell_costs.sql new file mode 100644 index 0000000..187c2d7 --- /dev/null +++ b/backend/sql/update_product_sell_costs.sql @@ -0,0 +1,69 @@ +-- Backup original sell_cost values (just in case) +-- Run this once: will add a column original_sell_cost and copy existing sell_cost into it +ALTER TABLE IF EXISTS falukant_type.product + ADD COLUMN IF NOT EXISTS original_sell_cost numeric; + +UPDATE falukant_type.product +SET sell_cost = sell_cost * ((6 * 7 / 100) + 100); + +-- Compute min and max cumulative tax across all regions +WITH RECURSIVE ancestors AS ( + SELECT id AS start_id, id, parent_id, tax_percent FROM falukant_data.region + UNION ALL + SELECT a.start_id, r.id, r.parent_id, r.tax_percent + FROM falukant_data.region r + JOIN ancestors a ON r.id = a.parent_id +), totals AS ( + SELECT start_id, COALESCE(SUM(tax_percent),0) AS total FROM ancestors GROUP BY start_id +), mm AS ( + SELECT COALESCE(MIN(total),0) AS min_total, COALESCE(MAX(total),0) AS max_total FROM totals +) +SELECT * FROM mm; + +-- Choose one of the following update blocks to run: + +-- 1) MIN-STRATEGY: increase sell_cost so that taxes at the minimal cumulative tax have no effect +-- (this will set sell_cost = CEIL(original_sell_cost * (1 / (1 - min_total/100)))) + +-- BEGIN MIN-STRATEGY +-- WITH mm AS ( +-- WITH RECURSIVE ancestors AS ( +-- SELECT id AS start_id, id, parent_id, tax_percent FROM falukant_data.region +-- UNION ALL +-- SELECT a.start_id, r.id, r.parent_id, r.tax_percent +-- FROM falukant_data.region r +-- JOIN ancestors a ON r.id = a.parent_id +-- ), totals AS ( +-- SELECT start_id, COALESCE(SUM(tax_percent),0) AS total FROM ancestors GROUP BY start_id +-- ) +-- SELECT COALESCE(MIN(total),0) AS min_total FROM totals +-- ) +-- UPDATE falukant_type.product +-- SET sell_cost = CEIL(original_sell_cost * (CASE WHEN (1 - (SELECT min_total FROM mm)/100) <= 0 THEN 1 ELSE (1 / (1 - (SELECT min_total FROM mm)/100)) END)); +-- END MIN-STRATEGY + +-- 2) MAX-STRATEGY: increase sell_cost so that taxes at the maximal cumulative tax have no effect +-- (this will set sell_cost = CEIL(original_sell_cost * (1 / (1 - max_total/100)))) + +-- BEGIN MAX-STRATEGY +-- WITH mm AS ( +-- WITH RECURSIVE ancestors AS ( +-- SELECT id AS start_id, id, parent_id, tax_percent FROM falukant_data.region +-- UNION ALL +-- SELECT a.start_id, r.id, r.parent_id, r.tax_percent +-- FROM falukant_data.region r +-- JOIN ancestors a ON r.id = a.parent_id +-- ), totals AS ( +-- SELECT start_id, COALESCE(SUM(tax_percent),0) AS total FROM ancestors GROUP BY start_id +-- ) +-- SELECT COALESCE(MAX(total),0) AS max_total FROM totals +-- ) +-- UPDATE falukant_type.product +-- SET sell_cost = CEIL(original_sell_cost * (CASE WHEN (1 - (SELECT max_total FROM mm)/100) <= 0 THEN 1 ELSE (1 / (1 - (SELECT max_total FROM mm)/100)) END)); +-- END MAX-STRATEGY + +-- Notes: +-- 1) Uncomment exactly one strategy block (MIN or MAX) and run the script. +-- 2) The script creates `original_sell_cost` as a backup; keep it for safety. +-- 3) CEIL is used to avoid undercompensating due to rounding. If you prefer ROUND use ROUND(...). +-- 4) Test on a staging DB first. diff --git a/backend/utils/falukant/initializeFalukantPredefines.js b/backend/utils/falukant/initializeFalukantPredefines.js index c392b86..2f9679a 100644 --- a/backend/utils/falukant/initializeFalukantPredefines.js +++ b/backend/utils/falukant/initializeFalukantPredefines.js @@ -228,10 +228,29 @@ async function initializeFalukantStockTypes() { } async function initializeFalukantProducts() { - await ProductType.bulkCreate([ + // compute min/max cumulative tax across regions + const taxRows = await sequelize.query(` + WITH RECURSIVE ancestors AS ( + SELECT id AS start_id, id, parent_id, tax_percent FROM falukant_data.region + UNION ALL + SELECT a.start_id, r.id, r.parent_id, r.tax_percent + FROM falukant_data.region r + JOIN ancestors a ON r.id = a.parent_id + ), totals AS ( + SELECT start_id, COALESCE(SUM(tax_percent), 0) AS total FROM ancestors GROUP BY start_id + ) + SELECT COALESCE(MIN(total),0) AS min_total, COALESCE(MAX(total),0) AS max_total FROM totals + `, { type: sequelize.QueryTypes.SELECT }); + + const minTax = parseFloat(taxRows?.[0]?.min_total) || 0; + const maxTax = parseFloat(taxRows?.[0]?.max_total) || 0; + const factorMin = (minTax >= 100) ? 1 : (1 / (1 - minTax / 100)); + const factorMax = (maxTax >= 100) ? 1 : (1 / (1 - maxTax / 100)); + + const baseProducts = [ { labelTr: 'wheat', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'grain', category: 1, productionTime: 2, sellCost: 7 }, - { labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 46}, + { labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 5}, { labelTr: 'fish', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'meat', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'leather', category: 1, productionTime: 2, sellCost: 7 }, @@ -261,7 +280,15 @@ async function initializeFalukantProducts() { { labelTr: 'shield', category: 4, productionTime: 5, sellCost: 60 }, { labelTr: 'horse', category: 5, productionTime: 5, sellCost: 60 }, { labelTr: 'ox', category: 5, productionTime: 5, sellCost: 60 }, - ], { + ]; + + const productsToInsert = baseProducts.map(p => ({ + ...p, + sellCostMinNeutral: Math.ceil(p.sellCost * factorMin), + sellCostMaxNeutral: Math.ceil(p.sellCost * factorMax), + })); + + await ProductType.bulkCreate(productsToInsert, { ignoreDuplicates: true, }); } diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js index 64c2d9f..2f2d282 100644 --- a/backend/utils/falukant/initializeFalukantTypes.js +++ b/backend/utils/falukant/initializeFalukantTypes.js @@ -302,6 +302,7 @@ const politicalOfficeBenefitTypes = [ { tr: 'tax_exemption' }, { tr: 'guard_protection' }, { tr: 'court_immunity' }, + { tr: 'set_regionl_tax' }, ]; const politicalOffices = [ diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index a1a7cf3..abc2859 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -82,6 +82,16 @@ "title": "Erdbeben", "description": "Ein Erdbeben hat die Region {regionName} erschüttert." } + , + "taxes": { + "title": "Steuern", + "loading": "Steuerdaten werden geladen...", + "total": "Gesamte Steuer", + "table": { + "region": "Region", + "taxPercent": "Steuersatz" + } + } } }, "health": { @@ -177,7 +187,20 @@ "inventory": "Inventar", "production": "Produktion", "storage": "Lager", - "transport": "Transportmittel" + "transport": "Transportmittel", + "taxes": "Steuern" + }, + "taxes": { + "title": "Steuern", + "loading": "Steuerdaten werden geladen...", + "loadingError": "Fehler beim Laden der Steuerdaten: {error}", + "retry": "Erneut laden", + "noData": "Keine Steuerdaten verfügbar", + "total": "Gesamte Steuer", + "table": { + "region": "Region", + "taxPercent": "Steuersatz" + } }, "selection": { "title": "Niederlassungsauswahl", diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 27dc7db..060700d 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -172,6 +172,26 @@ "four_horse_carriage": "Four-horse carriage", "raft": "Raft", "sailing_ship": "Sailing ship" + }, + "tabs": { + "director": "Director", + "inventory": "Inventory", + "production": "Production", + "storage": "Storage", + "transport": "Transport", + "taxes": "Taxes" + } + ,"taxes": { + "title": "Taxes", + "loading": "Loading tax data...", + "loadingError": "Failed to load tax data: {error}", + "retry": "Retry", + "noData": "No tax data available", + "total": "Total tax", + "table": { + "region": "Region", + "taxPercent": "Tax %" + } } }, "nobility": { @@ -270,6 +290,16 @@ "details": { "title": "Child Details" } + , + "taxes": { + "title": "Taxes", + "loading": "Loading tax data...", + "total": "Total tax", + "table": { + "region": "Region", + "taxPercent": "Tax %" + } + } } } } diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index bf4fe4a..5ccae65 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -35,6 +35,11 @@
{{ $t('falukant.branch.taxes.loading') }}
+{{ $t('falukant.branch.taxes.loadingError', { error: branchTaxesError }) }}
+ +{{ $t('falukant.branch.taxes.noData') }}
++ {{ $t('falukant.branch.taxes.total') }}: + {{ formatCurrency(branchTaxes.total) }} +
+| {{ $t('falukant.branch.taxes.table.region') }} | +{{ $t('falukant.branch.taxes.table.taxPercent') }} | +
|---|---|
| {{ r.name }} | +{{ r.taxPercent }}% | +