Enhance SaleSection component to manage selling state with improved user feedback. Disable buttons during selling, show status messages for sellAll actions, and update translations for new statuses.
This commit is contained in:
601
SELL_OVERVIEW.md
Normal file
601
SELL_OVERVIEW.md
Normal file
@@ -0,0 +1,601 @@
|
||||
# Übersicht: Sell-Funktionen und verwendete Models/Tabellen
|
||||
|
||||
## Sell-Funktionen in `falukantService.js`
|
||||
|
||||
### 1. `sellProduct(hashedUserId, branchId, productId, quality, quantity)`
|
||||
Verkauft ein einzelnes Produkt mit bestimmter Qualität.
|
||||
|
||||
**Ablauf:**
|
||||
1. Lädt User, Branch, Character, Stock
|
||||
2. Lädt Inventory mit ProductType und Knowledge
|
||||
3. Berechnet Preis pro Einheit mit `calcRegionalSellPrice()`
|
||||
4. Berechnet kumulative Steuer mit politischen Befreiungen
|
||||
5. Passt Preis an (Inflation basierend auf Steuer)
|
||||
6. Berechnet Revenue, Tax, Net
|
||||
7. Aktualisiert Geld für Verkäufer und Treasury
|
||||
8. Entfernt verkaufte Items aus Inventory
|
||||
9. Erstellt/aktualisiert DaySell Eintrag
|
||||
10. Sendet Socket-Notifications
|
||||
|
||||
**Verwendete Models/Tabellen:**
|
||||
- `FalukantUser` (`falukant_data.falukant_user`)
|
||||
- `Branch` (`falukant_data.branch`)
|
||||
- `FalukantCharacter` (`falukant_data.character`)
|
||||
- `FalukantStock` (`falukant_data.stock`)
|
||||
- `Inventory` (`falukant_data.inventory`)
|
||||
- `ProductType` (`falukant_type.product`)
|
||||
- `Knowledge` (`falukant_data.knowledge`)
|
||||
- `TownProductWorth` (`falukant_data.town_product_worth`)
|
||||
- `RegionData` (`falukant_data.region`)
|
||||
- `RegionType` (`falukant_type.region`)
|
||||
- `PoliticalOffice` (`falukant_data.political_office`)
|
||||
- `PoliticalOfficeType` (`falukant_type.political_office_type`)
|
||||
- `DaySell` (`falukant_log.day_sell`)
|
||||
- `MoneyFlow` (via `updateFalukantUserMoney`)
|
||||
|
||||
### 2. `sellAllProducts(hashedUserId, branchId)`
|
||||
Verkauft alle Produkte eines Branches.
|
||||
|
||||
**Ablauf:**
|
||||
1. Lädt User, Branch mit Stocks
|
||||
2. Lädt alle Inventory Items mit ProductType, Knowledge, Stock, Branch
|
||||
3. Für jedes Item:
|
||||
- Berechnet Preis pro Einheit
|
||||
- Berechnet kumulative Steuer
|
||||
- Passt Preis an
|
||||
- Erstellt/aktualisiert DaySell Eintrag
|
||||
4. Berechnet Gesamt-Tax pro Region
|
||||
5. Aktualisiert Geld für Verkäufer und Treasury
|
||||
6. Löscht alle Inventory Items
|
||||
7. Sendet Socket-Notifications
|
||||
|
||||
**Verwendete Models/Tabellen:**
|
||||
- `FalukantUser` (`falukant_data.falukant_user`)
|
||||
- `Branch` (`falukant_data.branch`)
|
||||
- `FalukantStock` (`falukant_data.stock`)
|
||||
- `FalukantStockType` (`falukant_type.stock`)
|
||||
- `FalukantCharacter` (`falukant_data.character`)
|
||||
- `Inventory` (`falukant_data.inventory`)
|
||||
- `ProductType` (`falukant_type.product`)
|
||||
- `Knowledge` (`falukant_data.knowledge`)
|
||||
- `TownProductWorth` (`falukant_data.town_product_worth`)
|
||||
- `RegionData` (`falukant_data.region`)
|
||||
- `RegionType` (`falukant_type.region`)
|
||||
- `PoliticalOffice` (`falukant_data.political_office`)
|
||||
- `PoliticalOfficeType` (`falukant_type.political_office_type`)
|
||||
- `DaySell` (`falukant_log.day_sell`)
|
||||
- `MoneyFlow` (via `updateFalukantUserMoney`)
|
||||
|
||||
### 3. `addSellItem(branchId, userId, productId, quantity)`
|
||||
Erstellt oder aktualisiert einen DaySell Eintrag für einen Verkauf.
|
||||
|
||||
**Ablauf:**
|
||||
1. Lädt Branch
|
||||
2. Sucht nach existierendem DaySell Eintrag
|
||||
3. Erstellt neuen oder aktualisiert existierenden Eintrag
|
||||
|
||||
**Verwendete Models/Tabellen:**
|
||||
- `Branch` (`falukant_data.branch`)
|
||||
- `DaySell` (`falukant_log.day_sell`)
|
||||
|
||||
## Hilfsfunktionen
|
||||
|
||||
### `calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null)`
|
||||
Berechnet den Verkaufspreis eines Produkts basierend auf:
|
||||
- Basispreis (`product.sellCost`)
|
||||
- Regionalem Worth-Percent (`town_product_worth.worth_percent`)
|
||||
- Knowledge-Faktor (0-100)
|
||||
|
||||
**Verwendete Models/Tabellen:**
|
||||
- `ProductType` (`falukant_type.product`)
|
||||
- `TownProductWorth` (`falukant_data.town_product_worth`)
|
||||
|
||||
### `getCumulativeTaxPercent(regionId)`
|
||||
Berechnet die kumulative Steuer für eine Region und alle Vorfahren (rekursiv).
|
||||
|
||||
**SQL Query:**
|
||||
```sql
|
||||
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;
|
||||
```
|
||||
|
||||
**Verwendete Tabellen:**
|
||||
- `falukant_data.region`
|
||||
|
||||
### `getCumulativeTaxPercentWithExemptions(userId, regionId)`
|
||||
Berechnet die kumulative Steuer mit politischen Befreiungen.
|
||||
|
||||
**Ablauf:**
|
||||
1. Lädt Character des Users
|
||||
2. Lädt alle PoliticalOffices des Characters
|
||||
3. Bestimmt befreite Region-Typen basierend auf Ämtern
|
||||
4. Berechnet kumulative Steuer, aber schließt befreite Region-Typen aus
|
||||
|
||||
**SQL Query:**
|
||||
```sql
|
||||
WITH RECURSIVE ancestors AS (
|
||||
SELECT r.id, r.parent_id, r.tax_percent, rt.label_tr as region_type
|
||||
FROM falukant_data.region r
|
||||
JOIN falukant_type.region rt ON rt.id = r.region_type_id
|
||||
WHERE r.id = :id
|
||||
UNION ALL
|
||||
SELECT reg.id, reg.parent_id, reg.tax_percent, rt2.label_tr
|
||||
FROM falukant_data.region reg
|
||||
JOIN falukant_type.region rt2 ON rt2.id = reg.region_type_id
|
||||
JOIN ancestors a ON reg.id = a.parent_id
|
||||
)
|
||||
SELECT COALESCE(SUM(CASE WHEN ARRAY[...] && ARRAY[region_type]::text[] THEN 0 ELSE tax_percent END),0) AS total FROM ancestors;
|
||||
```
|
||||
|
||||
**Verwendete Models/Tabellen:**
|
||||
- `FalukantCharacter` (`falukant_data.character`)
|
||||
- `PoliticalOffice` (`falukant_data.political_office`)
|
||||
- `PoliticalOfficeType` (`falukant_type.political_office_type`)
|
||||
- `RegionData` (`falukant_data.region`)
|
||||
- `RegionType` (`falukant_type.region`)
|
||||
|
||||
**Politische Steuerbefreiungen:**
|
||||
```javascript
|
||||
const POLITICAL_TAX_EXEMPTIONS = {
|
||||
'council': ['city'],
|
||||
'taxman': ['city', 'county'],
|
||||
'treasurerer': ['city', 'county', 'shire'],
|
||||
'super-state-administrator': ['city', 'county', 'shire', 'markgrave', 'duchy'],
|
||||
'chancellor': ['city','county','shire','markgrave','duchy'] // = alle Typen
|
||||
};
|
||||
```
|
||||
|
||||
## Model-Definitionen
|
||||
|
||||
### Inventory (`falukant_data.inventory`)
|
||||
```javascript
|
||||
// backend/models/falukant/data/inventory.js
|
||||
- id
|
||||
- stockId (FK zu falukant_data.stock)
|
||||
- productId (FK zu falukant_type.product)
|
||||
- quantity
|
||||
- quality
|
||||
- producedAt
|
||||
```
|
||||
|
||||
### DaySell (`falukant_log.day_sell`)
|
||||
```javascript
|
||||
// backend/models/falukant/log/daysell.js
|
||||
- id
|
||||
- regionId (FK zu falukant_data.region)
|
||||
- productId (FK zu falukant_type.product)
|
||||
- sellerId (FK zu falukant_data.falukant_user)
|
||||
- quantity
|
||||
- createdAt
|
||||
- updatedAt
|
||||
```
|
||||
|
||||
### TownProductWorth (`falukant_data.town_product_worth`)
|
||||
```javascript
|
||||
// backend/models/falukant/data/town_product_worth.js
|
||||
- id
|
||||
- productId (FK zu falukant_type.product)
|
||||
- regionId (FK zu falukant_data.region)
|
||||
- worthPercent (0-100)
|
||||
```
|
||||
|
||||
### Knowledge (`falukant_data.knowledge`)
|
||||
```javascript
|
||||
// backend/models/falukant/data/product_knowledge.js
|
||||
- id
|
||||
- productId (FK zu falukant_type.product)
|
||||
- characterId (FK zu falukant_data.character)
|
||||
- knowledge (0-99)
|
||||
```
|
||||
|
||||
## Wichtige SQL-Queries
|
||||
|
||||
### 1. Inventory mit ProductType und Knowledge laden
|
||||
```javascript
|
||||
Inventory.findAll({
|
||||
where: { quality },
|
||||
include: [
|
||||
{
|
||||
model: ProductType,
|
||||
as: 'productType',
|
||||
required: true,
|
||||
where: { id: productId },
|
||||
include: [
|
||||
{
|
||||
model: Knowledge,
|
||||
as: 'knowledges',
|
||||
required: false,
|
||||
where: { characterId: character.id }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### 2. Kumulative Steuer mit Befreiungen berechnen
|
||||
Siehe `getCumulativeTaxPercentWithExemptions()` oben.
|
||||
|
||||
## Preisberechnung
|
||||
|
||||
### Formel für `calcRegionalSellPrice`:
|
||||
1. Basispreis = `product.sellCost * (worthPercent / 100)`
|
||||
2. Min = `basePrice * 0.6`
|
||||
3. Max = `basePrice`
|
||||
4. Preis = `min + (max - min) * (knowledgeFactor / 100)`
|
||||
|
||||
### Steueranpassung:
|
||||
1. `inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100))`
|
||||
2. `adjustedPricePerUnit = pricePerUnit * inflationFactor`
|
||||
3. `revenue = quantity * adjustedPricePerUnit`
|
||||
4. `taxValue = revenue * cumulativeTax / 100`
|
||||
5. `net = revenue - taxValue`
|
||||
|
||||
## Vollständige Code-Snippets
|
||||
|
||||
### `calcRegionalSellPrice()`
|
||||
```javascript
|
||||
async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null) {
|
||||
if (worthPercent === null) {
|
||||
const townWorth = await TownProductWorth.findOne({
|
||||
where: { productId: product.id, regionId: regionId }
|
||||
});
|
||||
worthPercent = townWorth?.worthPercent || 50; // Default 50% wenn nicht gefunden
|
||||
}
|
||||
|
||||
// Basispreis basierend auf regionalem worthPercent
|
||||
const basePrice = product.sellCost * (worthPercent / 100);
|
||||
|
||||
// Dann Knowledge-Faktor anwenden
|
||||
const min = basePrice * 0.6;
|
||||
const max = basePrice;
|
||||
return min + (max - min) * (knowledgeFactor / 100);
|
||||
}
|
||||
```
|
||||
|
||||
### `getCumulativeTaxPercent()`
|
||||
```javascript
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
### `getCumulativeTaxPercentWithExemptions()` (vereinfacht)
|
||||
```javascript
|
||||
async function getCumulativeTaxPercentWithExemptions(userId, regionId) {
|
||||
if (!regionId) return 0;
|
||||
|
||||
// Character finden
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId },
|
||||
attributes: ['id']
|
||||
});
|
||||
if (!character) return 0;
|
||||
|
||||
// Politische Ämter laden
|
||||
const offices = await PoliticalOffice.findAll({
|
||||
where: { characterId: character.id },
|
||||
include: [
|
||||
{ model: PoliticalOfficeType, as: 'type', attributes: ['name'] },
|
||||
{
|
||||
model: RegionData,
|
||||
as: 'region',
|
||||
include: [{
|
||||
model: RegionType,
|
||||
as: 'regionType',
|
||||
attributes: ['labelTr']
|
||||
}]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Befreite Region-Typen bestimmen
|
||||
const exemptTypes = new Set();
|
||||
let hasChancellor = false;
|
||||
for (const o of offices) {
|
||||
const name = o.type?.name;
|
||||
if (!name) continue;
|
||||
if (name === 'chancellor') { hasChancellor = true; break; }
|
||||
const allowed = POLITICAL_TAX_EXEMPTIONS[name];
|
||||
if (allowed && Array.isArray(allowed)) {
|
||||
for (const t of allowed) exemptTypes.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChancellor) return 0;
|
||||
|
||||
// SQL Query mit Befreiungen
|
||||
const exemptTypesArray = Array.from(exemptTypes);
|
||||
const exemptTypesString = exemptTypesArray.length > 0
|
||||
? `ARRAY[${exemptTypesArray.map(t => `'${t.replace(/'/g, "''")}'`).join(',')}]`
|
||||
: `ARRAY[]::text[]`;
|
||||
|
||||
const rows = await sequelize.query(
|
||||
`WITH RECURSIVE ancestors AS (
|
||||
SELECT r.id, r.parent_id, r.tax_percent, rt.label_tr as region_type
|
||||
FROM falukant_data.region r
|
||||
JOIN falukant_type.region rt ON rt.id = r.region_type_id
|
||||
WHERE r.id = :id
|
||||
UNION ALL
|
||||
SELECT reg.id, reg.parent_id, reg.tax_percent, rt2.label_tr
|
||||
FROM falukant_data.region reg
|
||||
JOIN falukant_type.region rt2 ON rt2.id = reg.region_type_id
|
||||
JOIN ancestors a ON reg.id = a.parent_id
|
||||
)
|
||||
SELECT COALESCE(SUM(CASE WHEN ${exemptTypesString} && ARRAY[region_type]::text[] THEN 0 ELSE tax_percent END),0) AS total FROM ancestors;`,
|
||||
{
|
||||
replacements: { id: regionId },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
const val = rows?.[0]?.total ?? 0;
|
||||
return parseFloat(val) || 0;
|
||||
}
|
||||
```
|
||||
|
||||
### `sellProduct()` (Kern-Logik)
|
||||
```javascript
|
||||
async sellProduct(hashedUserId, branchId, productId, quality, quantity) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const branch = await getBranchOrFail(user.id, branchId);
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||
if (!character) throw new Error('No character found for user');
|
||||
const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
|
||||
if (!stock) throw new Error('Stock not found');
|
||||
|
||||
// Inventory laden
|
||||
const inventory = await Inventory.findAll({
|
||||
where: { quality },
|
||||
include: [
|
||||
{
|
||||
model: ProductType,
|
||||
as: 'productType',
|
||||
required: true,
|
||||
where: { id: productId },
|
||||
include: [
|
||||
{
|
||||
model: Knowledge,
|
||||
as: 'knowledges',
|
||||
required: false,
|
||||
where: { characterId: character.id }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!inventory.length) throw new Error('No inventory found');
|
||||
|
||||
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
||||
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);
|
||||
|
||||
// Steuer berechnen
|
||||
const cumulativeTax = await getCumulativeTaxPercentWithExemptions(user.id, branch.regionId);
|
||||
const inflationFactor = cumulativeTax >= 100 ? 1 : (1 / (1 - cumulativeTax / 100));
|
||||
const adjustedPricePerUnit = Math.round(pricePerUnit * inflationFactor * 100) / 100;
|
||||
const revenue = quantity * adjustedPricePerUnit;
|
||||
|
||||
// Tax und Net berechnen
|
||||
const taxValue = Math.round((revenue * cumulativeTax / 100) * 100) / 100;
|
||||
const net = Math.round((revenue - taxValue) * 100) / 100;
|
||||
|
||||
// Geld aktualisieren
|
||||
const moneyResult = await updateFalukantUserMoney(user.id, net, `Product sale (net)`, user.id);
|
||||
if (!moneyResult.success) throw new Error('Failed to update money for seller');
|
||||
|
||||
// Steuer an Treasury buchen
|
||||
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');
|
||||
}
|
||||
|
||||
// Inventory aktualisieren
|
||||
let remaining = quantity;
|
||||
for (const inv of inventory) {
|
||||
if (inv.quantity <= remaining) {
|
||||
remaining -= inv.quantity;
|
||||
await inv.destroy();
|
||||
} else {
|
||||
await inv.update({ quantity: inv.quantity - remaining });
|
||||
remaining = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// DaySell Eintrag erstellen/aktualisieren
|
||||
await this.addSellItem(branchId, user.id, productId, quantity);
|
||||
|
||||
// Notifications senden
|
||||
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
|
||||
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
### `addSellItem()`
|
||||
```javascript
|
||||
async addSellItem(branchId, userId, productId, quantity) {
|
||||
const branch = await Branch.findOne({
|
||||
where: { id: branchId },
|
||||
});
|
||||
const daySell = await DaySell.findOne({
|
||||
where: {
|
||||
regionId: branch.regionId,
|
||||
productId: productId,
|
||||
sellerId: userId,
|
||||
}
|
||||
});
|
||||
if (daySell) {
|
||||
daySell.quantity += quantity;
|
||||
await daySell.save();
|
||||
} else {
|
||||
await DaySell.create({
|
||||
regionId: branch.regionId,
|
||||
productId: productId,
|
||||
sellerId: userId,
|
||||
quantity: quantity,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Wichtige Hinweise
|
||||
|
||||
1. **Inventory wird nach Verkauf gelöscht/aktualisiert**: Items werden aus der Inventory entfernt oder die Menge reduziert.
|
||||
|
||||
2. **DaySell wird aggregiert**: Wenn bereits ein DaySell Eintrag für Region/Product/Seller existiert, wird die Menge addiert.
|
||||
|
||||
3. **Steuer wird an Treasury gebucht**: Wenn `TREASURY_FALUKANT_USER_ID` gesetzt ist, wird die Steuer an diesen User gebucht.
|
||||
|
||||
4. **Socket-Notifications**: Nach jedem Verkauf werden `falukantUpdateStatus` und `falukantBranchUpdate` Events gesendet.
|
||||
|
||||
5. **Politische Befreiungen**: Bestimmte politische Ämter befreien von Steuern in bestimmten Region-Typen. Chancellor befreit von allen Steuern.
|
||||
|
||||
6. **Preis-Inflation**: Der Preis wird basierend auf der Steuer inflatiert, damit der Netto-Betrag für den Verkäufer gleich bleibt.
|
||||
|
||||
## Tabellenübersicht
|
||||
|
||||
### `falukant_data.inventory`
|
||||
- `id` (PK)
|
||||
- `stock_id` (FK zu `falukant_data.stock`)
|
||||
- `product_id` (FK zu `falukant_type.product`)
|
||||
- `quantity` (INTEGER)
|
||||
- `quality` (INTEGER)
|
||||
- `produced_at` (DATE)
|
||||
|
||||
### `falukant_log.sell` (DaySell)
|
||||
- `id` (PK)
|
||||
- `region_id` (FK zu `falukant_data.region`)
|
||||
- `product_id` (FK zu `falukant_type.product`)
|
||||
- `seller_id` (FK zu `falukant_data.falukant_user`)
|
||||
- `quantity` (INTEGER)
|
||||
- `sell_timestamp` (DATE)
|
||||
- **Unique Index**: `(seller_id, product_id, region_id)`
|
||||
|
||||
### `falukant_data.town_product_worth`
|
||||
- `id` (PK)
|
||||
- `product_id` (FK zu `falukant_type.product`)
|
||||
- `region_id` (FK zu `falukant_data.region`)
|
||||
- `worth_percent` (INTEGER, 0-100)
|
||||
|
||||
### `falukant_data.knowledge`
|
||||
- `id` (PK)
|
||||
- `product_id` (FK zu `falukant_type.product`)
|
||||
- `character_id` (FK zu `falukant_data.character`)
|
||||
- `knowledge` (INTEGER, 0-99)
|
||||
|
||||
### `falukant_data.political_office`
|
||||
- `id` (PK)
|
||||
- `office_type_id` (FK zu `falukant_type.political_office_type`)
|
||||
- `character_id` (FK zu `falukant_data.character`)
|
||||
- `region_id` (FK zu `falukant_data.region`)
|
||||
- `created_at`, `updated_at`
|
||||
|
||||
### `falukant_type.political_office_type`
|
||||
- `id` (PK)
|
||||
- `name` (STRING) - z.B. 'council', 'taxman', 'treasurerer', 'super-state-administrator', 'chancellor'
|
||||
- `seats_per_region` (INTEGER)
|
||||
- `region_type` (STRING)
|
||||
- `term_length` (INTEGER)
|
||||
|
||||
### `falukant_data.region`
|
||||
- `id` (PK)
|
||||
- `name` (STRING)
|
||||
- `region_type_id` (FK zu `falukant_type.region`)
|
||||
- `parent_id` (FK zu `falukant_data.region`, nullable)
|
||||
- `map` (JSONB)
|
||||
- `tax_percent` (DECIMAL)
|
||||
|
||||
### `falukant_type.region`
|
||||
- `id` (PK)
|
||||
- `label_tr` (STRING) - z.B. 'city', 'county', 'shire', 'markgrave', 'duchy'
|
||||
- `parent_id` (FK zu `falukant_type.region`, nullable)
|
||||
|
||||
### `falukant_data.falukant_user`
|
||||
- `id` (PK)
|
||||
- `user_id` (FK zu `community.user`)
|
||||
- `money` (DECIMAL)
|
||||
- `credit_amount`, `today_credit_taken`, `credit_interest_rate`
|
||||
- `certificate`
|
||||
- `main_branch_region_id`
|
||||
- `last_nobility_advance_at`
|
||||
- `created_at`, `updated_at`
|
||||
|
||||
### `falukant_data.character`
|
||||
- `id` (PK)
|
||||
- `user_id` (FK zu `falukant_data.falukant_user`)
|
||||
- `region_id` (FK zu `falukant_data.region`)
|
||||
- `first_name`, `last_name`
|
||||
- `birthdate`, `gender`, `health`
|
||||
- `title_of_nobility` (FK zu `falukant_type.title_of_nobility`)
|
||||
- `mood_id` (FK zu `falukant_type.mood`)
|
||||
- `created_at`, `updated_at`
|
||||
|
||||
### `falukant_data.branch`
|
||||
- `id` (PK)
|
||||
- `branch_type_id` (FK zu `falukant_type.branch`)
|
||||
- `region_id` (FK zu `falukant_data.region`)
|
||||
- `falukant_user_id` (FK zu `falukant_data.falukant_user`)
|
||||
|
||||
### `falukant_data.stock`
|
||||
- `id` (PK)
|
||||
- `branch_id` (FK zu `falukant_data.branch`)
|
||||
- `stock_type_id` (FK zu `falukant_type.stock`)
|
||||
- `quantity` (INTEGER)
|
||||
- `product_quality` (INTEGER, nullable)
|
||||
|
||||
### `falukant_type.product`
|
||||
- `id` (PK)
|
||||
- `label_tr` (STRING, unique)
|
||||
- `category` (INTEGER)
|
||||
- `production_time` (INTEGER)
|
||||
- `sell_cost` (INTEGER)
|
||||
|
||||
## Dateipfade
|
||||
|
||||
- **Service**: `backend/services/falukantService.js`
|
||||
- **Models**:
|
||||
- `backend/models/falukant/data/inventory.js`
|
||||
- `backend/models/falukant/log/daysell.js`
|
||||
- `backend/models/falukant/data/town_product_worth.js`
|
||||
- `backend/models/falukant/data/product_knowledge.js`
|
||||
- `backend/models/falukant/data/political_office.js`
|
||||
- `backend/models/falukant/type/political_office_type.js`
|
||||
- `backend/models/falukant/data/region.js`
|
||||
- `backend/models/falukant/type/region.js`
|
||||
- `backend/models/falukant/data/character.js`
|
||||
- `backend/models/falukant/data/user.js`
|
||||
- `backend/models/falukant/data/branch.js`
|
||||
- `backend/models/falukant/data/stock.js`
|
||||
- `backend/models/falukant/type/product.js`
|
||||
|
||||
Reference in New Issue
Block a user