602 lines
20 KiB
Markdown
602 lines
20 KiB
Markdown
# Ü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`
|
|
|