feat(user): add certificate production tracking and update localization
All checks were successful
Deploy to production / deploy (push) Successful in 2m50s
All checks were successful
Deploy to production / deploy (push) Successful in 2m50s
- Introduced a new field `certificateProductionsCountSince` in the `FalukantUser` model to track the date from which production logs are counted for certificate requirements. - Updated the `FalukantService` to utilize the new field for calculating completed productions since the specified date. - Enhanced the UI to display the count of productions since the last promotion, with corresponding translations added for multiple languages including Cebuano, German, English, Spanish, and French. - Implemented a method to delete old production logs, ensuring efficient data management while maintaining necessary historical records for certificate calculations.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.falukant_user
|
||||
ADD COLUMN IF NOT EXISTS certificate_productions_count_since TIMESTAMPTZ;
|
||||
`);
|
||||
await queryInterface.sequelize.query(`
|
||||
COMMENT ON COLUMN falukant_data.falukant_user.certificate_productions_count_since IS
|
||||
'Daemon/UI: Zählt nur falukant_log.production-Zeilen mit COALESCE(production_timestamp, production_date::timestamp) >= diesem Wert; bei Stufenänderung (Aufstieg/Bankrott/Erbfolge) auf NOW() (YpDaemon QUERY_UPDATE_FALUKANT_USER_CERTIFICATE). NULL = alle passenden Log-Zeilen bis zur ersten Stufenänderung nach Migration. Kein Löschen der Logs zum Reset.';
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data.falukant_user
|
||||
DROP COLUMN IF EXISTS certificate_productions_count_since;
|
||||
`);
|
||||
}
|
||||
};
|
||||
13
backend/migrations/README.md
Normal file
13
backend/migrations/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Backend-Migrationen (Sequelize)
|
||||
|
||||
Migrationen in diesem Ordner werden mit dem Sequelize-CLI ausgeführt (Konfiguration siehe Projekt-Root / `backend`).
|
||||
|
||||
## Falukant: Zertifikat und Produktionszählung
|
||||
|
||||
| Datei | Inhalt |
|
||||
|--------|--------|
|
||||
| `20260402140000-add-certificate-productions-count-since.cjs` | Spalte `falukant_data.falukant_user.certificate_productions_count_since` (`TIMESTAMPTZ`, nullable) inkl. Kommentar. Setzt die DB-Grundlage dafür, dass Daemon, Backend und UI dieselbe Periode für „abgeschlossene Produktionen“ nutzen (Filter mit `COALESCE(production_timestamp, production_date::timestamp)` ab diesem Zeitpunkt; `NULL` = bisherige Historie). |
|
||||
|
||||
Eine parallele SQL-Migration im Daemon-Repository (z. B. `014_falukant_certificate_productions_count_since.sql`) kann dieselbe Spalte anlegen, wenn das Deployment dort getrennt ist – Schema doppelt anlegen vermeiden.
|
||||
|
||||
Details zur Zähl- und Retention-Logik: `docs/FALUKANT_PRODUCTION_CERTIFICATE.md`.
|
||||
@@ -29,6 +29,10 @@ FalukantUser.init({
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1},
|
||||
certificateProductionsCountSince: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
mainBranchRegionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
|
||||
@@ -14,7 +14,6 @@ import TitleBenefit from '../models/falukant/type/title_benefit.js';
|
||||
import Branch from '../models/falukant/data/branch.js';
|
||||
import BranchType from '../models/falukant/type/branch.js';
|
||||
import Production from '../models/falukant/data/production.js';
|
||||
import DayProduction from '../models/falukant/log/dayproduction.js';
|
||||
import ProductType from '../models/falukant/type/product.js';
|
||||
import Knowledge from '../models/falukant/data/product_knowledge.js';
|
||||
import Inventory from '../models/falukant/data/inventory.js';
|
||||
@@ -976,7 +975,7 @@ class FalukantService extends BaseService {
|
||||
]
|
||||
},
|
||||
],
|
||||
attributes: ['id', 'money', 'creditAmount', 'todayCreditTaken', 'certificate']
|
||||
attributes: ['id', 'money', 'creditAmount', 'todayCreditTaken', 'certificate', 'certificateProductionsCountSince']
|
||||
});
|
||||
if (!u) throw new Error('User not found');
|
||||
if (u.certificate == null) {
|
||||
@@ -2952,6 +2951,36 @@ class FalukantService extends BaseService {
|
||||
return candidates[0] || { rank: 0, name: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Zertifikat: abgeschlossene Produktionen über alle Regionen/Niederlassungen.
|
||||
* Pro (Produkt, Kalendertag) nur ein Zähler – mehrere Niederlassungen in verschiedenen Regionen werden zusammengeführt.
|
||||
* Filter bei gesetztem countSince wie Daemon (GET_PRODUCTION_CERTIFICATE_INPUT_ROWS):
|
||||
* COALESCE(production_timestamp, production_date::timestamp) >= countSince.
|
||||
*
|
||||
* @param {number} producerId falukant_user.id
|
||||
* @param {Date|null|undefined} countSince null/undefined = gesamte Historie (Bestand / vor erster Stufenänderung)
|
||||
*/
|
||||
async getCertificateCompletedProductionCount(producerId, countSince) {
|
||||
const sinceClause = countSince
|
||||
? ' AND COALESCE(production_timestamp, production_date::timestamp) >= :countSince'
|
||||
: '';
|
||||
const replacements = { producerId };
|
||||
if (countSince) replacements.countSince = countSince;
|
||||
const rows = await sequelize.query(
|
||||
`
|
||||
SELECT COUNT(*)::int AS cnt
|
||||
FROM (
|
||||
SELECT 1
|
||||
FROM falukant_log.production
|
||||
WHERE producer_id = :producerId${sinceClause}
|
||||
GROUP BY product_id, production_date
|
||||
) AS sub
|
||||
`,
|
||||
{ replacements, type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
return Number(rows[0]?.cnt ?? 0);
|
||||
}
|
||||
|
||||
async buildCertificateProgress(user) {
|
||||
const character = user?.character || await FalukantCharacter.findOne({
|
||||
where: { userId: user.id },
|
||||
@@ -2961,9 +2990,10 @@ class FalukantService extends BaseService {
|
||||
return null;
|
||||
}
|
||||
|
||||
const productionsSince = user.certificateProductionsCountSince ?? null;
|
||||
const [avgKnowledge, completedProductions, highestPoliticalOffice, highestChurchOffice, house, title] = await Promise.all([
|
||||
this.calculateAverageKnowledge(character.id),
|
||||
DayProduction.count({ where: { producerId: user.id } }),
|
||||
this.getCertificateCompletedProductionCount(user.id, productionsSince),
|
||||
this.getHighestPoliticalOfficeInfo(user.id),
|
||||
this.getHighestChurchOfficeInfo(user.id),
|
||||
UserHouse.findOne({
|
||||
@@ -3077,6 +3107,9 @@ class FalukantService extends BaseService {
|
||||
scoreRequirementMet,
|
||||
minimumRequirementsMet,
|
||||
readyForNextCertificate: scoreRequirementMet && minimumRequirementsMet,
|
||||
certificateProductionsCountSince: productionsSince
|
||||
? new Date(productionsSince).toISOString()
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4112,6 +4145,10 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
|
||||
await candidate.update({ userId: user.id });
|
||||
await FalukantUser.update(
|
||||
{ certificateProductionsCountSince: new Date() },
|
||||
{ where: { id: user.id } }
|
||||
);
|
||||
return { success: true, heirId: candidate.id };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user