shutro-erp 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account_portal/.kosh/bindings.js +1 -1
- package/dist/account_portal/.kosh/schema.d.ts +54 -3
- package/dist/account_portal/module.d.ts +359 -6
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +54 -3
- package/dist/accounting/index.js +214 -2
- package/dist/accounting/module.d.ts +217 -5
- package/dist/accounting/module.js +23 -2
- package/dist/calendar/.kosh/bindings.d.ts +22 -0
- package/dist/calendar/.kosh/bindings.js +15 -0
- package/dist/calendar/.kosh/schema.d.ts +795 -0
- package/dist/calendar/demo.d.ts +6 -0
- package/dist/calendar/demo.js +24 -0
- package/dist/calendar/index.d.ts +3 -0
- package/dist/calendar/index.js +291 -0
- package/dist/calendar/module.d.ts +1705 -0
- package/dist/calendar/module.js +180 -0
- package/dist/cli/init.js +48 -0
- package/dist/cli/templates.d.ts +1 -1
- package/dist/cli/templates.js +137 -0
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +78 -3
- package/dist/contacts/demo.js +30 -1
- package/dist/contacts/index.d.ts +1 -1
- package/dist/contacts/index.js +155 -9
- package/dist/contacts/module.d.ts +338 -2
- package/dist/contacts/module.js +82 -5
- package/dist/crm/.kosh/bindings.d.ts +22 -0
- package/dist/crm/.kosh/bindings.js +15 -0
- package/dist/crm/.kosh/schema.d.ts +799 -0
- package/dist/crm/demo.d.ts +6 -0
- package/dist/crm/demo.js +45 -0
- package/dist/crm/index.d.ts +3 -0
- package/dist/crm/index.js +361 -0
- package/dist/crm/module.d.ts +1843 -0
- package/dist/crm/module.js +207 -0
- package/dist/event/.kosh/bindings.d.ts +22 -0
- package/dist/event/.kosh/bindings.js +15 -0
- package/dist/event/.kosh/schema.d.ts +843 -0
- package/dist/event/demo.d.ts +6 -0
- package/dist/event/demo.js +65 -0
- package/dist/event/index.d.ts +14 -0
- package/dist/event/index.js +333 -0
- package/dist/event/mails.d.ts +15 -0
- package/dist/event/mails.js +112 -0
- package/dist/event/module.d.ts +2800 -0
- package/dist/event/module.js +323 -0
- package/dist/event/web/CheckInBox.d.ts +9 -0
- package/dist/event/web/CheckInBox.js +87 -0
- package/dist/event/web/index.d.ts +11 -0
- package/dist/event/web/index.js +16 -0
- package/dist/event/web/messages.d.ts +2 -0
- package/dist/event/web/messages.js +23 -0
- package/dist/event_account/.kosh/bindings.d.ts +22 -0
- package/dist/event_account/.kosh/bindings.js +15 -0
- package/dist/event_account/.kosh/schema.d.ts +1639 -0
- package/dist/event_account/index.d.ts +3 -0
- package/dist/event_account/index.js +28 -0
- package/dist/event_account/module.d.ts +6968 -0
- package/dist/event_account/module.js +90 -0
- package/dist/hr/.kosh/bindings.d.ts +22 -0
- package/dist/hr/.kosh/bindings.js +15 -0
- package/dist/hr/.kosh/schema.d.ts +878 -0
- package/dist/hr/demo.d.ts +6 -0
- package/dist/hr/demo.js +60 -0
- package/dist/hr/index.d.ts +3 -0
- package/dist/hr/index.js +483 -0
- package/dist/hr/module.d.ts +2611 -0
- package/dist/hr/module.js +350 -0
- package/dist/l10n_bd/.kosh/bindings.js +1 -1
- package/dist/l10n_bd/.kosh/schema.d.ts +54 -3
- package/dist/l10n_bd/module.d.ts +359 -6
- package/dist/l10n_in/.kosh/bindings.js +1 -1
- package/dist/l10n_in/.kosh/schema.d.ts +54 -3
- package/dist/l10n_in/module.d.ts +380 -7
- package/dist/maintenance/.kosh/bindings.d.ts +22 -0
- package/dist/maintenance/.kosh/bindings.js +15 -0
- package/dist/maintenance/.kosh/schema.d.ts +911 -0
- package/dist/maintenance/demo.d.ts +6 -0
- package/dist/maintenance/demo.js +60 -0
- package/dist/maintenance/index.d.ts +3 -0
- package/dist/maintenance/index.js +422 -0
- package/dist/maintenance/module.d.ts +3545 -0
- package/dist/maintenance/module.js +446 -0
- package/dist/maintenance_stock/.kosh/bindings.d.ts +22 -0
- package/dist/maintenance_stock/.kosh/bindings.js +15 -0
- package/dist/maintenance_stock/.kosh/schema.d.ts +1371 -0
- package/dist/maintenance_stock/index.d.ts +9 -0
- package/dist/maintenance_stock/index.js +52 -0
- package/dist/maintenance_stock/module.d.ts +6953 -0
- package/dist/maintenance_stock/module.js +192 -0
- package/dist/modules-sdk/index.d.ts +1 -1
- package/dist/modules-sdk/manifest.d.ts +70 -0
- package/dist/modules-sdk/manifest.js +35 -1
- package/dist/mrp/.kosh/bindings.d.ts +22 -0
- package/dist/mrp/.kosh/bindings.js +15 -0
- package/dist/mrp/.kosh/schema.d.ts +1378 -0
- package/dist/mrp/demo.d.ts +7 -0
- package/dist/mrp/demo.js +42 -0
- package/dist/mrp/index.d.ts +3 -0
- package/dist/mrp/index.js +392 -0
- package/dist/mrp/module.d.ts +8608 -0
- package/dist/mrp/module.js +695 -0
- package/dist/mrp_account/.kosh/bindings.d.ts +22 -0
- package/dist/mrp_account/.kosh/bindings.js +15 -0
- package/dist/mrp_account/.kosh/schema.d.ts +2106 -0
- package/dist/mrp_account/index.d.ts +3 -0
- package/dist/mrp_account/index.js +22 -0
- package/dist/mrp_account/module.d.ts +20840 -0
- package/dist/mrp_account/module.js +123 -0
- package/dist/payment/.kosh/bindings.js +1 -1
- package/dist/payment/.kosh/schema.d.ts +54 -3
- package/dist/payment/module.d.ts +359 -6
- package/dist/portal/.kosh/bindings.js +1 -1
- package/dist/portal/.kosh/schema.d.ts +78 -3
- package/dist/portal/module.d.ts +166 -2
- package/dist/pos/.kosh/bindings.d.ts +22 -0
- package/dist/pos/.kosh/bindings.js +15 -0
- package/dist/pos/.kosh/schema.d.ts +1690 -0
- package/dist/pos/accounting.d.ts +10 -0
- package/dist/pos/accounting.js +149 -0
- package/dist/pos/demo.d.ts +6 -0
- package/dist/pos/demo.js +90 -0
- package/dist/pos/index.d.ts +9 -0
- package/dist/pos/index.js +349 -0
- package/dist/pos/module.d.ts +9636 -0
- package/dist/pos/module.js +423 -0
- package/dist/pos/routes.d.ts +39 -0
- package/dist/pos/routes.js +368 -0
- package/dist/pos/web/Register.d.ts +9 -0
- package/dist/pos/web/Register.js +252 -0
- package/dist/pos/web/index.d.ts +11 -0
- package/dist/pos/web/index.js +16 -0
- package/dist/pos/web/messages.d.ts +2 -0
- package/dist/pos/web/messages.js +87 -0
- package/dist/pos_stock/.kosh/bindings.d.ts +22 -0
- package/dist/pos_stock/.kosh/bindings.js +15 -0
- package/dist/pos_stock/.kosh/schema.d.ts +2015 -0
- package/dist/pos_stock/index.d.ts +9 -0
- package/dist/pos_stock/index.js +35 -0
- package/dist/pos_stock/module.d.ts +11935 -0
- package/dist/pos_stock/module.js +115 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +51 -1
- package/dist/product/index.js +81 -4
- package/dist/product/module.d.ts +43 -1
- package/dist/product/module.js +9 -2
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +54 -3
- package/dist/project/index.js +147 -7
- package/dist/project/module.d.ts +210 -2
- package/dist/project/module.js +52 -5
- package/dist/project_portal/.kosh/bindings.js +1 -1
- package/dist/project_portal/.kosh/schema.d.ts +54 -3
- package/dist/project_portal/module.d.ts +353 -4
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +54 -3
- package/dist/purchase/index.js +163 -3
- package/dist/purchase/module.d.ts +215 -3
- package/dist/purchase/module.js +21 -3
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +55 -3
- package/dist/purchase_account/index.js +26 -1
- package/dist/purchase_account/module.d.ts +393 -7
- package/dist/purchase_portal/.kosh/bindings.js +1 -1
- package/dist/purchase_portal/.kosh/schema.d.ts +54 -3
- package/dist/purchase_portal/module.d.ts +366 -5
- package/dist/purchase_stock/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_stock/.kosh/bindings.js +15 -0
- package/dist/purchase_stock/.kosh/schema.d.ts +1310 -0
- package/dist/purchase_stock/index.d.ts +7 -0
- package/dist/purchase_stock/index.js +91 -0
- package/dist/purchase_stock/module.d.ts +6763 -0
- package/dist/purchase_stock/module.js +144 -0
- package/dist/purchase_stock_account/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_stock_account/.kosh/bindings.js +15 -0
- package/dist/purchase_stock_account/.kosh/schema.d.ts +1959 -0
- package/dist/purchase_stock_account/index.d.ts +3 -0
- package/dist/purchase_stock_account/index.js +24 -0
- package/dist/purchase_stock_account/module.d.ts +35366 -0
- package/dist/purchase_stock_account/module.js +162 -0
- package/dist/recruitment/.kosh/bindings.d.ts +22 -0
- package/dist/recruitment/.kosh/bindings.js +15 -0
- package/dist/recruitment/.kosh/schema.d.ts +990 -0
- package/dist/recruitment/demo.d.ts +6 -0
- package/dist/recruitment/demo.js +45 -0
- package/dist/recruitment/index.d.ts +3 -0
- package/dist/recruitment/index.js +385 -0
- package/dist/recruitment/module.d.ts +3247 -0
- package/dist/recruitment/module.js +193 -0
- package/dist/recruitment/web/HireButton.d.ts +9 -0
- package/dist/recruitment/web/HireButton.js +32 -0
- package/dist/recruitment/web/index.d.ts +11 -0
- package/dist/recruitment/web/index.js +16 -0
- package/dist/recruitment/web/messages.d.ts +2 -0
- package/dist/recruitment/web/messages.js +19 -0
- package/dist/reporting/documents.d.ts +5 -0
- package/dist/reporting/documents.js +28 -1
- package/dist/reporting/index.d.ts +1 -1
- package/dist/reporting/index.js +4 -2
- package/dist/reporting/models.js +9 -0
- package/dist/reporting/overviews.d.ts +16 -0
- package/dist/reporting/overviews.js +56 -0
- package/dist/reporting/routes-documents.js +1 -1
- package/dist/reporting/routes.js +138 -80
- package/dist/reporting/web/DashboardView.d.ts +11 -0
- package/dist/reporting/web/DashboardView.js +2 -2
- package/dist/reporting/web/DesignerView.js +5 -0
- package/dist/reporting/web/Overview.d.ts +9 -0
- package/dist/reporting/web/Overview.js +53 -0
- package/dist/reporting/web/index.d.ts +1 -0
- package/dist/reporting/web/index.js +4 -1
- package/dist/reporting/web/messages.js +9 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +59 -3
- package/dist/sale_account/index.js +22 -1
- package/dist/sale_account/module.d.ts +461 -7
- package/dist/sale_portal/.kosh/bindings.js +1 -1
- package/dist/sale_portal/.kosh/schema.d.ts +58 -3
- package/dist/sale_portal/module.d.ts +434 -5
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +60 -3
- package/dist/sale_project/module.d.ts +476 -6
- package/dist/sale_stock/.kosh/bindings.d.ts +22 -0
- package/dist/sale_stock/.kosh/bindings.js +15 -0
- package/dist/sale_stock/.kosh/schema.d.ts +1228 -0
- package/dist/sale_stock/index.d.ts +7 -0
- package/dist/sale_stock/index.js +84 -0
- package/dist/sale_stock/module.d.ts +6791 -0
- package/dist/sale_stock/module.js +146 -0
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +58 -3
- package/dist/sales/index.js +191 -4
- package/dist/sales/module.d.ts +355 -3
- package/dist/sales/module.js +78 -3
- package/dist/server/api/metadata.d.ts +5 -0
- package/dist/server/api/metadata.js +5 -0
- package/dist/server/api/rest.js +64 -1
- package/dist/server/api/views.js +1 -0
- package/dist/server/app-settings.d.ts +7 -0
- package/dist/server/app-settings.js +10 -0
- package/dist/server/auth/base.js +9 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/settings/app-settings.d.ts +2 -0
- package/dist/settings/app-settings.js +518 -0
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/people.js +29 -5
- package/dist/settings/web/AppSettingsPage.d.ts +11 -0
- package/dist/settings/web/AppSettingsPage.js +210 -0
- package/dist/settings/web/OverviewPage.js +16 -3
- package/dist/settings/web/PeoplePage.js +20 -6
- package/dist/settings/web/index.d.ts +1 -0
- package/dist/settings/web/index.js +3 -0
- package/dist/settings/web/messages.js +118 -2
- package/dist/stock/.kosh/bindings.d.ts +22 -0
- package/dist/stock/.kosh/bindings.js +15 -0
- package/dist/stock/.kosh/schema.d.ts +1147 -0
- package/dist/stock/demo.d.ts +7 -0
- package/dist/stock/demo.js +55 -0
- package/dist/stock/index.d.ts +3 -0
- package/dist/stock/index.js +674 -0
- package/dist/stock/module.d.ts +6330 -0
- package/dist/stock/module.js +1325 -0
- package/dist/stock/web/ScanBox.d.ts +8 -0
- package/dist/stock/web/ScanBox.js +90 -0
- package/dist/stock/web/index.d.ts +11 -0
- package/dist/stock/web/index.js +16 -0
- package/dist/stock/web/messages.d.ts +2 -0
- package/dist/stock/web/messages.js +25 -0
- package/dist/stock_account/.kosh/bindings.d.ts +22 -0
- package/dist/stock_account/.kosh/bindings.js +15 -0
- package/dist/stock_account/.kosh/schema.d.ts +1875 -0
- package/dist/stock_account/index.d.ts +3 -0
- package/dist/stock_account/index.js +130 -0
- package/dist/stock_account/module.d.ts +10655 -0
- package/dist/stock_account/module.js +339 -0
- package/dist/storage/attachments.d.ts +5 -0
- package/dist/storage/attachments.js +12 -1
- package/dist/storage/models.js +3 -0
- package/dist/storage/routes.js +13 -2
- package/dist/todo/.kosh/bindings.d.ts +22 -0
- package/dist/todo/.kosh/bindings.js +15 -0
- package/dist/todo/.kosh/schema.d.ts +732 -0
- package/dist/todo/demo.d.ts +6 -0
- package/dist/todo/demo.js +35 -0
- package/dist/todo/index.d.ts +3 -0
- package/dist/todo/index.js +187 -0
- package/dist/todo/module.d.ts +1180 -0
- package/dist/todo/module.js +130 -0
- package/dist/trade_finance/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance/.kosh/bindings.js +15 -0
- package/dist/trade_finance/.kosh/schema.d.ts +884 -0
- package/dist/trade_finance/demo.d.ts +6 -0
- package/dist/trade_finance/demo.js +63 -0
- package/dist/trade_finance/index.d.ts +3 -0
- package/dist/trade_finance/index.js +413 -0
- package/dist/trade_finance/module.d.ts +3779 -0
- package/dist/trade_finance/module.js +505 -0
- package/dist/trade_finance_account/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_account/.kosh/bindings.js +15 -0
- package/dist/trade_finance_account/.kosh/schema.d.ts +1711 -0
- package/dist/trade_finance_account/index.d.ts +3 -0
- package/dist/trade_finance_account/index.js +92 -0
- package/dist/trade_finance_account/module.d.ts +7928 -0
- package/dist/trade_finance_account/module.js +244 -0
- package/dist/trade_finance_purchase/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_purchase/.kosh/bindings.js +15 -0
- package/dist/trade_finance_purchase/.kosh/schema.d.ts +1153 -0
- package/dist/trade_finance_purchase/index.d.ts +8 -0
- package/dist/trade_finance_purchase/index.js +50 -0
- package/dist/trade_finance_purchase/module.d.ts +4252 -0
- package/dist/trade_finance_purchase/module.js +44 -0
- package/dist/trade_finance_sale/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_sale/.kosh/bindings.js +15 -0
- package/dist/trade_finance_sale/.kosh/schema.d.ts +1071 -0
- package/dist/trade_finance_sale/index.d.ts +8 -0
- package/dist/trade_finance_sale/index.js +50 -0
- package/dist/trade_finance_sale/module.d.ts +4280 -0
- package/dist/trade_finance_sale/module.js +44 -0
- package/dist/ui/api.d.ts +2 -1
- package/dist/ui/api.js +2 -1
- package/dist/ui/app.js +2 -1
- package/dist/ui/components/AppIcon.js +23 -1
- package/dist/ui/components/primitives.d.ts +2 -0
- package/dist/ui/components/primitives.js +7 -1
- package/dist/ui/i18n/messages.d.ts +23 -0
- package/dist/ui/i18n/messages.js +38 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/offline/queue.d.ts +5 -1
- package/dist/ui/offline/queue.js +2 -0
- package/dist/ui/registry.d.ts +5 -0
- package/dist/ui/shell/Home.js +55 -4
- package/dist/ui/views/FormView.js +98 -17
- package/dist/ui/views/KanbanView.d.ts +2 -0
- package/dist/ui/views/KanbanView.js +32 -3
- package/dist/ui/views/ListView.js +108 -11
- package/dist/ui/views/StatsRow.d.ts +14 -0
- package/dist/ui/views/StatsRow.js +88 -0
- package/dist/ui/views/widgets.d.ts +2 -1
- package/dist/ui/views/widgets.js +194 -1
- package/dist/views/builders.d.ts +19 -2
- package/dist/views/builders.js +21 -1
- package/dist/views/condition.d.ts +6 -0
- package/dist/views/condition.js +20 -0
- package/dist/views/documents.d.ts +8 -0
- package/dist/views/extend.d.ts +2 -2
- package/dist/views/extend.js +11 -1
- package/dist/views/index.d.ts +2 -2
- package/dist/views/index.js +2 -2
- package/dist/views/types.d.ts +52 -5
- package/dist/views/validate.js +39 -1
- package/dist/website/.kosh/bindings.d.ts +22 -0
- package/dist/website/.kosh/bindings.js +15 -0
- package/dist/website/.kosh/schema.d.ts +881 -0
- package/dist/website/demo.d.ts +6 -0
- package/dist/website/demo.js +115 -0
- package/dist/website/index.d.ts +9 -0
- package/dist/website/index.js +250 -0
- package/dist/website/module.d.ts +2987 -0
- package/dist/website/module.js +281 -0
- package/dist/website/render.d.ts +45 -0
- package/dist/website/render.js +102 -0
- package/dist/website/routes.d.ts +12 -0
- package/dist/website/routes.js +246 -0
- package/dist/website/web/PagePreview.d.ts +7 -0
- package/dist/website/web/PagePreview.js +33 -0
- package/dist/website/web/index.d.ts +11 -0
- package/dist/website/web/index.js +16 -0
- package/dist/website/web/messages.d.ts +2 -0
- package/dist/website/web/messages.js +15 -0
- package/dist/website_crm/.kosh/bindings.d.ts +22 -0
- package/dist/website_crm/.kosh/bindings.js +15 -0
- package/dist/website_crm/.kosh/schema.d.ts +966 -0
- package/dist/website_crm/index.d.ts +3 -0
- package/dist/website_crm/index.js +39 -0
- package/dist/website_crm/module.d.ts +2955 -0
- package/dist/website_crm/module.js +44 -0
- package/package.json +118 -1
package/dist/reporting/routes.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* - `POST /api/v1/models/:model/export/xlsx` a list as a formatted XLSX (needs `export`; audited)
|
|
7
7
|
*/
|
|
8
8
|
import { audit, HttpProblem, publicApiModels, } from '../server/index.js';
|
|
9
|
+
import { renderPdf } from './documents.js';
|
|
9
10
|
import { ReportError, runReport, } from './pivot.js';
|
|
10
11
|
import { moneyFormat, workbook } from './xlsx.js';
|
|
11
12
|
const LIST_XLSX_LIMIT = 100_000;
|
|
13
|
+
const LIST_PDF_LIMIT = 2000;
|
|
12
14
|
const search = (env, model, query) => env.model(model).search(query);
|
|
13
15
|
function bad(detail) {
|
|
14
16
|
return new HttpProblem(400, 'invalid-report', 'Invalid report', detail);
|
|
@@ -220,91 +222,34 @@ export function registerReportRoutes(context) {
|
|
|
220
222
|
const limit = raw['limit'] === undefined ? LIST_XLSX_LIMIT : Number(raw['limit']);
|
|
221
223
|
if (!Number.isInteger(limit) || limit < 1 || limit > LIST_XLSX_LIMIT)
|
|
222
224
|
throw bad(`limit must be 1–${LIST_XLSX_LIMIT}.`);
|
|
223
|
-
const sheet = await request.env(
|
|
224
|
-
env.access.check(model.name, 'export');
|
|
225
|
-
const fields = (keys.length > 0
|
|
226
|
-
? keys
|
|
227
|
-
: model.fields
|
|
228
|
-
.filter((f) => f.stored &&
|
|
229
|
-
f.kind !== 'binary' &&
|
|
230
|
-
f.kind !== 'one2many' &&
|
|
231
|
-
f.kind !== 'many2many')
|
|
232
|
-
.map((f) => f.key))
|
|
233
|
-
.map((k) => model.byKey.get(k))
|
|
234
|
-
.filter((f) => f !== undefined && env.access.canField(model.name, f.key, 'read'));
|
|
235
|
-
const found = await search(env, model.name, {
|
|
236
|
-
...(where === undefined ? {} : { where }),
|
|
237
|
-
order: ['id asc'],
|
|
238
|
-
limit,
|
|
239
|
-
});
|
|
240
|
-
const loaded = found.length === 0
|
|
241
|
-
? []
|
|
242
|
-
: (await found.load(['id', ...fields.map((f) => f?.key)])).read(fields.map((f) => f?.key));
|
|
243
|
-
// many2one columns show names, not ids.
|
|
244
|
-
const names = new Map();
|
|
245
|
-
for (const f of fields)
|
|
246
|
-
if (f?.kind === 'many2one' && f.target !== null) {
|
|
247
|
-
const ids = [
|
|
248
|
-
...new Set(loaded.map((r) => r[f.key]).filter((v) => typeof v === 'number')),
|
|
249
|
-
];
|
|
250
|
-
names.set(f.key, ids.length === 0 ? new Map() : await displayNames(env, f.target, ids));
|
|
251
|
-
}
|
|
252
|
-
await audit(env, {
|
|
253
|
-
kind: 'data.export',
|
|
254
|
-
summary: `Exported ${loaded.length} ${model.name} record(s) as xlsx.`,
|
|
255
|
-
targetModel: model.name,
|
|
256
|
-
details: { fields: fields.map((f) => f?.key), count: loaded.length, filter: where ?? null },
|
|
257
|
-
ip: request.ip,
|
|
258
|
-
});
|
|
259
|
-
const header = fields.map((f) => f?.label ?? '');
|
|
260
|
-
const body = loaded.map((r) => fields.map((f) => {
|
|
261
|
-
if (f === undefined)
|
|
262
|
-
return null;
|
|
263
|
-
const v = r[f.key];
|
|
264
|
-
if (v === null || v === undefined)
|
|
265
|
-
return null;
|
|
266
|
-
switch (f.kind) {
|
|
267
|
-
case 'many2one':
|
|
268
|
-
return names.get(f.key)?.get(v) ?? `#${String(v)}`;
|
|
269
|
-
case 'decimal':
|
|
270
|
-
case 'float':
|
|
271
|
-
return { decimal: String(v) };
|
|
272
|
-
case 'monetary': {
|
|
273
|
-
const m = (typeof v.toJSON === 'function'
|
|
274
|
-
? v.toJSON()
|
|
275
|
-
: v);
|
|
276
|
-
return {
|
|
277
|
-
decimal: String(m.amount ?? v),
|
|
278
|
-
format: moneyFormat(String(m.currency ?? '')),
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
case 'date':
|
|
282
|
-
return new Date(`${String(v).slice(0, 10)}T00:00:00Z`);
|
|
283
|
-
case 'datetime':
|
|
284
|
-
return v instanceof Date ? v : new Date(String(v));
|
|
285
|
-
case 'selection':
|
|
286
|
-
case 'state':
|
|
287
|
-
return f.options?.find(([value]) => value === v)?.[1] ?? String(v);
|
|
288
|
-
case 'boolean':
|
|
289
|
-
return v === true;
|
|
290
|
-
case 'integer':
|
|
291
|
-
case 'bigint':
|
|
292
|
-
return Number(v);
|
|
293
|
-
default:
|
|
294
|
-
return String(v);
|
|
295
|
-
}
|
|
296
|
-
}));
|
|
297
|
-
return {
|
|
298
|
-
name: model.label,
|
|
299
|
-
rows: [header, ...body],
|
|
300
|
-
widths: fields.map((f) => Math.min(48, Math.max(10, (f?.label.length ?? 10) + 4))),
|
|
301
|
-
};
|
|
302
|
-
});
|
|
225
|
+
const sheet = await request.env((env) => listSheet(env, model, keys, where, limit, request.ip, 'xlsx'));
|
|
303
226
|
await reply
|
|
304
227
|
.type('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
|
305
228
|
.header('content-disposition', `attachment; filename="${model.name}.xlsx"`)
|
|
306
229
|
.send(workbook([sheet]));
|
|
307
230
|
});
|
|
231
|
+
// RFC 0022 §3.5: a list printed as PDF — the columns shown, the filter, up to LIST_PDF_LIMIT rows.
|
|
232
|
+
app.post('/api/v1/models/:model/export/pdf', async (request, reply) => {
|
|
233
|
+
const model = modelOf(request.params);
|
|
234
|
+
const raw = bodyOf(request);
|
|
235
|
+
const keys = strings(raw['fields'], 'fields');
|
|
236
|
+
const where = raw['filter'];
|
|
237
|
+
const limit = raw['limit'] === undefined ? LIST_PDF_LIMIT : Number(raw['limit']);
|
|
238
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > LIST_PDF_LIMIT)
|
|
239
|
+
throw bad(`limit must be 1–${LIST_PDF_LIMIT}.`);
|
|
240
|
+
const sheet = await request.env((env) => listSheet(env, model, keys, where, limit, request.ip, 'pdf'));
|
|
241
|
+
const lang = String(request.headers['accept-language'] ?? 'en').slice(0, 2);
|
|
242
|
+
const html = listHtml(sheet, { lang, dir: lang === 'ar' || lang === 'he' ? 'rtl' : 'ltr' });
|
|
243
|
+
const pdf = await renderPdf(html, {
|
|
244
|
+
paper: 'A4',
|
|
245
|
+
orientation: (sheet.rows[0]?.length ?? 0) > 6 ? 'landscape' : 'portrait',
|
|
246
|
+
blocks: [],
|
|
247
|
+
});
|
|
248
|
+
await reply
|
|
249
|
+
.type('application/pdf')
|
|
250
|
+
.header('content-disposition', `attachment; filename="${model.name}.pdf"`)
|
|
251
|
+
.send(pdf);
|
|
252
|
+
});
|
|
308
253
|
}
|
|
309
254
|
function csvCell(value) {
|
|
310
255
|
let t = value;
|
|
@@ -312,3 +257,116 @@ function csvCell(value) {
|
|
|
312
257
|
t = `'${t}`;
|
|
313
258
|
return /[",\r\n]/.test(t) ? `"${t.replaceAll('"', '""')}"` : t;
|
|
314
259
|
}
|
|
260
|
+
/** The rows of a list for an export: the columns the user may read, many2one names, typed cells; audited. */
|
|
261
|
+
async function listSheet(env, model, keys, where, limit, ip, format) {
|
|
262
|
+
env.access.check(model.name, 'export');
|
|
263
|
+
const fields = (keys.length > 0
|
|
264
|
+
? keys
|
|
265
|
+
: model.fields
|
|
266
|
+
.filter((f) => f.stored && f.kind !== 'binary' && f.kind !== 'one2many' && f.kind !== 'many2many')
|
|
267
|
+
.map((f) => f.key))
|
|
268
|
+
.map((k) => model.byKey.get(k))
|
|
269
|
+
.filter((f) => f !== undefined && env.access.canField(model.name, f.key, 'read'));
|
|
270
|
+
const found = await search(env, model.name, {
|
|
271
|
+
...(where === undefined ? {} : { where }),
|
|
272
|
+
order: ['id asc'],
|
|
273
|
+
limit,
|
|
274
|
+
});
|
|
275
|
+
const loaded = found.length === 0
|
|
276
|
+
? []
|
|
277
|
+
: (await found.load(['id', ...fields.map((f) => f?.key)])).read(fields.map((f) => f?.key));
|
|
278
|
+
// many2one columns show names, not ids.
|
|
279
|
+
const names = new Map();
|
|
280
|
+
for (const f of fields)
|
|
281
|
+
if (f?.kind === 'many2one' && f.target !== null) {
|
|
282
|
+
const ids = [
|
|
283
|
+
...new Set(loaded.map((r) => r[f.key]).filter((v) => typeof v === 'number')),
|
|
284
|
+
];
|
|
285
|
+
names.set(f.key, ids.length === 0 ? new Map() : await displayNames(env, f.target, ids));
|
|
286
|
+
}
|
|
287
|
+
await audit(env, {
|
|
288
|
+
kind: 'data.export',
|
|
289
|
+
summary: `Exported ${loaded.length} ${model.name} record(s) as ${format}.`,
|
|
290
|
+
targetModel: model.name,
|
|
291
|
+
details: { fields: fields.map((f) => f?.key), count: loaded.length, filter: where ?? null },
|
|
292
|
+
ip,
|
|
293
|
+
});
|
|
294
|
+
const header = fields.map((f) => f?.label ?? '');
|
|
295
|
+
const body = loaded.map((r) => fields.map((f) => {
|
|
296
|
+
if (f === undefined)
|
|
297
|
+
return null;
|
|
298
|
+
const v = r[f.key];
|
|
299
|
+
if (v === null || v === undefined)
|
|
300
|
+
return null;
|
|
301
|
+
switch (f.kind) {
|
|
302
|
+
case 'many2one':
|
|
303
|
+
return names.get(f.key)?.get(v) ?? `#${String(v)}`;
|
|
304
|
+
case 'decimal':
|
|
305
|
+
case 'float':
|
|
306
|
+
return { decimal: String(v) };
|
|
307
|
+
case 'monetary': {
|
|
308
|
+
const m = (typeof v.toJSON === 'function'
|
|
309
|
+
? v.toJSON()
|
|
310
|
+
: v);
|
|
311
|
+
return {
|
|
312
|
+
decimal: String(m.amount ?? v),
|
|
313
|
+
format: moneyFormat(String(m.currency ?? '')),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
case 'date':
|
|
317
|
+
return new Date(`${String(v).slice(0, 10)}T00:00:00Z`);
|
|
318
|
+
case 'datetime':
|
|
319
|
+
return v instanceof Date ? v : new Date(String(v));
|
|
320
|
+
case 'selection':
|
|
321
|
+
case 'state':
|
|
322
|
+
return f.options?.find(([value]) => value === v)?.[1] ?? String(v);
|
|
323
|
+
case 'boolean':
|
|
324
|
+
return v === true;
|
|
325
|
+
case 'integer':
|
|
326
|
+
case 'bigint':
|
|
327
|
+
return Number(v);
|
|
328
|
+
default:
|
|
329
|
+
return String(v);
|
|
330
|
+
}
|
|
331
|
+
}));
|
|
332
|
+
return {
|
|
333
|
+
name: model.label,
|
|
334
|
+
rows: [header, ...body],
|
|
335
|
+
widths: fields.map((f) => Math.min(48, Math.max(10, (f?.label.length ?? 10) + 4))),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const escapeHtml = (text) => text.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] ?? c);
|
|
339
|
+
/** A cell as text on paper. */
|
|
340
|
+
function cellText(cell) {
|
|
341
|
+
if (cell === null)
|
|
342
|
+
return '';
|
|
343
|
+
if (cell instanceof Date)
|
|
344
|
+
return cell.toISOString().slice(0, 10);
|
|
345
|
+
if (typeof cell === 'boolean')
|
|
346
|
+
return cell ? '✓' : '';
|
|
347
|
+
if (typeof cell === 'object')
|
|
348
|
+
return 'decimal' in cell ? cell.decimal : cell.text;
|
|
349
|
+
return String(cell);
|
|
350
|
+
}
|
|
351
|
+
/** A list as a printable page: the title, the count, a table with the header on every page. */
|
|
352
|
+
function listHtml(sheet, options) {
|
|
353
|
+
const [header = [], ...body] = sheet.rows;
|
|
354
|
+
const numeric = header.map((_, i) => body.every((row) => {
|
|
355
|
+
const c = row[i];
|
|
356
|
+
return (c === null ||
|
|
357
|
+
c === undefined ||
|
|
358
|
+
typeof c === 'number' ||
|
|
359
|
+
(typeof c === 'object' && 'decimal' in c));
|
|
360
|
+
}));
|
|
361
|
+
const th = header.map((h) => `<th>${escapeHtml(cellText(h))}</th>`).join('');
|
|
362
|
+
const tr = body
|
|
363
|
+
.map((row) => `<tr>${row.map((c, i) => `<td${numeric[i] ? ' class="n"' : ''}>${escapeHtml(cellText(c))}</td>`).join('')}</tr>`)
|
|
364
|
+
.join('\n');
|
|
365
|
+
return `<!doctype html><html lang="${escapeHtml(options.lang)}" dir="${options.dir}"><head><meta charset="utf-8"><title>${escapeHtml(sheet.name)}</title><style>
|
|
366
|
+
body{font:10pt/1.4 "Inter","Noto Sans Bengali",system-ui,sans-serif;color:#111;margin:0}
|
|
367
|
+
h1{font-size:14pt;margin:0 0 2mm}p{margin:0 0 4mm;color:#555}
|
|
368
|
+
table{width:100%;border-collapse:collapse}thead{display:table-header-group}
|
|
369
|
+
th,td{padding:1.2mm 2mm;border-bottom:0.2mm solid #ccc;text-align:start;vertical-align:top}
|
|
370
|
+
th{font-weight:600;background:#f3f4f6}td.n,th.n{text-align:end;font-variant-numeric:tabular-nums}
|
|
371
|
+
tr{break-inside:avoid}</style></head><body><h1>${escapeHtml(sheet.name)}</h1><p>${body.length}</p><table><thead><tr>${th}</tr></thead><tbody>${tr}</tbody></table></body></html>`;
|
|
372
|
+
}
|
|
@@ -20,4 +20,15 @@ export interface Card {
|
|
|
20
20
|
readonly w: 3 | 4 | 6 | 12;
|
|
21
21
|
}
|
|
22
22
|
export declare function measureOf(text: string): MeasureSpec;
|
|
23
|
+
export declare function Records({ model, domain, onClose, }: {
|
|
24
|
+
readonly model: string;
|
|
25
|
+
readonly domain: Condition;
|
|
26
|
+
readonly onClose: () => void;
|
|
27
|
+
}): ReactElement;
|
|
28
|
+
export declare function CardFrame({ card, editing, onRemove, onDrill, }: {
|
|
29
|
+
readonly card: Card;
|
|
30
|
+
readonly editing: boolean;
|
|
31
|
+
readonly onRemove: () => void;
|
|
32
|
+
readonly onDrill: (domain: Condition) => void;
|
|
33
|
+
}): ReactElement;
|
|
23
34
|
export declare function DashboardView(_props: ViewProps): ReactElement;
|
|
@@ -25,7 +25,7 @@ function thisMonth() {
|
|
|
25
25
|
const to = new Date(Date.UTC(now.getFullYear(), now.getMonth() + 1, 1));
|
|
26
26
|
return { from: from.toISOString().slice(0, 10), to: to.toISOString().slice(0, 10) };
|
|
27
27
|
}
|
|
28
|
-
function Records({ model, domain, onClose, }) {
|
|
28
|
+
export function Records({ model, domain, onClose, }) {
|
|
29
29
|
const { api } = useKosh();
|
|
30
30
|
const { t } = useTranslation('reporting');
|
|
31
31
|
const rows = useQuery({
|
|
@@ -90,7 +90,7 @@ const SPAN = {
|
|
|
90
90
|
6: 'lg:col-span-6',
|
|
91
91
|
12: 'lg:col-span-12',
|
|
92
92
|
};
|
|
93
|
-
function CardFrame({ card, editing, onRemove, onDrill, }) {
|
|
93
|
+
export function CardFrame({ card, editing, onRemove, onDrill, }) {
|
|
94
94
|
const { t } = useTranslation('reporting');
|
|
95
95
|
const drag = useDraggable({ id: card.id, disabled: !editing });
|
|
96
96
|
const drop = useDroppable({ id: card.id });
|
|
@@ -21,6 +21,7 @@ const KINDS = [
|
|
|
21
21
|
'divider',
|
|
22
22
|
'pageBreak',
|
|
23
23
|
'signature',
|
|
24
|
+
'qr',
|
|
24
25
|
];
|
|
25
26
|
function newBlock(kind, n) {
|
|
26
27
|
const id = `${kind}_${n}`;
|
|
@@ -39,6 +40,8 @@ function newBlock(kind, n) {
|
|
|
39
40
|
return { kind, id, left: [], right: [] };
|
|
40
41
|
case 'signature':
|
|
41
42
|
return { kind, id, labels: ['Prepared by', 'Approved by'] };
|
|
43
|
+
case 'qr':
|
|
44
|
+
return { kind, id, bind: 'name' };
|
|
42
45
|
default:
|
|
43
46
|
return { kind, id };
|
|
44
47
|
}
|
|
@@ -52,6 +55,8 @@ function BlockEditor({ block, onChange, }) {
|
|
|
52
55
|
return (_jsxs("div", { className: "grid gap-2", children: [text(t('designer.text'), block.text, (v) => onChange({ ...block, text: v })), _jsx(Select, { label: "Level", value: String(block.level ?? 1), options: ['1', '2', '3'].map((l) => ({ value: l, label: `H${l}` })), onChange: (v) => onChange({ ...block, level: Number(v) }) })] }));
|
|
53
56
|
case 'text':
|
|
54
57
|
return text(t('designer.text'), block.text, (v) => onChange({ ...block, text: v }), true);
|
|
58
|
+
case 'qr':
|
|
59
|
+
return (_jsxs("div", { className: "grid gap-2", children: [text(t('designer.bind'), block.bind, (v) => onChange({ ...block, bind: v })), text(t('designer.label'), block.label ?? '', (v) => onChange({ ...block, label: v === '' ? undefined : v }))] }));
|
|
55
60
|
case 'field':
|
|
56
61
|
return (_jsxs("div", { className: "grid gap-2", children: [text(t('designer.bind'), block.bind, (v) => onChange({ ...block, bind: v })), text(t('designer.label'), block.label ?? '', (v) => onChange({ ...block, label: v === '' ? undefined : v }))] }));
|
|
57
62
|
case 'fields':
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An app's overview page (RFC 0022 §3.2): the cards its manifest declared — KPIs with their change against the
|
|
3
|
+
* previous period, charts, small pivots, top records — opened by the app's first menu. The cards come from the
|
|
4
|
+
* overview route; each one queries the reports as the user (their rights and record rules apply), and a chart point
|
|
5
|
+
* or a pivot row opens the records behind it. Read-only: the layout belongs to the app (personal layouts: step 3).
|
|
6
|
+
*/
|
|
7
|
+
import { type PageProps } from '../../ui/index.js';
|
|
8
|
+
import { type ReactElement } from 'react';
|
|
9
|
+
export declare function Overview({ app }: PageProps): ReactElement;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* An app's overview page (RFC 0022 §3.2): the cards its manifest declared — KPIs with their change against the
|
|
4
|
+
* previous period, charts, small pivots, top records — opened by the app's first menu. The cards come from the
|
|
5
|
+
* overview route; each one queries the reports as the user (their rights and record rules apply), and a chart point
|
|
6
|
+
* or a pivot row opens the records behind it. Read-only: the layout belongs to the app (personal layouts: step 3).
|
|
7
|
+
*/
|
|
8
|
+
import { Alert, Button, Spinner, useKosh } from '../../ui/index.js';
|
|
9
|
+
import { useQuery } from '@tanstack/react-query';
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { useTranslation } from 'react-i18next';
|
|
12
|
+
import { CardFrame, Records } from './DashboardView.js';
|
|
13
|
+
export function Overview({ app }) {
|
|
14
|
+
const { api, uid } = useKosh();
|
|
15
|
+
const { t } = useTranslation('reporting');
|
|
16
|
+
const [drill, setDrill] = useState(null);
|
|
17
|
+
// RFC 0022 step 3: the cards a person hid, kept in their browser (like a list's columns).
|
|
18
|
+
const hiddenKey = `kosh.overview.${uid}.${app ?? ''}`;
|
|
19
|
+
const [hidden, setHidden] = useState(() => {
|
|
20
|
+
try {
|
|
21
|
+
const raw = localStorage.getItem(hiddenKey);
|
|
22
|
+
return raw === null ? [] : JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const [editing, setEditing] = useState(false);
|
|
29
|
+
const hide = (ids) => {
|
|
30
|
+
setHidden(ids);
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(hiddenKey, JSON.stringify(ids));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// storage unavailable: the choice lasts for the page
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const overview = useQuery({
|
|
39
|
+
queryKey: ['overview', app],
|
|
40
|
+
queryFn: () => api.call('GET', `/api/v1/reports/overview?app=${encodeURIComponent(app ?? '')}`),
|
|
41
|
+
enabled: app !== undefined,
|
|
42
|
+
});
|
|
43
|
+
if (overview.isError)
|
|
44
|
+
return _jsx(Alert, { children: overview.error.message });
|
|
45
|
+
if (overview.data === undefined)
|
|
46
|
+
return _jsx(Spinner, { label: t('overview') });
|
|
47
|
+
return (_jsxs("div", { className: "grid gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h2", { className: "text-base font-semibold", children: `${overview.data.label} · ${t('overview')}` }), _jsxs("div", { className: "ms-auto flex items-center gap-2", children: [hidden.length === 0 ? null : (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => hide([]), children: t('showAllCards', { count: hidden.length }) })), _jsx(Button, { size: "sm", onClick: () => setEditing((e) => !e), children: editing ? t('done') : t('customise') })] })] }), _jsx("div", { className: "grid grid-cols-12 gap-3", children: overview.data.cards
|
|
48
|
+
.filter((card) => editing || !hidden.includes(card.id))
|
|
49
|
+
.map((card) => (_jsx(CardFrame, { card: card, editing: editing && !hidden.includes(card.id), onRemove: () => hide([...hidden, card.id]), onDrill: (domain) => setDrill({
|
|
50
|
+
model: card.model,
|
|
51
|
+
domain: card.filter === undefined ? domain : { and: [card.filter, domain] },
|
|
52
|
+
}) }, card.id))) }), drill === null ? null : (_jsx(Records, { model: drill.model, domain: drill.domain, onClose: () => setDrill(null) }))] }));
|
|
53
|
+
}
|
|
@@ -17,6 +17,7 @@ export { GanttView, windowOf } from './GanttView.js';
|
|
|
17
17
|
export { chartData, GraphView, ReportChart, type ReportChartProps } from './GraphView.js';
|
|
18
18
|
export { HierarchyView } from './HierarchyView.js';
|
|
19
19
|
export { DEFAULT_MAP_STYLE, MapView } from './MapView.js';
|
|
20
|
+
export { Overview } from './Overview.js';
|
|
20
21
|
export { PivotView } from './PivotView.js';
|
|
21
22
|
export { SpreadsheetView } from './SpreadsheetView.js';
|
|
22
23
|
/** Registers the report views (and their messages in `i18n`, when given). */
|
|
@@ -15,6 +15,7 @@ import { GraphView } from './GraphView.js';
|
|
|
15
15
|
import { HierarchyView } from './HierarchyView.js';
|
|
16
16
|
import { MapView } from './MapView.js';
|
|
17
17
|
import { messages } from './messages.js';
|
|
18
|
+
import { Overview } from './Overview.js';
|
|
18
19
|
import { PivotView } from './PivotView.js';
|
|
19
20
|
import { SpreadsheetView } from './SpreadsheetView.js';
|
|
20
21
|
export { CalendarView, gridDays } from './CalendarView.js';
|
|
@@ -27,6 +28,7 @@ export { GanttView, windowOf } from './GanttView.js';
|
|
|
27
28
|
export { chartData, GraphView, ReportChart } from './GraphView.js';
|
|
28
29
|
export { HierarchyView } from './HierarchyView.js';
|
|
29
30
|
export { DEFAULT_MAP_STYLE, MapView } from './MapView.js';
|
|
31
|
+
export { Overview } from './Overview.js';
|
|
30
32
|
export { PivotView } from './PivotView.js';
|
|
31
33
|
export { SpreadsheetView } from './SpreadsheetView.js';
|
|
32
34
|
/** Registers the report views (and their messages in `i18n`, when given). */
|
|
@@ -43,7 +45,8 @@ export function registerReportingWeb(registry, i18n) {
|
|
|
43
45
|
.registerView('dashboard', DashboardView)
|
|
44
46
|
.registerView('designer', DesignerView)
|
|
45
47
|
.registerComponent('reporting.Documents', Documents)
|
|
46
|
-
.registerComponent('reporting.ScheduleOutputs', ScheduleOutputs)
|
|
48
|
+
.registerComponent('reporting.ScheduleOutputs', ScheduleOutputs)
|
|
49
|
+
.registerPage('reporting.Overview', Overview);
|
|
47
50
|
if (i18n !== undefined)
|
|
48
51
|
addMessages(i18n, 'reporting', messages);
|
|
49
52
|
return registry;
|
|
@@ -33,6 +33,10 @@ export const messages = {
|
|
|
33
33
|
churn: 'Closed',
|
|
34
34
|
newSheet: 'New spreadsheet',
|
|
35
35
|
newDashboard: 'New dashboard',
|
|
36
|
+
overview: 'Overview',
|
|
37
|
+
customise: 'Customise',
|
|
38
|
+
showAllCards_one: 'Show the {{count}} hidden card',
|
|
39
|
+
showAllCards_other: 'Show the {{count}} hidden cards',
|
|
36
40
|
name: 'Name',
|
|
37
41
|
save: 'Save',
|
|
38
42
|
saved: 'Saved',
|
|
@@ -87,6 +91,7 @@ export const messages = {
|
|
|
87
91
|
divider: 'Divider',
|
|
88
92
|
pageBreak: 'Page break',
|
|
89
93
|
signature: 'Signatures',
|
|
94
|
+
qr: 'QR code',
|
|
90
95
|
},
|
|
91
96
|
},
|
|
92
97
|
},
|
|
@@ -112,6 +117,10 @@ export const messages = {
|
|
|
112
117
|
next: 'পরের',
|
|
113
118
|
newSheet: 'নতুন স্প্রেডশিট',
|
|
114
119
|
newDashboard: 'নতুন ড্যাশবোর্ড',
|
|
120
|
+
overview: 'সারসংক্ষেপ',
|
|
121
|
+
customise: 'সাজান',
|
|
122
|
+
showAllCards_one: 'লুকানো {{count}}টি কার্ড দেখান',
|
|
123
|
+
showAllCards_other: 'লুকানো {{count}}টি কার্ড দেখান',
|
|
115
124
|
name: 'নাম',
|
|
116
125
|
save: 'সংরক্ষণ',
|
|
117
126
|
addCard: 'কার্ড যোগ করুন',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 6e014c4aaba6871d
|
|
2
2
|
// Bindings of module `sale_account`: import the definition API from here to get types scoped to what it can see.
|
|
3
3
|
export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
|
|
4
4
|
import { definitions } from '../../server/orm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 6e014c4aaba6871d
|
|
2
2
|
// Schema view of module `sale_account` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
|
|
3
3
|
import type { F } from '../../server/orm.js';
|
|
4
4
|
|
|
@@ -30,6 +30,7 @@ export interface SchemaView {
|
|
|
30
30
|
readonly 'auth.user_group': AuthUserGroup;
|
|
31
31
|
readonly 'kosh.image': KoshImage;
|
|
32
32
|
readonly 'kosh.saved_view': KoshSavedView;
|
|
33
|
+
readonly 'kosh.setting': KoshSetting;
|
|
33
34
|
readonly 'mail.activity': MailActivity;
|
|
34
35
|
readonly 'mail.activity.type': MailActivityType;
|
|
35
36
|
readonly 'mail.channel': MailChannel;
|
|
@@ -53,6 +54,7 @@ export interface SchemaView {
|
|
|
53
54
|
readonly 'res.currency': ResCurrency;
|
|
54
55
|
readonly 'res.currency.rate': ResCurrencyRate;
|
|
55
56
|
readonly 'res.partner': ResPartner;
|
|
57
|
+
readonly 'res.partner.tag': ResPartnerTag;
|
|
56
58
|
readonly 'res.users': ResUsers;
|
|
57
59
|
readonly 'sale.order': SaleOrder;
|
|
58
60
|
readonly 'sale.order.line': SaleOrderLine;
|
|
@@ -779,6 +781,26 @@ export interface KoshSavedView {
|
|
|
779
781
|
export interface KoshSavedViewMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'kosh.saved_view'> {
|
|
780
782
|
}
|
|
781
783
|
|
|
784
|
+
/** `kosh.setting` (owner: base). */
|
|
785
|
+
export interface KoshSetting {
|
|
786
|
+
readonly name: 'kosh.setting';
|
|
787
|
+
readonly fields: {
|
|
788
|
+
readonly id: F.Id<'kosh.setting'>;
|
|
789
|
+
readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
790
|
+
readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
791
|
+
readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
792
|
+
readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
793
|
+
readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
|
|
794
|
+
readonly key: F.Char<{ readonly required: true }>;
|
|
795
|
+
readonly value: F.Json;
|
|
796
|
+
};
|
|
797
|
+
readonly methods: KoshSettingMethods;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** Methods of `kosh.setting`: declared ones and transitions. */
|
|
801
|
+
export interface KoshSettingMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'kosh.setting'> {
|
|
802
|
+
}
|
|
803
|
+
|
|
782
804
|
/** `mail.activity` (owner: messaging). */
|
|
783
805
|
export interface MailActivity {
|
|
784
806
|
readonly name: 'mail.activity';
|
|
@@ -1093,6 +1115,7 @@ export interface ProductProduct {
|
|
|
1093
1115
|
readonly category: F.Many2one<'product.category'>;
|
|
1094
1116
|
readonly uom: F.Many2one<'uom.uom'>;
|
|
1095
1117
|
readonly barcode: F.Char;
|
|
1118
|
+
readonly image: F.Many2one<'storage.attachment'>;
|
|
1096
1119
|
readonly listPrice: F.Decimal<{ readonly required: true; readonly default: true }>;
|
|
1097
1120
|
readonly standardPrice: F.Decimal<{ readonly required: true; readonly default: true }>;
|
|
1098
1121
|
readonly saleOk: F.Bool<{ readonly required: true; readonly default: true }>;
|
|
@@ -1123,6 +1146,7 @@ export interface ReportDashboard {
|
|
|
1123
1146
|
readonly name: F.Char<{ readonly required: true }>;
|
|
1124
1147
|
readonly items: F.Json<unknown, { readonly required: true; readonly default: true }>;
|
|
1125
1148
|
readonly shared: F.Bool<{ readonly required: true; readonly default: true }>;
|
|
1149
|
+
readonly key: F.Char;
|
|
1126
1150
|
};
|
|
1127
1151
|
readonly methods: ReportDashboardMethods;
|
|
1128
1152
|
}
|
|
@@ -1263,7 +1287,7 @@ export interface ResBankAccount {
|
|
|
1263
1287
|
export interface ResBankAccountMethods extends F.Methods<typeof import('../../accounting/index.js').accountingShutro, 'res.bank.account'> {
|
|
1264
1288
|
}
|
|
1265
1289
|
|
|
1266
|
-
/** `res.company` (owner: base; extended by: accounting). */
|
|
1290
|
+
/** `res.company` (owner: base; extended by: contacts, accounting, sales). */
|
|
1267
1291
|
export interface ResCompany {
|
|
1268
1292
|
readonly name: 'res.company';
|
|
1269
1293
|
readonly fields: {
|
|
@@ -1283,6 +1307,8 @@ export interface ResCompany {
|
|
|
1283
1307
|
readonly email: F.Char;
|
|
1284
1308
|
readonly phone: F.Char;
|
|
1285
1309
|
readonly website: F.Char;
|
|
1310
|
+
readonly contactTaxIdRequired: F.Bool<{ readonly required: true; readonly default: true }>; // contacts
|
|
1311
|
+
readonly contactUniqueEmail: F.Bool<{ readonly required: true; readonly default: true }>; // contacts
|
|
1286
1312
|
readonly fiscalYearStart: F.Integer<{ readonly required: true; readonly default: true }>; // accounting
|
|
1287
1313
|
readonly chartTemplate: F.Char; // accounting
|
|
1288
1314
|
readonly country: F.Char; // accounting
|
|
@@ -1294,12 +1320,14 @@ export interface ResCompany {
|
|
|
1294
1320
|
readonly accountExchangeGain: F.Many2one<'account.account'>; // accounting
|
|
1295
1321
|
readonly accountExchangeLoss: F.Many2one<'account.account'>; // accounting
|
|
1296
1322
|
readonly accountRetainedEarnings: F.Many2one<'account.account'>; // accounting
|
|
1323
|
+
readonly saleValidityDays: F.Integer<{ readonly default: true }>; // sales
|
|
1324
|
+
readonly saleTerms: F.Text; // sales
|
|
1297
1325
|
};
|
|
1298
1326
|
readonly methods: ResCompanyMethods;
|
|
1299
1327
|
}
|
|
1300
1328
|
|
|
1301
1329
|
/** Methods of `res.company`: declared ones and transitions. */
|
|
1302
|
-
export interface ResCompanyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'res.company'>, F.Methods<typeof import('../../accounting/index.js').accountingShutro, 'res.company'> {
|
|
1330
|
+
export interface ResCompanyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'res.company'>, F.Methods<typeof import('../../contacts/index.js').contactsShutro, 'res.company'>, F.Methods<typeof import('../../accounting/index.js').accountingShutro, 'res.company'>, F.Methods<typeof import('../../sales/index.js').salesShutro, 'res.company'> {
|
|
1303
1331
|
}
|
|
1304
1332
|
|
|
1305
1333
|
/** `res.currency` (owner: base). */
|
|
@@ -1358,6 +1386,10 @@ export interface ResPartner {
|
|
|
1358
1386
|
readonly active: F.Bool<{ readonly required: true; readonly default: true }>;
|
|
1359
1387
|
readonly company: F.Many2one<'res.company', { readonly default: true }>;
|
|
1360
1388
|
readonly name: F.Char<{ readonly required: true }>;
|
|
1389
|
+
readonly image: F.Many2one<'storage.attachment'>;
|
|
1390
|
+
readonly tags: F.Many2many<'res.partner.tag', { readonly required: true }>;
|
|
1391
|
+
readonly latitude: F.Decimal;
|
|
1392
|
+
readonly longitude: F.Decimal;
|
|
1361
1393
|
readonly isCompany: F.Bool<{ readonly required: true; readonly default: true }>;
|
|
1362
1394
|
readonly parent: F.Many2one<'res.partner'>;
|
|
1363
1395
|
readonly contacts: F.One2many<'res.partner', { readonly required: true }>;
|
|
@@ -1384,6 +1416,25 @@ export interface ResPartner {
|
|
|
1384
1416
|
export interface ResPartnerMethods extends F.Methods<typeof import('../../contacts/index.js').contactsShutro, 'res.partner'>, F.Methods<typeof import('../../accounting/index.js').accountingShutro, 'res.partner'> {
|
|
1385
1417
|
}
|
|
1386
1418
|
|
|
1419
|
+
/** `res.partner.tag` (owner: contacts). */
|
|
1420
|
+
export interface ResPartnerTag {
|
|
1421
|
+
readonly name: 'res.partner.tag';
|
|
1422
|
+
readonly fields: {
|
|
1423
|
+
readonly id: F.Id<'res.partner.tag'>;
|
|
1424
|
+
readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
1425
|
+
readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
1426
|
+
readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
1427
|
+
readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
1428
|
+
readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
|
|
1429
|
+
readonly name: F.Char<{ readonly required: true }>;
|
|
1430
|
+
};
|
|
1431
|
+
readonly methods: ResPartnerTagMethods;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
/** Methods of `res.partner.tag`: declared ones and transitions. */
|
|
1435
|
+
export interface ResPartnerTagMethods extends F.Methods<typeof import('../../contacts/index.js').contactsShutro, 'res.partner.tag'> {
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1387
1438
|
/** `res.users` (owner: base). */
|
|
1388
1439
|
export interface ResUsers {
|
|
1389
1440
|
readonly name: 'res.users';
|
|
@@ -1435,6 +1486,8 @@ export interface SaleOrder {
|
|
|
1435
1486
|
readonly amountUntaxed: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1436
1487
|
readonly amountTax: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1437
1488
|
readonly amountTotal: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1489
|
+
readonly margin: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1490
|
+
readonly marginPercent: F.Decimal<{ readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1438
1491
|
readonly state: F.State<'draft' | 'sent' | 'sale' | 'done' | 'cancel', { readonly required: true; readonly readonly: true; readonly default: true }>;
|
|
1439
1492
|
readonly invoices: F.One2many<'account.move', { readonly required: true }>; // sale_account
|
|
1440
1493
|
readonly invoiceState: F.Selection<'no' | 'to_invoice' | 'invoiced', { readonly readonly: true; readonly compute: true; readonly store: true }>; // sale_account
|
|
@@ -1474,6 +1527,7 @@ export interface SaleOrderLine {
|
|
|
1474
1527
|
readonly tax: F.Many2one<'account.tax', { readonly readonly: true }>;
|
|
1475
1528
|
readonly currency: F.Many2one<'res.currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1476
1529
|
readonly subtotal: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1530
|
+
readonly margin: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1477
1531
|
readonly taxAmount: F.Monetary<'currency', { readonly readonly: true; readonly compute: true; readonly store: true }>;
|
|
1478
1532
|
};
|
|
1479
1533
|
readonly methods: SaleOrderLineMethods;
|
|
@@ -1500,6 +1554,8 @@ export interface StorageAttachment {
|
|
|
1500
1554
|
readonly key: F.Char<{ readonly required: true }>;
|
|
1501
1555
|
readonly resModel: F.Char<{ readonly required: true }>;
|
|
1502
1556
|
readonly resId: F.Integer<{ readonly required: true }>;
|
|
1557
|
+
readonly variantOf: F.Many2one<'storage.attachment'>;
|
|
1558
|
+
readonly width: F.Integer;
|
|
1503
1559
|
};
|
|
1504
1560
|
readonly methods: StorageAttachmentMethods;
|
|
1505
1561
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { accounting } from '../accounting/index.js';
|
|
8
8
|
import { defineModule } from '../modules-sdk/index.js';
|
|
9
9
|
import { sales } from '../sales/index.js';
|
|
10
|
-
import { field } from '../views/index.js';
|
|
10
|
+
import { field, stat } from '../views/index.js';
|
|
11
11
|
import { saleAccountDemo } from './demo.js';
|
|
12
12
|
import { saleAccountShutro } from './module.js';
|
|
13
13
|
export { MoveSaleOrder, SaleOrderInvoicing, saleAccountShutro } from './module.js';
|
|
@@ -37,6 +37,27 @@ export const saleAccount = defineModule({
|
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
39
|
{ append: 'dates', nodes: [field({ field: 'invoiceState', id: 'invoice_state' })] },
|
|
40
|
+
{
|
|
41
|
+
append: 'related',
|
|
42
|
+
nodes: [
|
|
43
|
+
stat({
|
|
44
|
+
id: 'order_invoices',
|
|
45
|
+
label: 'Invoices',
|
|
46
|
+
model: 'account.move',
|
|
47
|
+
where: { saleOrder: { $record: 'id' } },
|
|
48
|
+
action: 'accounting.action_customer_invoices',
|
|
49
|
+
}),
|
|
50
|
+
stat({
|
|
51
|
+
id: 'order_invoiced',
|
|
52
|
+
label: 'Invoiced total',
|
|
53
|
+
model: 'account.move',
|
|
54
|
+
where: { saleOrder: { $record: 'id' }, state: 'posted' },
|
|
55
|
+
measure: { sum: 'amountTotal' },
|
|
56
|
+
action: 'accounting.action_customer_invoices',
|
|
57
|
+
tone: 'success',
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
},
|
|
40
61
|
],
|
|
41
62
|
},
|
|
42
63
|
{
|