shutro-erp 0.2.1 → 0.3.0
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/README.md +17 -10
- package/dist/account_portal/.kosh/bindings.d.ts +22 -0
- package/dist/account_portal/.kosh/bindings.js +15 -0
- package/dist/account_portal/.kosh/schema.d.ts +1350 -0
- package/dist/account_portal/index.d.ts +3 -0
- package/dist/account_portal/index.js +73 -0
- package/dist/account_portal/module.d.ts +5983 -0
- package/dist/account_portal/module.js +18 -0
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +142 -9
- package/dist/accounting/chart.d.ts +40 -8
- package/dist/accounting/chart.js +124 -30
- package/dist/accounting/closing.d.ts +6 -1
- package/dist/accounting/closing.js +5 -1
- package/dist/accounting/demo.js +6 -5
- package/dist/accounting/index.d.ts +3 -3
- package/dist/accounting/index.js +200 -23
- package/dist/accounting/module.d.ts +3326 -1086
- package/dist/accounting/module.js +408 -66
- package/dist/accounting/payments.d.ts +25 -0
- package/dist/accounting/payments.js +96 -4
- package/dist/accounting/web/RegisterPayment.d.ts +3 -2
- package/dist/accounting/web/RegisterPayment.js +39 -6
- package/dist/accounting/web/SettingsPage.js +5 -1
- package/dist/accounting/web/messages.js +10 -0
- package/dist/cli/init.js +172 -48
- package/dist/cli/launch.d.ts +16 -0
- package/dist/cli/launch.js +138 -0
- package/dist/cli/prompt.d.ts +35 -0
- package/dist/cli/prompt.js +46 -0
- package/dist/cli/templates.d.ts +11 -3
- package/dist/cli/templates.js +32 -7
- package/dist/cli/tui.d.ts +7 -0
- package/dist/cli/tui.js +254 -0
- package/dist/cli/ui.d.ts +25 -5
- package/dist/cli/ui.js +49 -9
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +3 -1
- package/dist/contacts/index.js +24 -1
- package/dist/contacts/module.d.ts +42 -0
- package/dist/contacts/module.js +3 -1
- package/dist/import/convert.d.ts +55 -0
- package/dist/import/convert.js +224 -0
- package/dist/import/csv.d.ts +28 -0
- package/dist/import/csv.js +115 -0
- package/dist/import/index.d.ts +33 -0
- package/dist/import/index.js +60 -0
- package/dist/import/mapping.d.ts +15 -0
- package/dist/import/mapping.js +33 -0
- package/dist/import/match.d.ts +24 -0
- package/dist/import/match.js +60 -0
- package/dist/import/models.d.ts +2 -0
- package/dist/import/models.js +102 -0
- package/dist/import/routes.d.ts +13 -0
- package/dist/import/routes.js +392 -0
- package/dist/import/run.d.ts +131 -0
- package/dist/import/run.js +568 -0
- package/dist/import/service.d.ts +153 -0
- package/dist/import/service.js +367 -0
- package/dist/import/web/ImportButton.d.ts +7 -0
- package/dist/import/web/ImportButton.js +31 -0
- package/dist/import/web/ImportWizard.d.ts +75 -0
- package/dist/import/web/ImportWizard.js +217 -0
- package/dist/import/web/index.d.ts +13 -0
- package/dist/import/web/index.js +18 -0
- package/dist/import/web/messages.d.ts +2 -0
- package/dist/import/web/messages.js +139 -0
- package/dist/import/worker.d.ts +3 -0
- package/dist/import/worker.js +52 -0
- package/dist/import/xlsx.d.ts +20 -0
- package/dist/import/xlsx.js +192 -0
- package/dist/l10n_bd/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_bd/.kosh/bindings.js +15 -0
- package/dist/l10n_bd/.kosh/schema.d.ts +1281 -0
- package/dist/l10n_bd/documents.d.ts +8 -0
- package/dist/l10n_bd/documents.js +183 -0
- package/dist/l10n_bd/index.d.ts +8 -0
- package/dist/l10n_bd/index.js +156 -0
- package/dist/l10n_bd/module.d.ts +5705 -0
- package/dist/l10n_bd/module.js +163 -0
- package/dist/l10n_bd/mushak.d.ts +67 -0
- package/dist/l10n_bd/mushak.js +139 -0
- package/dist/l10n_bd/routes.d.ts +3 -0
- package/dist/l10n_bd/routes.js +205 -0
- package/dist/l10n_bd/web/MushakPage.d.ts +7 -0
- package/dist/l10n_bd/web/MushakPage.js +53 -0
- package/dist/l10n_bd/web/index.d.ts +13 -0
- package/dist/l10n_bd/web/index.js +18 -0
- package/dist/l10n_bd/web/messages.d.ts +2 -0
- package/dist/l10n_bd/web/messages.js +101 -0
- package/dist/l10n_in/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_in/.kosh/bindings.js +15 -0
- package/dist/l10n_in/.kosh/schema.d.ts +1343 -0
- package/dist/l10n_in/documents.d.ts +8 -0
- package/dist/l10n_in/documents.js +114 -0
- package/dist/l10n_in/einvoice.d.ts +253 -0
- package/dist/l10n_in/einvoice.js +485 -0
- package/dist/l10n_in/gstr.d.ts +109 -0
- package/dist/l10n_in/gstr.js +430 -0
- package/dist/l10n_in/index.d.ts +20 -0
- package/dist/l10n_in/index.js +232 -0
- package/dist/l10n_in/module.d.ts +7823 -0
- package/dist/l10n_in/module.js +427 -0
- package/dist/l10n_in/routes-einvoice.d.ts +15 -0
- package/dist/l10n_in/routes-einvoice.js +124 -0
- package/dist/l10n_in/routes.d.ts +17 -0
- package/dist/l10n_in/routes.js +282 -0
- package/dist/l10n_in/web/EInvoicePanel.d.ts +8 -0
- package/dist/l10n_in/web/EInvoicePanel.js +63 -0
- package/dist/l10n_in/web/GstPage.d.ts +7 -0
- package/dist/l10n_in/web/GstPage.js +57 -0
- package/dist/l10n_in/web/GstSettings.d.ts +10 -0
- package/dist/l10n_in/web/GstSettings.js +36 -0
- package/dist/l10n_in/web/index.d.ts +15 -0
- package/dist/l10n_in/web/index.js +22 -0
- package/dist/l10n_in/web/messages.d.ts +2 -0
- package/dist/l10n_in/web/messages.js +166 -0
- package/dist/modules-sdk/demo.d.ts +2 -0
- package/dist/modules-sdk/demo.js +7 -3
- package/dist/modules-sdk/index.d.ts +2 -2
- package/dist/modules-sdk/index.js +1 -1
- package/dist/modules-sdk/manifest.d.ts +132 -0
- package/dist/modules-sdk/manifest.js +22 -0
- package/dist/notify/inbox.js +21 -0
- package/dist/notify/messages.js +4 -0
- package/dist/notify/models.d.ts +1 -1
- package/dist/notify/models.js +2 -0
- package/dist/notify/web/messages.js +4 -0
- package/dist/portal/.kosh/bindings.d.ts +22 -0
- package/dist/portal/.kosh/bindings.js +15 -0
- package/dist/portal/.kosh/schema.d.ts +346 -0
- package/dist/portal/announce.d.ts +16 -0
- package/dist/portal/announce.js +117 -0
- package/dist/portal/documents.d.ts +17 -0
- package/dist/portal/documents.js +61 -0
- package/dist/portal/index.d.ts +20 -0
- package/dist/portal/index.js +65 -0
- package/dist/portal/module.d.ts +1405 -0
- package/dist/portal/module.js +84 -0
- package/dist/portal/routes-documents.d.ts +2 -0
- package/dist/portal/routes-documents.js +391 -0
- package/dist/portal/routes.d.ts +13 -0
- package/dist/portal/routes.js +313 -0
- package/dist/portal/rules.d.ts +11 -0
- package/dist/portal/rules.js +25 -0
- package/dist/portal/web/PortalAccess.d.ts +8 -0
- package/dist/portal/web/PortalAccess.js +66 -0
- package/dist/portal/web/index.d.ts +12 -0
- package/dist/portal/web/index.js +17 -0
- package/dist/portal/web/messages.d.ts +2 -0
- package/dist/portal/web/messages.js +33 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +12 -3
- package/dist/product/demo.js +6 -5
- package/dist/product/index.d.ts +1 -0
- package/dist/product/index.js +17 -4
- package/dist/product/module.d.ts +346 -14
- package/dist/product/module.js +48 -18
- package/dist/product/taxes.d.ts +59 -0
- package/dist/product/taxes.js +61 -0
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +3 -1
- package/dist/project/module.d.ts +20 -0
- package/dist/project_portal/.kosh/bindings.d.ts +22 -0
- package/dist/project_portal/.kosh/bindings.js +15 -0
- package/dist/project_portal/.kosh/schema.d.ts +671 -0
- package/dist/project_portal/index.d.ts +9 -0
- package/dist/project_portal/index.js +42 -0
- package/dist/project_portal/module.d.ts +2142 -0
- package/dist/project_portal/module.js +22 -0
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +15 -4
- package/dist/purchase/demo.js +3 -3
- package/dist/purchase/index.js +1 -1
- package/dist/purchase/module.d.ts +232 -8
- package/dist/purchase/module.js +51 -9
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +144 -10
- package/dist/purchase_account/module.d.ts +2008 -641
- package/dist/purchase_account/module.js +2 -2
- package/dist/purchase_portal/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_portal/.kosh/bindings.js +15 -0
- package/dist/purchase_portal/.kosh/schema.d.ts +822 -0
- package/dist/purchase_portal/index.d.ts +9 -0
- package/dist/purchase_portal/index.js +53 -0
- package/dist/purchase_portal/module.d.ts +2758 -0
- package/dist/purchase_portal/module.js +18 -0
- package/dist/reporting/documents.js +20 -3
- package/dist/reporting/index.d.ts +1 -0
- package/dist/reporting/index.js +1 -0
- package/dist/reporting/routes-documents.js +9 -2
- package/dist/reporting/words.d.ts +11 -0
- package/dist/reporting/words.js +229 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +144 -10
- package/dist/sale_account/module.d.ts +2008 -641
- package/dist/sale_account/module.js +3 -3
- package/dist/sale_portal/.kosh/bindings.d.ts +22 -0
- package/dist/sale_portal/.kosh/bindings.js +15 -0
- package/dist/sale_portal/.kosh/schema.d.ts +736 -0
- package/dist/sale_portal/index.d.ts +9 -0
- package/dist/sale_portal/index.js +71 -0
- package/dist/sale_portal/module.d.ts +2718 -0
- package/dist/sale_portal/module.js +19 -0
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +15 -4
- package/dist/sale_project/module.d.ts +390 -10
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +15 -4
- package/dist/sales/demo.js +3 -3
- package/dist/sales/index.js +67 -1
- package/dist/sales/module.d.ts +232 -8
- package/dist/sales/module.js +51 -12
- package/dist/server/auth/base.js +2 -0
- package/dist/server/auth/service.d.ts +2 -0
- package/dist/server/auth/service.js +7 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/server.js +26 -1
- package/dist/ui/api.d.ts +2 -0
- package/dist/ui/app.js +1 -1
- package/dist/ui/context.d.ts +3 -1
- package/dist/ui/context.js +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/lib/format.d.ts +2 -0
- package/dist/ui/lib/format.js +3 -1
- package/dist/ui/views/FormView.js +19 -6
- package/package.json +50 -2
|
@@ -320,12 +320,18 @@ export class AuthService {
|
|
|
320
320
|
const [credential] = await fetch(model(env, 'auth.credential').search({ where: { user: session.user } }), ['totpEnabled']);
|
|
321
321
|
const administrator = await this.#holdsAdministrator(env, session.user);
|
|
322
322
|
const twoFactorEnabled = credential?.['totpEnabled'] === true;
|
|
323
|
+
const companyIds = user?.['companies'] ?? [];
|
|
324
|
+
const groupings = {};
|
|
325
|
+
if (companyIds.length > 0)
|
|
326
|
+
for (const c of await fetch(model(env, 'res.company').search({ where: { id: { in: companyIds } } }), ['numberGrouping']))
|
|
327
|
+
groupings[String(c['id'])] = c['numberGrouping'] === 'lakh' ? 'lakh' : 'international';
|
|
323
328
|
return {
|
|
324
329
|
id: session.user,
|
|
325
330
|
login: String(user?.['login']),
|
|
326
331
|
name: String(user?.['name']),
|
|
327
332
|
company: typeof user?.['company'] === 'number' ? user['company'] : null,
|
|
328
|
-
companies:
|
|
333
|
+
companies: companyIds,
|
|
334
|
+
groupings,
|
|
329
335
|
twoFactorEnabled,
|
|
330
336
|
administrator,
|
|
331
337
|
administratorNeedsSecondFactor: administrator && !(session.secondFactor && twoFactorEnabled),
|
package/dist/server/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { announceChange, CHANGES_CHANNEL, parseChange, type RecordChange } from './api/changes.js';
|
|
7
7
|
export { generateClient, typeOf } from './api/client.js';
|
|
8
|
-
export { IDEMPOTENCY_TABLE, idempotencyTableStatements, type Outcome } from './api/idempotency.js';
|
|
8
|
+
export { IDEMPOTENCY_TABLE, idempotencyTableStatements, idempotent, type Outcome, } from './api/idempotency.js';
|
|
9
9
|
export { type ApiField, type ApiModel, apiModels, defaultFields, publicApiModels, schemaName, } from './api/metadata.js';
|
|
10
10
|
export { type FoundRecord, type MenuNode, moduleVisible, Navigation } from './api/navigation.js';
|
|
11
11
|
export { fieldSchema, openApiDocument, openApiOf } from './api/openapi.js';
|
package/dist/server/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { announceChange, CHANGES_CHANNEL, parseChange } from './api/changes.js';
|
|
7
7
|
export { generateClient, typeOf } from './api/client.js';
|
|
8
|
-
export { IDEMPOTENCY_TABLE, idempotencyTableStatements } from './api/idempotency.js';
|
|
8
|
+
export { IDEMPOTENCY_TABLE, idempotencyTableStatements, idempotent, } from './api/idempotency.js';
|
|
9
9
|
export { apiModels, defaultFields, publicApiModels, schemaName, } from './api/metadata.js';
|
|
10
10
|
export { moduleVisible, Navigation } from './api/navigation.js';
|
|
11
11
|
export { fieldSchema, openApiDocument, openApiOf } from './api/openapi.js';
|
package/dist/server/server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { registerRestApi } from './api/rest.js';
|
|
|
14
14
|
import { ViewRegistry } from './api/views.js';
|
|
15
15
|
import { registerAuthRoutes } from './auth/routes.js';
|
|
16
16
|
import { AuthService } from './auth/service.js';
|
|
17
|
-
import { HttpProblem, problemOf } from './errors.js';
|
|
17
|
+
import { HttpProblem, problemOf, ServerConfigError } from './errors.js';
|
|
18
18
|
import { denyAll, runOptionsOf } from './identity.js';
|
|
19
19
|
import { orm, pg } from './orm.js';
|
|
20
20
|
import { buildKoshRegistry, KOSH_VERSION } from './registry.js';
|
|
@@ -25,6 +25,22 @@ const REDACT = [
|
|
|
25
25
|
'req.headers["x-api-key"]',
|
|
26
26
|
'res.headers["set-cookie"]',
|
|
27
27
|
];
|
|
28
|
+
/** What a portal user reaches: the portal API, and signing in and out (RFC 0015 §5.1). */
|
|
29
|
+
const PORTAL_PATHS = ['/api/portal/', '/api/v1/auth/'];
|
|
30
|
+
/**
|
|
31
|
+
* The external groups the modules declare (RFC 0015 §5.1). @throws {ServerConfigError} for one implying
|
|
32
|
+
* `base.user`: its holders would be internal users.
|
|
33
|
+
*/
|
|
34
|
+
function externalGroupsOf(registry) {
|
|
35
|
+
const groups = new Set(registry.loaded.modules.flatMap((m) => m.externalGroups ?? []));
|
|
36
|
+
for (const group of groups) {
|
|
37
|
+
if (!registry.registry.security.groups.has(group))
|
|
38
|
+
throw new ServerConfigError(`The external group '${group}' is not defined.`, 'Define it in the module that declares it.');
|
|
39
|
+
if (registry.registry.security.closure([group]).has('base.user'))
|
|
40
|
+
throw new ServerConfigError(`The external group '${group}' implies base.user: its users would be internal.`, 'Remove base.user from what it implies.');
|
|
41
|
+
}
|
|
42
|
+
return groups;
|
|
43
|
+
}
|
|
28
44
|
/** Creates the server (not listening yet). @throws {ServerConfigError} for modules that cannot load together */
|
|
29
45
|
export async function createServer(options) {
|
|
30
46
|
const registry = buildKoshRegistry(options.modules, {
|
|
@@ -37,6 +53,7 @@ export async function createServer(options) {
|
|
|
37
53
|
applicationName: 'kosh',
|
|
38
54
|
});
|
|
39
55
|
const hasBase = registry.registry.models.has('auth.session');
|
|
56
|
+
const external = externalGroupsOf(registry);
|
|
40
57
|
let shutro;
|
|
41
58
|
const auth = hasBase ? new AuthService(() => shutro, options.auth) : undefined;
|
|
42
59
|
const verify = auth !== undefined && (options.verifyIdentities ?? options.authenticate === undefined);
|
|
@@ -101,6 +118,14 @@ export async function createServer(options) {
|
|
|
101
118
|
if (config.public === true || config.auth === false)
|
|
102
119
|
return;
|
|
103
120
|
request.identity = await authenticate(request);
|
|
121
|
+
// Customers and vendors of the portal (RFC 0015 §5.1): the portal API and signing in, nothing else.
|
|
122
|
+
if (request.identity !== null &&
|
|
123
|
+
external.size > 0 &&
|
|
124
|
+
request.identity.groups.length > 0 &&
|
|
125
|
+
request.identity.groups.every((g) => external.has(g)) &&
|
|
126
|
+
!PORTAL_PATHS.some((p) => request.url.startsWith(p)) &&
|
|
127
|
+
request.url.startsWith('/api/'))
|
|
128
|
+
throw new HttpProblem(403, 'internal-only', 'For the company’s users only', 'Portal users reach the portal only.');
|
|
104
129
|
});
|
|
105
130
|
if (options.rateLimit?.max !== 0)
|
|
106
131
|
await app.register(rateLimit, {
|
package/dist/ui/api.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface CurrentUser {
|
|
|
65
65
|
readonly name: string;
|
|
66
66
|
readonly company: number | null;
|
|
67
67
|
readonly companies: readonly number[];
|
|
68
|
+
/** Number grouping per company (RFC 0016); older servers send none. */
|
|
69
|
+
readonly groupings?: Readonly<Record<string, 'international' | 'lakh'>>;
|
|
68
70
|
readonly twoFactorEnabled: boolean;
|
|
69
71
|
readonly administrator: boolean;
|
|
70
72
|
readonly administratorNeedsSecondFactor: boolean;
|
package/dist/ui/app.js
CHANGED
|
@@ -177,7 +177,7 @@ function Shell() {
|
|
|
177
177
|
const user = me.data;
|
|
178
178
|
const current = company ?? user.company ?? user.companies[0] ?? 0;
|
|
179
179
|
const app = apps.data?.find((a) => a.code === params.app);
|
|
180
|
-
return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), _jsx("header", { className: "sticky top-0 z-30 border-b bg-surface", children: _jsxs("div", { className: "flex h-12 items-center gap-2 px-3", children: [_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.apps'), children: _jsx(Grid3x3, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { align: "start", children: [_jsx(MenuItem, { onSelect: () => void navigate({ to: '/' }), children: t('shell.apps') }), _jsx(MenuSeparator, {}), (apps.data ?? []).map((a) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/$app', params: { app: a.code } }), children: a.label }, a.code)))] })] }), _jsx(Link, { to: "/", className: "font-semibold", children: app?.label ?? t('app.name') }), _jsx("nav", { "aria-label": t('shell.menu'), className: "ms-2 flex-1", children: params.app === undefined ? null : (_jsx(MenuLinks, { app: params.app, menus: menus.data ?? [] })) }), _jsxs(Button, { variant: "secondary", size: "sm", className: "hidden h-8 w-56 justify-between text-muted-foreground sm:flex", onClick: () => setPalette(true), "aria-keyshortcuts": "Control+K Meta+K", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Search, { className: "size-4", "aria-hidden": true }), t('palette.button')] }), _jsx("kbd", { className: "rounded border bg-surface px-1.5 text-xs", children: "Ctrl K" })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "sm:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(CompanySwitcher, { me: user, company: current, onChange: (id) => {
|
|
180
|
+
return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, grouping: user.groupings?.[String(current)] ?? 'international', ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), _jsx("header", { className: "sticky top-0 z-30 border-b bg-surface", children: _jsxs("div", { className: "flex h-12 items-center gap-2 px-3", children: [_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.apps'), children: _jsx(Grid3x3, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { align: "start", children: [_jsx(MenuItem, { onSelect: () => void navigate({ to: '/' }), children: t('shell.apps') }), _jsx(MenuSeparator, {}), (apps.data ?? []).map((a) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/$app', params: { app: a.code } }), children: a.label }, a.code)))] })] }), _jsx(Link, { to: "/", className: "font-semibold", children: app?.label ?? t('app.name') }), _jsx("nav", { "aria-label": t('shell.menu'), className: "ms-2 flex-1", children: params.app === undefined ? null : (_jsx(MenuLinks, { app: params.app, menus: menus.data ?? [] })) }), _jsxs(Button, { variant: "secondary", size: "sm", className: "hidden h-8 w-56 justify-between text-muted-foreground sm:flex", onClick: () => setPalette(true), "aria-keyshortcuts": "Control+K Meta+K", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Search, { className: "size-4", "aria-hidden": true }), t('palette.button')] }), _jsx("kbd", { className: "rounded border bg-surface px-1.5 text-xs", children: "Ctrl K" })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "sm:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(CompanySwitcher, { me: user, company: current, onChange: (id) => {
|
|
181
181
|
api.setCompany(id);
|
|
182
182
|
setCompany(id);
|
|
183
183
|
void queryClient.invalidateQueries();
|
package/dist/ui/context.d.ts
CHANGED
|
@@ -26,13 +26,15 @@ export interface KoshContextValue {
|
|
|
26
26
|
export declare function useKosh(): KoshContextValue;
|
|
27
27
|
/** Applies the theme to `<html>` (the `dark` class) and follows the system when asked to. */
|
|
28
28
|
declare function useTheme(): [ThemeChoice, (t: ThemeChoice) => void];
|
|
29
|
-
export declare function KoshProvider({ api, registry, uid, companyId, lang, tz, realtime, offline, children, }: {
|
|
29
|
+
export declare function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, realtime, offline, children, }: {
|
|
30
30
|
readonly api: KoshApi;
|
|
31
31
|
readonly registry: Registry;
|
|
32
32
|
readonly uid: number;
|
|
33
33
|
readonly companyId: number;
|
|
34
34
|
readonly lang: string;
|
|
35
35
|
readonly tz: string;
|
|
36
|
+
/** The company's number grouping (RFC 0016). */
|
|
37
|
+
readonly grouping?: 'international' | 'lakh';
|
|
36
38
|
readonly realtime?: RealtimeClient;
|
|
37
39
|
readonly offline?: OfflineQueue;
|
|
38
40
|
readonly children: ReactNode;
|
package/dist/ui/context.js
CHANGED
|
@@ -47,9 +47,9 @@ function useTheme() {
|
|
|
47
47
|
}, [theme]);
|
|
48
48
|
return [theme, setTheme];
|
|
49
49
|
}
|
|
50
|
-
export function KoshProvider({ api, registry, uid, companyId, lang, tz, realtime, offline, children, }) {
|
|
50
|
+
export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, realtime, offline, children, }) {
|
|
51
51
|
const [theme, setTheme] = useTheme();
|
|
52
|
-
const format = useMemo(() => createFormatter(lang, tz), [lang, tz]);
|
|
52
|
+
const format = useMemo(() => createFormatter(lang, tz, grouping === undefined ? {} : { grouping }), [lang, tz, grouping]);
|
|
53
53
|
const value = useMemo(() => ({
|
|
54
54
|
api,
|
|
55
55
|
registry,
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { ApiError, type ApiOptions, type AppEntry, type CurrentUser, createKoshA
|
|
|
11
11
|
export { KoshApp, type KoshAppProps } from './app.js';
|
|
12
12
|
export { AppIcon } from './components/AppIcon.js';
|
|
13
13
|
export * from './components/primitives.js';
|
|
14
|
-
export { type KoshContextValue, KoshProvider, type ThemeChoice, useKosh } from './context.js';
|
|
14
|
+
export { type KoshContextValue, KoshProvider, type ThemeChoice, useKosh, useTheme, } from './context.js';
|
|
15
15
|
export { type DesignContextValue, DesignProvider, type DesignSpot, useDesign, } from './design.js';
|
|
16
16
|
export { addMessages, createI18n, directionOf, NAMESPACE } from './i18n/index.js';
|
|
17
17
|
export { LANGUAGES } from './i18n/messages.js';
|
package/dist/ui/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { ApiError, createKoshApi, NetworkError, } from './api.js';
|
|
|
15
15
|
export { KoshApp } from './app.js';
|
|
16
16
|
export { AppIcon } from './components/AppIcon.js';
|
|
17
17
|
export * from './components/primitives.js';
|
|
18
|
-
export { KoshProvider, useKosh } from './context.js';
|
|
18
|
+
export { KoshProvider, useKosh, useTheme, } from './context.js';
|
|
19
19
|
export { DesignProvider, useDesign, } from './design.js';
|
|
20
20
|
export { addMessages, createI18n, directionOf, NAMESPACE } from './i18n/index.js';
|
|
21
21
|
export { LANGUAGES } from './i18n/messages.js';
|
package/dist/ui/lib/format.d.ts
CHANGED
|
@@ -14,4 +14,6 @@ export interface Formatter {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function createFormatter(lang: string, tz: string, options?: {
|
|
16
16
|
readonly nativeDigits?: boolean;
|
|
17
|
+
/** `lakh`: 12,34,567 (RFC 0016). Bangla groups so already; English takes the Indian English pattern. */
|
|
18
|
+
readonly grouping?: 'international' | 'lakh';
|
|
17
19
|
}): Formatter;
|
package/dist/ui/lib/format.js
CHANGED
|
@@ -11,7 +11,9 @@ export function createFormatter(lang, tz, options = {}) {
|
|
|
11
11
|
// Bangla defaults to Bengali digits in ICU; RFC 0007 makes them a choice (Latin digits unless asked).
|
|
12
12
|
const locale = base.startsWith('bn')
|
|
13
13
|
? `${base}-u-nu-${options.nativeDigits === true ? 'beng' : 'latn'}`
|
|
14
|
-
: base
|
|
14
|
+
: options.grouping === 'lakh' && base.startsWith('en')
|
|
15
|
+
? 'en-IN'
|
|
16
|
+
: base;
|
|
15
17
|
const integer = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
|
|
16
18
|
const digits = new Intl.NumberFormat(locale, { useGrouping: false });
|
|
17
19
|
const separator = new Intl.NumberFormat(locale).formatToParts(1.5).find((p) => p.type === 'decimal')?.value ??
|
|
@@ -19,23 +19,36 @@ import { cn } from '../lib/cn.js';
|
|
|
19
19
|
import { useLiveRecord } from '../live.js';
|
|
20
20
|
import { useToast } from '../shell/toast.js';
|
|
21
21
|
import { PrefetchedNames, useRowNames } from './widgets.js';
|
|
22
|
-
|
|
22
|
+
/** A line's values as the server takes them: many2many as `{ set }` (a line's taxes). */
|
|
23
|
+
function lineValues(values, fields) {
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [key, value] of Object.entries(values))
|
|
26
|
+
out[key] = fields[key]?.kind === 'many2many' && Array.isArray(value) ? { set: value } : value;
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function commandsOf(lines, fields) {
|
|
23
30
|
return {
|
|
24
31
|
...(lines.rows.some((r) => r.id === undefined)
|
|
25
|
-
? {
|
|
32
|
+
? {
|
|
33
|
+
create: lines.rows
|
|
34
|
+
.filter((r) => r.id === undefined)
|
|
35
|
+
.map((r) => lineValues(r.values, fields)),
|
|
36
|
+
}
|
|
26
37
|
: {}),
|
|
27
38
|
...(lines.rows.some((r) => r.id !== undefined && r.dirty)
|
|
28
39
|
? {
|
|
29
40
|
update: lines.rows
|
|
30
41
|
.filter((r) => r.id !== undefined && r.dirty)
|
|
31
|
-
.map((r) => ({ id: r.id, values: r.values })),
|
|
42
|
+
.map((r) => ({ id: r.id, values: lineValues(r.values, fields) })),
|
|
32
43
|
}
|
|
33
44
|
: {}),
|
|
34
45
|
...(lines.deleted.length > 0 ? { delete: lines.deleted } : {}),
|
|
35
46
|
};
|
|
36
47
|
}
|
|
37
48
|
/** The payload of a save: changed values, many2many as `{ set }`. */
|
|
38
|
-
function payloadOf(changes, fields, lines
|
|
49
|
+
function payloadOf(changes, fields, lines,
|
|
50
|
+
/** The fields of a one2many's lines. */
|
|
51
|
+
lineFields) {
|
|
39
52
|
const out = {};
|
|
40
53
|
for (const [key, value] of Object.entries(changes)) {
|
|
41
54
|
if (fields[key]?.kind === 'many2many')
|
|
@@ -44,7 +57,7 @@ function payloadOf(changes, fields, lines) {
|
|
|
44
57
|
out[key] = value;
|
|
45
58
|
}
|
|
46
59
|
for (const [key, state] of lines) {
|
|
47
|
-
const commands = commandsOf(state);
|
|
60
|
+
const commands = commandsOf(state, lineFields(key));
|
|
48
61
|
if (Object.keys(commands).length > 0)
|
|
49
62
|
out[key] = commands;
|
|
50
63
|
}
|
|
@@ -177,7 +190,7 @@ export function FormView({ served, recordId, defaults, onSaved, onBack, crumbs,
|
|
|
177
190
|
};
|
|
178
191
|
const save = useMutation({
|
|
179
192
|
mutationFn: async () => {
|
|
180
|
-
const payload = payloadOf(changes, fields, lines);
|
|
193
|
+
const payload = payloadOf(changes, fields, lines, (key) => served.fields[fields[key]?.target ?? ''] ?? {});
|
|
181
194
|
const etag = loaded.data?.etag ?? null;
|
|
182
195
|
try {
|
|
183
196
|
return isNew
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shutro-erp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shutro ERP (Kosh): a modern ERP framework on TypeScript, PostgreSQL and the Shutro ORM — typed end to end, permissions enforced on the server, metadata-driven views, reporting, documents and a chatter on every record.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"erp",
|
|
@@ -137,6 +137,14 @@
|
|
|
137
137
|
"types": "./dist/assistant/web/index.d.ts",
|
|
138
138
|
"default": "./dist/assistant/web/index.js"
|
|
139
139
|
},
|
|
140
|
+
"./import": {
|
|
141
|
+
"types": "./dist/import/index.d.ts",
|
|
142
|
+
"default": "./dist/import/index.js"
|
|
143
|
+
},
|
|
144
|
+
"./import/web": {
|
|
145
|
+
"types": "./dist/import/web/index.d.ts",
|
|
146
|
+
"default": "./dist/import/web/index.js"
|
|
147
|
+
},
|
|
140
148
|
"./contacts": {
|
|
141
149
|
"types": "./dist/contacts/index.d.ts",
|
|
142
150
|
"default": "./dist/contacts/index.js"
|
|
@@ -177,6 +185,46 @@
|
|
|
177
185
|
"types": "./dist/sale_project/index.d.ts",
|
|
178
186
|
"default": "./dist/sale_project/index.js"
|
|
179
187
|
},
|
|
188
|
+
"./portal": {
|
|
189
|
+
"types": "./dist/portal/index.d.ts",
|
|
190
|
+
"default": "./dist/portal/index.js"
|
|
191
|
+
},
|
|
192
|
+
"./portal/web": {
|
|
193
|
+
"types": "./dist/portal/web/index.d.ts",
|
|
194
|
+
"default": "./dist/portal/web/index.js"
|
|
195
|
+
},
|
|
196
|
+
"./sale_portal": {
|
|
197
|
+
"types": "./dist/sale_portal/index.d.ts",
|
|
198
|
+
"default": "./dist/sale_portal/index.js"
|
|
199
|
+
},
|
|
200
|
+
"./account_portal": {
|
|
201
|
+
"types": "./dist/account_portal/index.d.ts",
|
|
202
|
+
"default": "./dist/account_portal/index.js"
|
|
203
|
+
},
|
|
204
|
+
"./purchase_portal": {
|
|
205
|
+
"types": "./dist/purchase_portal/index.d.ts",
|
|
206
|
+
"default": "./dist/purchase_portal/index.js"
|
|
207
|
+
},
|
|
208
|
+
"./project_portal": {
|
|
209
|
+
"types": "./dist/project_portal/index.d.ts",
|
|
210
|
+
"default": "./dist/project_portal/index.js"
|
|
211
|
+
},
|
|
212
|
+
"./l10n_bd": {
|
|
213
|
+
"types": "./dist/l10n_bd/index.d.ts",
|
|
214
|
+
"default": "./dist/l10n_bd/index.js"
|
|
215
|
+
},
|
|
216
|
+
"./l10n_bd/web": {
|
|
217
|
+
"types": "./dist/l10n_bd/web/index.d.ts",
|
|
218
|
+
"default": "./dist/l10n_bd/web/index.js"
|
|
219
|
+
},
|
|
220
|
+
"./l10n_in": {
|
|
221
|
+
"types": "./dist/l10n_in/index.d.ts",
|
|
222
|
+
"default": "./dist/l10n_in/index.js"
|
|
223
|
+
},
|
|
224
|
+
"./l10n_in/web": {
|
|
225
|
+
"types": "./dist/l10n_in/web/index.d.ts",
|
|
226
|
+
"default": "./dist/l10n_in/web/index.js"
|
|
227
|
+
},
|
|
180
228
|
"./ui": {
|
|
181
229
|
"types": "./dist/ui/index.d.ts",
|
|
182
230
|
"default": "./dist/ui/index.js"
|
|
@@ -229,7 +277,7 @@
|
|
|
229
277
|
"postal-mime": "^4.1.4",
|
|
230
278
|
"radix-ui": "^1.8.1",
|
|
231
279
|
"sanitize-html": "^2.18.1",
|
|
232
|
-
"shutro": "^0.3.
|
|
280
|
+
"shutro": "^0.3.2",
|
|
233
281
|
"tailwind-merge": "^3.7.0",
|
|
234
282
|
"web-push": "^3.6.7"
|
|
235
283
|
},
|