From b58c1eb9ec971a120b692d79e1fea4a627fdeb2d Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Mon, 6 Jul 2026 18:52:32 +0200 Subject: [PATCH] Add daily report product target controls --- scripts/playwright-pr-mapping.mjs | 9 + .../Objects/DepartmentDailyReports.vue | 15 ++ src/i18n/generated/da-v2.json | 8 + src/i18n/generated/de-v2.json | 8 + src/i18n/generated/en-v2.json | 8 + src/i18n/generated/global-v2.json | 8 + src/i18n/generated/no-v2.json | 8 + src/i18n/generated/sv-v2.json | 8 + .../da/phrases/compat/admin/index-01.json | 8 + .../de/phrases/compat/admin/index-01.json | 8 + .../en/phrases/compat/admin/index-01.json | 8 + .../source/global/shared/admin/index-01.json | 8 + .../no/phrases/compat/admin/index-01.json | 8 + .../sv/phrases/compat/admin/index-01.json | 8 + .../DepartmentDailyReportObject.vue | 11 + .../DepartmentDashboardDailyReportCount.vue | 243 +++++++++++++++++- ...rtmentDashboardDailyReportProductSales.vue | 67 ++++- tests/e2e/admin-daily-report.spec.ts | 137 +++++++++- ...tment-daily-report-object.behavior.spec.js | 21 ++ ...aily-report-product-sales.behavior.spec.js | 93 +++++++ tests/unit/playwright-pr-mapping.spec.js | 11 + 21 files changed, 694 insertions(+), 9 deletions(-) diff --git a/scripts/playwright-pr-mapping.mjs b/scripts/playwright-pr-mapping.mjs index b940a4ae..adb53298 100644 --- a/scripts/playwright-pr-mapping.mjs +++ b/scripts/playwright-pr-mapping.mjs @@ -126,6 +126,15 @@ export const sourceMappings = [ specs: ["tests/e2e/admin-department-notifications.spec.ts"], projects: chromiumProjects, }, + { + name: "admin-daily-report", + patterns: [ + /^src\/views\/dashboards\/departmentDashboard\/modules\/daily-report\//u, + /^src\/components\/session\/token\/SessionUser\/Objects\/DepartmentDailyReports\.vue$/u, + ], + specs: ["tests/e2e/admin-daily-report.spec.ts"], + projects: chromiumProjects, + }, { name: "limited-backoffice", patterns: [/^src\/views\/backoffice\/LimitedBackoffice/u], diff --git a/src/components/session/token/SessionUser/Objects/DepartmentDailyReports.vue b/src/components/session/token/SessionUser/Objects/DepartmentDailyReports.vue index 7230f2d7..3c00d345 100644 --- a/src/components/session/token/SessionUser/Objects/DepartmentDailyReports.vue +++ b/src/components/session/token/SessionUser/Objects/DepartmentDailyReports.vue @@ -164,6 +164,21 @@ const t = (key) => i18n.global.t(key); } ); }, + setProductTarget: async ({ department_id, product_id, target_percentage }) => { + const normalizedTarget = target_percentage === null || target_percentage === undefined || target_percentage === "" + ? null + : Number(target_percentage); + + return authenticatedRequest( + "/departments/daily-reports/product-targets", + "PUT", + { + department_id: parseInt(department_id, 10), + product_id: parseInt(product_id, 10), + target_percentage: normalizedTarget, + } + ); + }, createComplaint: async ({ department_id, customer_number = null, diff --git a/src/i18n/generated/da-v2.json b/src/i18n/generated/da-v2.json index 58dbdf12..63e231e1 100644 --- a/src/i18n/generated/da-v2.json +++ b/src/i18n/generated/da-v2.json @@ -1517,6 +1517,14 @@ "subtitle_suffix": "@:{'words.generated.salg'} @:{'words.generated.af'} produktet", "title": "Produktsalg" }, + "product_targets": { + "cancel_aria": "Annuller mål", + "edit_aria": "Rediger målprocent", + "input_label": "Målprocent", + "save_aria": "Gem mål", + "save_error": "Målet kunne ikke gemmes.", + "validation_range": "Målprocenten skal være mellem 0 og 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'words.generated.antal'} @:{'words.generated.fuldførte'}", diff --git a/src/i18n/generated/de-v2.json b/src/i18n/generated/de-v2.json index 147d89c3..0eea276f 100644 --- a/src/i18n/generated/de-v2.json +++ b/src/i18n/generated/de-v2.json @@ -1628,6 +1628,14 @@ "subtitle_suffix": "Produktverk?@:{'words.generated.ufe'}", "title": "@:{'templates.generated.compat.admin.daily_report.product_sales.subtitle_suffix'}" }, + "product_targets": { + "cancel_aria": "Ziel abbrechen", + "edit_aria": "Zielprozentsatz bearbeiten", + "input_label": "Zielprozentsatz", + "save_aria": "Ziel speichern", + "save_error": "Das Ziel konnte nicht gespeichert werden.", + "validation_range": "Der Zielprozentsatz muss zwischen 0 und 100 liegen." + }, "reports": { "bookings": { "completed_count": "@:{'words.generated.abgeschlossene'} @:{'words.generated.anzahl'}", diff --git a/src/i18n/generated/en-v2.json b/src/i18n/generated/en-v2.json index a536a7b8..b026f3a6 100644 --- a/src/i18n/generated/en-v2.json +++ b/src/i18n/generated/en-v2.json @@ -1349,6 +1349,14 @@ "subtitle_suffix": "@:{'words.generated.product'} @:{'words.generated.sales'}", "title": "@.capitalize:{'words.generated.product'} @.capitalize:{'words.generated.sales'}" }, + "product_targets": { + "cancel_aria": "Cancel target", + "edit_aria": "Edit target percentage", + "input_label": "Target percentage", + "save_aria": "Save target", + "save_error": "The target could not be saved.", + "validation_range": "The target percentage must be between 0 and 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'words.generated.completed'} @:{'words.generated.count'}", diff --git a/src/i18n/generated/global-v2.json b/src/i18n/generated/global-v2.json index 65b8c952..014f119e 100644 --- a/src/i18n/generated/global-v2.json +++ b/src/i18n/generated/global-v2.json @@ -126,6 +126,14 @@ "subtitle_suffix": "@:{'templates.generated.compat.admin.daily_report.product_sales.subtitle_suffix'}", "title": "@:{'templates.generated.compat.admin.daily_report.product_sales.title'}" }, + "product_targets": { + "cancel_aria": "@:{'templates.generated.compat.admin.daily_report.product_targets.cancel_aria'}", + "edit_aria": "@:{'templates.generated.compat.admin.daily_report.product_targets.edit_aria'}", + "input_label": "@:{'templates.generated.compat.admin.daily_report.product_targets.input_label'}", + "save_aria": "@:{'templates.generated.compat.admin.daily_report.product_targets.save_aria'}", + "save_error": "@:{'templates.generated.compat.admin.daily_report.product_targets.save_error'}", + "validation_range": "@:{'templates.generated.compat.admin.daily_report.product_targets.validation_range'}" + }, "reports": { "avg_revenue": { "label": "@:{'templates.generated.compat.department_reports.avg_revenue_per_wash'}", diff --git a/src/i18n/generated/no-v2.json b/src/i18n/generated/no-v2.json index 5daf0a2d..06d031fe 100644 --- a/src/i18n/generated/no-v2.json +++ b/src/i18n/generated/no-v2.json @@ -1631,6 +1631,14 @@ "subtitle_suffix": "@:{'words.generated.produktsalg'}", "title": "@.capitalize:{'words.generated.produktsalg'}" }, + "product_targets": { + "cancel_aria": "Avbryt mål", + "edit_aria": "Rediger målprosent", + "input_label": "Målprosent", + "save_aria": "Lagre mål", + "save_error": "Målet kunne ikke lagres.", + "validation_range": "Målprosenten må være mellom 0 og 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'words.generated.fullført'} @:{'words.generated.telling'}", diff --git a/src/i18n/generated/sv-v2.json b/src/i18n/generated/sv-v2.json index 73d28513..30728fd2 100644 --- a/src/i18n/generated/sv-v2.json +++ b/src/i18n/generated/sv-v2.json @@ -1681,6 +1681,14 @@ "subtitle_suffix": "@:{'words.generated.produktforsaljning'}", "title": "@.capitalize:{'words.generated.produktforsaljning'}" }, + "product_targets": { + "cancel_aria": "Avbryt mål", + "edit_aria": "Redigera målprocent", + "input_label": "Målprocent", + "save_aria": "Spara mål", + "save_error": "Målet kunde inte sparas.", + "validation_range": "Målprocenten måste vara mellan 0 och 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'words.generated.antal'} @:{'words.generated.slutforda'}", diff --git a/src/i18n/source/da/phrases/compat/admin/index-01.json b/src/i18n/source/da/phrases/compat/admin/index-01.json index f5faeee1..69fd6aa0 100644 --- a/src/i18n/source/da/phrases/compat/admin/index-01.json +++ b/src/i18n/source/da/phrases/compat/admin/index-01.json @@ -27,6 +27,14 @@ "subtitle_suffix": "@:{'terms.glossary.salg'} @:{'terms.glossary.af'} produktet", "title": "Produktsalg" }, + "product_targets": { + "cancel_aria": "Annuller mål", + "edit_aria": "Rediger målprocent", + "input_label": "Målprocent", + "save_aria": "Gem mål", + "save_error": "Målet kunne ikke gemmes.", + "validation_range": "Målprocenten skal være mellem 0 og 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'terms.glossary.antal'} @:{'terms.glossary.fuldførte'}", diff --git a/src/i18n/source/de/phrases/compat/admin/index-01.json b/src/i18n/source/de/phrases/compat/admin/index-01.json index 0660ae9a..55c87a87 100644 --- a/src/i18n/source/de/phrases/compat/admin/index-01.json +++ b/src/i18n/source/de/phrases/compat/admin/index-01.json @@ -27,6 +27,14 @@ "subtitle_suffix": "Produktverk?@:{'terms.glossary.ufe'}", "title": "@:{'phrases.compat.admin.daily_report.product_sales.subtitle_suffix'}" }, + "product_targets": { + "cancel_aria": "Ziel abbrechen", + "edit_aria": "Zielprozentsatz bearbeiten", + "input_label": "Zielprozentsatz", + "save_aria": "Ziel speichern", + "save_error": "Das Ziel konnte nicht gespeichert werden.", + "validation_range": "Der Zielprozentsatz muss zwischen 0 und 100 liegen." + }, "reports": { "bookings": { "completed_count": "@:{'terms.glossary.abgeschlossene'} @:{'terms.glossary.anzahl'}", diff --git a/src/i18n/source/en/phrases/compat/admin/index-01.json b/src/i18n/source/en/phrases/compat/admin/index-01.json index 32456e32..ec526c5f 100644 --- a/src/i18n/source/en/phrases/compat/admin/index-01.json +++ b/src/i18n/source/en/phrases/compat/admin/index-01.json @@ -27,6 +27,14 @@ "subtitle_suffix": "@:{'terms.glossary.product'} @:{'terms.glossary.sales'}", "title": "@.capitalize:{'terms.glossary.product'} @.capitalize:{'terms.glossary.sales'}" }, + "product_targets": { + "cancel_aria": "Cancel target", + "edit_aria": "Edit target percentage", + "input_label": "Target percentage", + "save_aria": "Save target", + "save_error": "The target could not be saved.", + "validation_range": "The target percentage must be between 0 and 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'terms.glossary.completed'} @:{'terms.glossary.count'}", diff --git a/src/i18n/source/global/shared/admin/index-01.json b/src/i18n/source/global/shared/admin/index-01.json index 9d7f2e12..e0f28a54 100644 --- a/src/i18n/source/global/shared/admin/index-01.json +++ b/src/i18n/source/global/shared/admin/index-01.json @@ -34,6 +34,14 @@ "subtitle_suffix": "@:{'phrases.compat.admin.daily_report.product_sales.subtitle_suffix'}", "title": "@:{'phrases.compat.admin.daily_report.product_sales.title'}" }, + "product_targets": { + "cancel_aria": "@:{'phrases.compat.admin.daily_report.product_targets.cancel_aria'}", + "edit_aria": "@:{'phrases.compat.admin.daily_report.product_targets.edit_aria'}", + "input_label": "@:{'phrases.compat.admin.daily_report.product_targets.input_label'}", + "save_aria": "@:{'phrases.compat.admin.daily_report.product_targets.save_aria'}", + "save_error": "@:{'phrases.compat.admin.daily_report.product_targets.save_error'}", + "validation_range": "@:{'phrases.compat.admin.daily_report.product_targets.validation_range'}" + }, "reports": { "avg_revenue": { "label": "@:{'phrases.compat.department_reports.avg_revenue_per_wash'}", diff --git a/src/i18n/source/no/phrases/compat/admin/index-01.json b/src/i18n/source/no/phrases/compat/admin/index-01.json index 36985838..959184c9 100644 --- a/src/i18n/source/no/phrases/compat/admin/index-01.json +++ b/src/i18n/source/no/phrases/compat/admin/index-01.json @@ -27,6 +27,14 @@ "subtitle_suffix": "@:{'terms.glossary.produktsalg'}", "title": "@.capitalize:{'terms.glossary.produktsalg'}" }, + "product_targets": { + "cancel_aria": "Avbryt mål", + "edit_aria": "Rediger målprosent", + "input_label": "Målprosent", + "save_aria": "Lagre mål", + "save_error": "Målet kunne ikke lagres.", + "validation_range": "Målprosenten må være mellom 0 og 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'terms.glossary.fullført'} @:{'terms.glossary.telling'}", diff --git a/src/i18n/source/sv/phrases/compat/admin/index-01.json b/src/i18n/source/sv/phrases/compat/admin/index-01.json index d83f0883..cf44cb3e 100644 --- a/src/i18n/source/sv/phrases/compat/admin/index-01.json +++ b/src/i18n/source/sv/phrases/compat/admin/index-01.json @@ -27,6 +27,14 @@ "subtitle_suffix": "@:{'terms.glossary.produktforsaljning'}", "title": "@.capitalize:{'terms.glossary.produktforsaljning'}" }, + "product_targets": { + "cancel_aria": "Avbryt mål", + "edit_aria": "Redigera målprocent", + "input_label": "Målprocent", + "save_aria": "Spara mål", + "save_error": "Målet kunde inte sparas.", + "validation_range": "Målprocenten måste vara mellan 0 och 100." + }, "reports": { "bookings": { "completed_count": "@.capitalize:{'terms.glossary.antal'} @:{'terms.glossary.slutforda'}", diff --git a/src/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue b/src/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue index 47a567db..bbd63ade 100644 --- a/src/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue +++ b/src/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue @@ -77,6 +77,15 @@ const normalizeMetricState = (metric, fallbackState = "ready", fallbackMessage = message: metric?.message || fallbackMessage, }); +const normalizeNullableNumber = (value) => { + if (value === null || value === undefined || value === "") { + return null; + } + + const normalized = Number(value); + return Number.isFinite(normalized) ? normalized : null; +}; + const resetOverviewState = () => { count_transactions.value = 0; count_products_sold.value = 0; @@ -149,6 +158,8 @@ const applyOverviewResponse = (overview) => { value: Number(product.value || 0), out_of: Number(product.out_of || 0), state: product.state || "ready", + target_percentage: normalizeNullableNumber(product.target_percentage), + target_department_id: normalizeNullableNumber(product.target_department_id), }, ]) ); diff --git a/src/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportCount.vue b/src/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportCount.vue index 81da74de..3df798a0 100644 --- a/src/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportCount.vue +++ b/src/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportCount.vue @@ -1,5 +1,5 @@