@baruk/core 0.3.0 → 0.4.2
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/.baruk-provenance.json +10 -0
- package/CHANGELOG.md +97 -0
- package/README.md +9 -2
- package/dist/auth/password-reset.d.ts +30 -1
- package/dist/auth/password-reset.d.ts.map +1 -1
- package/dist/auth/password-reset.js +139 -11
- package/dist/auth/password-reset.js.map +1 -1
- package/dist/auth/service.d.ts +39 -2
- package/dist/auth/service.d.ts.map +1 -1
- package/dist/auth/service.js +75 -10
- package/dist/auth/service.js.map +1 -1
- package/dist/billing/ar-repository.d.ts +334 -0
- package/dist/billing/ar-repository.d.ts.map +1 -0
- package/dist/billing/ar-repository.js +707 -0
- package/dist/billing/ar-repository.js.map +1 -0
- package/dist/billing/auto-generation.d.ts +87 -0
- package/dist/billing/auto-generation.d.ts.map +1 -0
- package/dist/billing/auto-generation.js +181 -0
- package/dist/billing/auto-generation.js.map +1 -0
- package/dist/billing/live-guard.d.ts +64 -0
- package/dist/billing/live-guard.d.ts.map +1 -0
- package/dist/billing/live-guard.js +68 -0
- package/dist/billing/live-guard.js.map +1 -0
- package/dist/billing/payment-means-catalog.d.ts +17 -0
- package/dist/billing/payment-means-catalog.d.ts.map +1 -0
- package/dist/billing/payment-means-catalog.js +21 -0
- package/dist/billing/payment-means-catalog.js.map +1 -0
- package/dist/billing/payment-means.d.ts +38 -0
- package/dist/billing/payment-means.d.ts.map +1 -0
- package/dist/billing/payment-means.js +29 -0
- package/dist/billing/payment-means.js.map +1 -0
- package/dist/billing/payment-rail.d.ts +118 -0
- package/dist/billing/payment-rail.d.ts.map +1 -0
- package/dist/billing/payment-rail.js +295 -0
- package/dist/billing/payment-rail.js.map +1 -0
- package/dist/billing/provider/asaas.d.ts +46 -1
- package/dist/billing/provider/asaas.d.ts.map +1 -1
- package/dist/billing/provider/asaas.js +194 -14
- package/dist/billing/provider/asaas.js.map +1 -1
- package/dist/billing/provider/manual.js +1 -1
- package/dist/billing/provider/manual.js.map +1 -1
- package/dist/billing/provider/types.d.ts +115 -8
- package/dist/billing/provider/types.d.ts.map +1 -1
- package/dist/billing/provider-account.d.ts +89 -0
- package/dist/billing/provider-account.d.ts.map +1 -0
- package/dist/billing/provider-account.js +161 -0
- package/dist/billing/provider-account.js.map +1 -0
- package/dist/billing/provider-onboarding.d.ts +67 -0
- package/dist/billing/provider-onboarding.d.ts.map +1 -0
- package/dist/billing/provider-onboarding.js +234 -0
- package/dist/billing/provider-onboarding.js.map +1 -0
- package/dist/billing/receivable.d.ts +5 -1
- package/dist/billing/receivable.d.ts.map +1 -1
- package/dist/billing/reconcile.d.ts +58 -0
- package/dist/billing/reconcile.d.ts.map +1 -0
- package/dist/billing/reconcile.js +138 -0
- package/dist/billing/reconcile.js.map +1 -0
- package/dist/billing/repository.d.ts +90 -1
- package/dist/billing/repository.d.ts.map +1 -1
- package/dist/billing/repository.js +148 -19
- package/dist/billing/repository.js.map +1 -1
- package/dist/cjs/auth/password-reset.js +141 -11
- package/dist/cjs/auth/password-reset.js.map +1 -1
- package/dist/cjs/auth/service.js +75 -9
- package/dist/cjs/auth/service.js.map +1 -1
- package/dist/cjs/billing/ar-repository.js +728 -0
- package/dist/cjs/billing/ar-repository.js.map +1 -0
- package/dist/cjs/billing/auto-generation.js +186 -0
- package/dist/cjs/billing/auto-generation.js.map +1 -0
- package/dist/cjs/billing/live-guard.js +72 -0
- package/dist/cjs/billing/live-guard.js.map +1 -0
- package/dist/cjs/billing/payment-means-catalog.js +25 -0
- package/dist/cjs/billing/payment-means-catalog.js.map +1 -0
- package/dist/cjs/billing/payment-means.js +33 -0
- package/dist/cjs/billing/payment-means.js.map +1 -0
- package/dist/cjs/billing/payment-rail.js +301 -0
- package/dist/cjs/billing/payment-rail.js.map +1 -0
- package/dist/cjs/billing/provider/asaas.js +194 -14
- package/dist/cjs/billing/provider/asaas.js.map +1 -1
- package/dist/cjs/billing/provider/manual.js +1 -1
- package/dist/cjs/billing/provider/manual.js.map +1 -1
- package/dist/cjs/billing/provider-account.js +170 -0
- package/dist/cjs/billing/provider-account.js.map +1 -0
- package/dist/cjs/billing/provider-onboarding.js +239 -0
- package/dist/cjs/billing/provider-onboarding.js.map +1 -0
- package/dist/cjs/billing/reconcile.js +143 -0
- package/dist/cjs/billing/reconcile.js.map +1 -0
- package/dist/cjs/billing/repository.js +150 -18
- package/dist/cjs/billing/repository.js.map +1 -1
- package/dist/cjs/clients/repository.js +96 -20
- package/dist/cjs/clients/repository.js.map +1 -1
- package/dist/cjs/collection/branding.js +140 -0
- package/dist/cjs/collection/branding.js.map +1 -0
- package/dist/cjs/collection/channel-accounts.js +137 -0
- package/dist/cjs/collection/channel-accounts.js.map +1 -0
- package/dist/cjs/collection/channel.js +17 -0
- package/dist/cjs/collection/channel.js.map +1 -0
- package/dist/cjs/collection/communication-bridge.js +140 -0
- package/dist/cjs/collection/communication-bridge.js.map +1 -0
- package/dist/cjs/collection/communication-runtime.js +193 -0
- package/dist/cjs/collection/communication-runtime.js.map +1 -0
- package/dist/cjs/collection/consent-repository.js +84 -0
- package/dist/cjs/collection/consent-repository.js.map +1 -0
- package/dist/cjs/collection/copy.js +125 -0
- package/dist/cjs/collection/copy.js.map +1 -0
- package/dist/cjs/collection/delivery-callback.js +151 -0
- package/dist/cjs/collection/delivery-callback.js.map +1 -0
- package/dist/cjs/collection/email-layout.js +252 -0
- package/dist/cjs/collection/email-layout.js.map +1 -0
- package/dist/cjs/collection/engine.js +414 -0
- package/dist/cjs/collection/engine.js.map +1 -0
- package/dist/cjs/collection/format.js +76 -0
- package/dist/cjs/collection/format.js.map +1 -0
- package/dist/cjs/collection/payment-block.js +3 -0
- package/dist/cjs/collection/payment-block.js.map +1 -0
- package/dist/cjs/collection/policy.js +147 -0
- package/dist/cjs/collection/policy.js.map +1 -0
- package/dist/cjs/collection/repository.js +260 -0
- package/dist/cjs/collection/repository.js.map +1 -0
- package/dist/cjs/collection/subject.js +104 -0
- package/dist/cjs/collection/subject.js.map +1 -0
- package/dist/cjs/collection/template-catalog.js +165 -0
- package/dist/cjs/collection/template-catalog.js.map +1 -0
- package/dist/cjs/collection/template.js +78 -0
- package/dist/cjs/collection/template.js.map +1 -0
- package/dist/cjs/communication/adapters/email.js +132 -0
- package/dist/cjs/communication/adapters/email.js.map +1 -0
- package/dist/cjs/communication/adapters/sms.js +127 -0
- package/dist/cjs/communication/adapters/sms.js.map +1 -0
- package/dist/cjs/communication/adapters/whatsapp-language.js +39 -0
- package/dist/cjs/communication/adapters/whatsapp-language.js.map +1 -0
- package/dist/cjs/communication/adapters/whatsapp-templates.js +100 -0
- package/dist/cjs/communication/adapters/whatsapp-templates.js.map +1 -0
- package/dist/cjs/communication/adapters/whatsapp.js +213 -0
- package/dist/cjs/communication/adapters/whatsapp.js.map +1 -0
- package/dist/cjs/communication/audit.js +61 -0
- package/dist/cjs/communication/audit.js.map +1 -0
- package/dist/cjs/communication/capabilities.js +76 -0
- package/dist/cjs/communication/capabilities.js.map +1 -0
- package/dist/cjs/communication/channel.js +53 -0
- package/dist/cjs/communication/channel.js.map +1 -0
- package/dist/cjs/communication/claim.js +228 -0
- package/dist/cjs/communication/claim.js.map +1 -0
- package/dist/cjs/communication/consent.js +43 -0
- package/dist/cjs/communication/consent.js.map +1 -0
- package/dist/cjs/communication/context.js +45 -0
- package/dist/cjs/communication/context.js.map +1 -0
- package/dist/cjs/communication/credentials.js +121 -0
- package/dist/cjs/communication/credentials.js.map +1 -0
- package/dist/cjs/communication/dry-run-scenario.js +161 -0
- package/dist/cjs/communication/dry-run-scenario.js.map +1 -0
- package/dist/cjs/communication/dry-run.js +97 -0
- package/dist/cjs/communication/dry-run.js.map +1 -0
- package/dist/cjs/communication/engine.js +483 -0
- package/dist/cjs/communication/engine.js.map +1 -0
- package/dist/cjs/communication/graph.js +50 -0
- package/dist/cjs/communication/graph.js.map +1 -0
- package/dist/cjs/communication/idempotency.js +26 -0
- package/dist/cjs/communication/idempotency.js.map +1 -0
- package/dist/cjs/communication/identity.js +110 -0
- package/dist/cjs/communication/identity.js.map +1 -0
- package/dist/cjs/communication/index.js +135 -0
- package/dist/cjs/communication/index.js.map +1 -0
- package/dist/cjs/communication/onboarding.js +297 -0
- package/dist/cjs/communication/onboarding.js.map +1 -0
- package/dist/cjs/communication/policy.js +16 -0
- package/dist/cjs/communication/policy.js.map +1 -0
- package/dist/cjs/communication/routing.js +77 -0
- package/dist/cjs/communication/routing.js.map +1 -0
- package/dist/cjs/communication/sender-profile.js +128 -0
- package/dist/cjs/communication/sender-profile.js.map +1 -0
- package/dist/cjs/communication/status.js +86 -0
- package/dist/cjs/communication/status.js.map +1 -0
- package/dist/cjs/communication/stores/onboarding.js +135 -0
- package/dist/cjs/communication/stores/onboarding.js.map +1 -0
- package/dist/cjs/communication/stores/postgres.js +458 -0
- package/dist/cjs/communication/stores/postgres.js.map +1 -0
- package/dist/cjs/communication/stores/routing.js +334 -0
- package/dist/cjs/communication/stores/routing.js.map +1 -0
- package/dist/cjs/communication/stores/secrets.js +126 -0
- package/dist/cjs/communication/stores/secrets.js.map +1 -0
- package/dist/cjs/communication/template-provider.js +22 -0
- package/dist/cjs/communication/template-provider.js.map +1 -0
- package/dist/cjs/communication/template.js +63 -0
- package/dist/cjs/communication/template.js.map +1 -0
- package/dist/cjs/communication/webhook-subscription.js +258 -0
- package/dist/cjs/communication/webhook-subscription.js.map +1 -0
- package/dist/cjs/communication/webhook.js +292 -0
- package/dist/cjs/communication/webhook.js.map +1 -0
- package/dist/cjs/competence/repository.js +309 -0
- package/dist/cjs/competence/repository.js.map +1 -0
- package/dist/cjs/contacts/phone.js +192 -0
- package/dist/cjs/contacts/phone.js.map +1 -0
- package/dist/cjs/db/seed-catalog.js +48 -0
- package/dist/cjs/db/seed-catalog.js.map +1 -0
- package/dist/cjs/db/seed.js +15 -7
- package/dist/cjs/db/seed.js.map +1 -1
- package/dist/cjs/documents/repository.js +244 -0
- package/dist/cjs/documents/repository.js.map +1 -0
- package/dist/cjs/documents/requests.js +136 -0
- package/dist/cjs/documents/requests.js.map +1 -0
- package/dist/cjs/index.js +427 -48
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/legal/company.js +36 -0
- package/dist/cjs/legal/company.js.map +1 -0
- package/dist/cjs/legal/documents.js +671 -0
- package/dist/cjs/legal/documents.js.map +1 -0
- package/dist/cjs/legal/index.js +15 -0
- package/dist/cjs/legal/index.js.map +1 -0
- package/dist/cjs/legal/types.js +34 -0
- package/dist/cjs/legal/types.js.map +1 -0
- package/dist/cjs/mail/auth-templates.js +29 -0
- package/dist/cjs/mail/auth-templates.js.map +1 -0
- package/dist/cjs/mail/dedup-store.js +12 -51
- package/dist/cjs/mail/dedup-store.js.map +1 -1
- package/dist/cjs/mail/index.js +16 -4
- package/dist/cjs/mail/index.js.map +1 -1
- package/dist/cjs/mail/provider.js +12 -247
- package/dist/cjs/mail/provider.js.map +1 -1
- package/dist/cjs/mail/resend.js +14 -18
- package/dist/cjs/mail/resend.js.map +1 -1
- package/dist/cjs/mail/sender-resolver.js +12 -11
- package/dist/cjs/mail/sender-resolver.js.map +1 -1
- package/dist/cjs/mail/template-renderer.js +11 -16
- package/dist/cjs/mail/template-renderer.js.map +1 -1
- package/dist/cjs/mail/transactional-outbound.js +117 -0
- package/dist/cjs/mail/transactional-outbound.js.map +1 -0
- package/dist/cjs/mail/types.js +8 -35
- package/dist/cjs/mail/types.js.map +1 -1
- package/dist/cjs/portal/provisioning.js +144 -0
- package/dist/cjs/portal/provisioning.js.map +1 -0
- package/dist/cjs/pricing-engine/canonical.js +42 -0
- package/dist/cjs/pricing-engine/canonical.js.map +1 -0
- package/dist/cjs/pricing-engine/errors.js +6 -1
- package/dist/cjs/pricing-engine/errors.js.map +1 -1
- package/dist/cjs/pricing-engine/index.js +12 -1
- package/dist/cjs/pricing-engine/index.js.map +1 -1
- package/dist/cjs/pricing-engine/pipeline.js +200 -26
- package/dist/cjs/pricing-engine/pipeline.js.map +1 -1
- package/dist/cjs/pricing-engine/shadow-adapter.js +79 -0
- package/dist/cjs/pricing-engine/shadow-adapter.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-diff.js +55 -0
- package/dist/cjs/pricing-engine/shadow-diff.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-facade.js +85 -0
- package/dist/cjs/pricing-engine/shadow-facade.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-legacy-types.js +19 -0
- package/dist/cjs/pricing-engine/shadow-legacy-types.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-metrics.js +34 -0
- package/dist/cjs/pricing-engine/shadow-metrics.js.map +1 -0
- package/dist/cjs/pricing-engine/shadow-normalizer.js +42 -0
- package/dist/cjs/pricing-engine/shadow-normalizer.js.map +1 -0
- package/dist/cjs/rbac/authorize.js +28 -0
- package/dist/cjs/rbac/authorize.js.map +1 -1
- package/dist/cjs/rbac/catalog.js +67 -1
- package/dist/cjs/rbac/catalog.js.map +1 -1
- package/dist/cjs/rbac/workload-provisioning.js +160 -0
- package/dist/cjs/rbac/workload-provisioning.js.map +1 -0
- package/dist/cjs/rbac/workload.js +191 -0
- package/dist/cjs/rbac/workload.js.map +1 -0
- package/dist/cjs/storage/checksum.js +15 -0
- package/dist/cjs/storage/checksum.js.map +1 -0
- package/dist/cjs/storage/key.js +72 -0
- package/dist/cjs/storage/key.js.map +1 -0
- package/dist/cjs/storage/local.js +100 -0
- package/dist/cjs/storage/local.js.map +1 -0
- package/dist/cjs/storage/r2.js +181 -0
- package/dist/cjs/storage/r2.js.map +1 -0
- package/dist/cjs/storage/registry.js +41 -0
- package/dist/cjs/storage/registry.js.map +1 -0
- package/dist/cjs/storage/types.js +50 -0
- package/dist/cjs/storage/types.js.map +1 -0
- package/dist/cjs/subscription-billing/provider/asaas.js +32 -7
- package/dist/cjs/subscription-billing/provider/asaas.js.map +1 -1
- package/dist/cjs/subscription-billing/provider/disabled.js +8 -0
- package/dist/cjs/subscription-billing/provider/disabled.js.map +1 -1
- package/dist/cjs/subscription-billing/provider/registry.js +17 -0
- package/dist/cjs/subscription-billing/provider/registry.js.map +1 -1
- package/dist/cjs/subscription-billing/service.js +57 -8
- package/dist/cjs/subscription-billing/service.js.map +1 -1
- package/dist/cjs/subscription-billing/types.js.map +1 -1
- package/dist/cjs/tenancy/provisioning.js +32 -8
- package/dist/cjs/tenancy/provisioning.js.map +1 -1
- package/dist/cjs/users/repository.js +119 -2
- package/dist/cjs/users/repository.js.map +1 -1
- package/dist/cjs/vault/repository.js +81 -0
- package/dist/cjs/vault/repository.js.map +1 -1
- package/dist/clients/repository.d.ts +42 -0
- package/dist/clients/repository.d.ts.map +1 -1
- package/dist/clients/repository.js +94 -21
- package/dist/clients/repository.js.map +1 -1
- package/dist/collection/branding.d.ts +72 -0
- package/dist/collection/branding.d.ts.map +1 -0
- package/dist/collection/branding.js +136 -0
- package/dist/collection/branding.js.map +1 -0
- package/dist/collection/channel-accounts.d.ts +57 -0
- package/dist/collection/channel-accounts.d.ts.map +1 -0
- package/dist/collection/channel-accounts.js +133 -0
- package/dist/collection/channel-accounts.js.map +1 -0
- package/dist/collection/channel.d.ts +24 -0
- package/dist/collection/channel.d.ts.map +1 -0
- package/dist/collection/channel.js +16 -0
- package/dist/collection/channel.js.map +1 -0
- package/dist/collection/communication-bridge.d.ts +66 -0
- package/dist/collection/communication-bridge.d.ts.map +1 -0
- package/dist/collection/communication-bridge.js +134 -0
- package/dist/collection/communication-bridge.js.map +1 -0
- package/dist/collection/communication-runtime.d.ts +95 -0
- package/dist/collection/communication-runtime.d.ts.map +1 -0
- package/dist/collection/communication-runtime.js +187 -0
- package/dist/collection/communication-runtime.js.map +1 -0
- package/dist/collection/consent-repository.d.ts +32 -0
- package/dist/collection/consent-repository.d.ts.map +1 -0
- package/dist/collection/consent-repository.js +79 -0
- package/dist/collection/consent-repository.js.map +1 -0
- package/dist/collection/copy.d.ts +50 -0
- package/dist/collection/copy.d.ts.map +1 -0
- package/dist/collection/copy.js +122 -0
- package/dist/collection/copy.js.map +1 -0
- package/dist/collection/delivery-callback.d.ts +49 -0
- package/dist/collection/delivery-callback.d.ts.map +1 -0
- package/dist/collection/delivery-callback.js +147 -0
- package/dist/collection/delivery-callback.js.map +1 -0
- package/dist/collection/email-layout.d.ts +45 -0
- package/dist/collection/email-layout.d.ts.map +1 -0
- package/dist/collection/email-layout.js +247 -0
- package/dist/collection/email-layout.js.map +1 -0
- package/dist/collection/engine.d.ts +177 -0
- package/dist/collection/engine.d.ts.map +1 -0
- package/dist/collection/engine.js +407 -0
- package/dist/collection/engine.js.map +1 -0
- package/dist/collection/format.d.ts +32 -0
- package/dist/collection/format.d.ts.map +1 -0
- package/dist/collection/format.js +70 -0
- package/dist/collection/format.js.map +1 -0
- package/dist/collection/payment-block.d.ts +24 -0
- package/dist/collection/payment-block.d.ts.map +1 -0
- package/dist/collection/payment-block.js +2 -0
- package/dist/collection/payment-block.js.map +1 -0
- package/dist/collection/policy.d.ts +53 -0
- package/dist/collection/policy.d.ts.map +1 -0
- package/dist/collection/policy.js +141 -0
- package/dist/collection/policy.js.map +1 -0
- package/dist/collection/repository.d.ts +129 -0
- package/dist/collection/repository.d.ts.map +1 -0
- package/dist/collection/repository.js +246 -0
- package/dist/collection/repository.js.map +1 -0
- package/dist/collection/subject.d.ts +66 -0
- package/dist/collection/subject.d.ts.map +1 -0
- package/dist/collection/subject.js +99 -0
- package/dist/collection/subject.js.map +1 -0
- package/dist/collection/template-catalog.d.ts +16 -0
- package/dist/collection/template-catalog.d.ts.map +1 -0
- package/dist/collection/template-catalog.js +162 -0
- package/dist/collection/template-catalog.js.map +1 -0
- package/dist/collection/template.d.ts +37 -0
- package/dist/collection/template.d.ts.map +1 -0
- package/dist/collection/template.js +73 -0
- package/dist/collection/template.js.map +1 -0
- package/dist/communication/adapters/email.d.ts +28 -0
- package/dist/communication/adapters/email.d.ts.map +1 -0
- package/dist/communication/adapters/email.js +129 -0
- package/dist/communication/adapters/email.js.map +1 -0
- package/dist/communication/adapters/sms.d.ts +95 -0
- package/dist/communication/adapters/sms.d.ts.map +1 -0
- package/dist/communication/adapters/sms.js +121 -0
- package/dist/communication/adapters/sms.js.map +1 -0
- package/dist/communication/adapters/whatsapp-language.d.ts +34 -0
- package/dist/communication/adapters/whatsapp-language.d.ts.map +1 -0
- package/dist/communication/adapters/whatsapp-language.js +36 -0
- package/dist/communication/adapters/whatsapp-language.js.map +1 -0
- package/dist/communication/adapters/whatsapp-templates.d.ts +18 -0
- package/dist/communication/adapters/whatsapp-templates.d.ts.map +1 -0
- package/dist/communication/adapters/whatsapp-templates.js +97 -0
- package/dist/communication/adapters/whatsapp-templates.js.map +1 -0
- package/dist/communication/adapters/whatsapp.d.ts +29 -0
- package/dist/communication/adapters/whatsapp.d.ts.map +1 -0
- package/dist/communication/adapters/whatsapp.js +210 -0
- package/dist/communication/adapters/whatsapp.js.map +1 -0
- package/dist/communication/audit.d.ts +105 -0
- package/dist/communication/audit.d.ts.map +1 -0
- package/dist/communication/audit.js +56 -0
- package/dist/communication/audit.js.map +1 -0
- package/dist/communication/capabilities.d.ts +88 -0
- package/dist/communication/capabilities.d.ts.map +1 -0
- package/dist/communication/capabilities.js +72 -0
- package/dist/communication/capabilities.js.map +1 -0
- package/dist/communication/channel.d.ts +125 -0
- package/dist/communication/channel.d.ts.map +1 -0
- package/dist/communication/channel.js +47 -0
- package/dist/communication/channel.js.map +1 -0
- package/dist/communication/claim.d.ts +368 -0
- package/dist/communication/claim.d.ts.map +1 -0
- package/dist/communication/claim.js +224 -0
- package/dist/communication/claim.js.map +1 -0
- package/dist/communication/consent.d.ts +65 -0
- package/dist/communication/consent.d.ts.map +1 -0
- package/dist/communication/consent.js +40 -0
- package/dist/communication/consent.js.map +1 -0
- package/dist/communication/context.d.ts +48 -0
- package/dist/communication/context.d.ts.map +1 -0
- package/dist/communication/context.js +41 -0
- package/dist/communication/context.js.map +1 -0
- package/dist/communication/credentials.d.ts +74 -0
- package/dist/communication/credentials.d.ts.map +1 -0
- package/dist/communication/credentials.js +115 -0
- package/dist/communication/credentials.js.map +1 -0
- package/dist/communication/dry-run-scenario.d.ts +37 -0
- package/dist/communication/dry-run-scenario.d.ts.map +1 -0
- package/dist/communication/dry-run-scenario.js +158 -0
- package/dist/communication/dry-run-scenario.js.map +1 -0
- package/dist/communication/dry-run.d.ts +40 -0
- package/dist/communication/dry-run.d.ts.map +1 -0
- package/dist/communication/dry-run.js +92 -0
- package/dist/communication/dry-run.js.map +1 -0
- package/dist/communication/engine.d.ts +153 -0
- package/dist/communication/engine.d.ts.map +1 -0
- package/dist/communication/engine.js +480 -0
- package/dist/communication/engine.js.map +1 -0
- package/dist/communication/graph.d.ts +38 -0
- package/dist/communication/graph.d.ts.map +1 -0
- package/dist/communication/graph.js +45 -0
- package/dist/communication/graph.js.map +1 -0
- package/dist/communication/idempotency.d.ts +33 -0
- package/dist/communication/idempotency.d.ts.map +1 -0
- package/dist/communication/idempotency.js +23 -0
- package/dist/communication/idempotency.js.map +1 -0
- package/dist/communication/identity.d.ts +127 -0
- package/dist/communication/identity.d.ts.map +1 -0
- package/dist/communication/identity.js +105 -0
- package/dist/communication/identity.js.map +1 -0
- package/dist/communication/index.d.ts +66 -0
- package/dist/communication/index.d.ts.map +1 -0
- package/dist/communication/index.js +42 -0
- package/dist/communication/index.js.map +1 -0
- package/dist/communication/onboarding.d.ts +223 -0
- package/dist/communication/onboarding.d.ts.map +1 -0
- package/dist/communication/onboarding.js +287 -0
- package/dist/communication/onboarding.js.map +1 -0
- package/dist/communication/policy.d.ts +99 -0
- package/dist/communication/policy.d.ts.map +1 -0
- package/dist/communication/policy.js +13 -0
- package/dist/communication/policy.js.map +1 -0
- package/dist/communication/routing.d.ts +99 -0
- package/dist/communication/routing.d.ts.map +1 -0
- package/dist/communication/routing.js +73 -0
- package/dist/communication/routing.js.map +1 -0
- package/dist/communication/sender-profile.d.ts +100 -0
- package/dist/communication/sender-profile.d.ts.map +1 -0
- package/dist/communication/sender-profile.js +123 -0
- package/dist/communication/sender-profile.js.map +1 -0
- package/dist/communication/status.d.ts +48 -0
- package/dist/communication/status.d.ts.map +1 -0
- package/dist/communication/status.js +79 -0
- package/dist/communication/status.js.map +1 -0
- package/dist/communication/stores/onboarding.d.ts +88 -0
- package/dist/communication/stores/onboarding.d.ts.map +1 -0
- package/dist/communication/stores/onboarding.js +127 -0
- package/dist/communication/stores/onboarding.js.map +1 -0
- package/dist/communication/stores/postgres.d.ts +81 -0
- package/dist/communication/stores/postgres.d.ts.map +1 -0
- package/dist/communication/stores/postgres.js +453 -0
- package/dist/communication/stores/postgres.js.map +1 -0
- package/dist/communication/stores/routing.d.ts +169 -0
- package/dist/communication/stores/routing.d.ts.map +1 -0
- package/dist/communication/stores/routing.js +324 -0
- package/dist/communication/stores/routing.js.map +1 -0
- package/dist/communication/stores/secrets.d.ts +89 -0
- package/dist/communication/stores/secrets.d.ts.map +1 -0
- package/dist/communication/stores/secrets.js +122 -0
- package/dist/communication/stores/secrets.js.map +1 -0
- package/dist/communication/template-provider.d.ts +85 -0
- package/dist/communication/template-provider.d.ts.map +1 -0
- package/dist/communication/template-provider.js +21 -0
- package/dist/communication/template-provider.js.map +1 -0
- package/dist/communication/template.d.ts +87 -0
- package/dist/communication/template.d.ts.map +1 -0
- package/dist/communication/template.js +59 -0
- package/dist/communication/template.js.map +1 -0
- package/dist/communication/webhook-subscription.d.ts +156 -0
- package/dist/communication/webhook-subscription.d.ts.map +1 -0
- package/dist/communication/webhook-subscription.js +252 -0
- package/dist/communication/webhook-subscription.js.map +1 -0
- package/dist/communication/webhook.d.ts +121 -0
- package/dist/communication/webhook.d.ts.map +1 -0
- package/dist/communication/webhook.js +283 -0
- package/dist/communication/webhook.js.map +1 -0
- package/dist/competence/repository.d.ts +147 -0
- package/dist/competence/repository.d.ts.map +1 -0
- package/dist/competence/repository.js +294 -0
- package/dist/competence/repository.js.map +1 -0
- package/dist/contacts/phone.d.ts +70 -0
- package/dist/contacts/phone.d.ts.map +1 -0
- package/dist/contacts/phone.js +186 -0
- package/dist/contacts/phone.js.map +1 -0
- package/dist/db/migrate.js +17 -11
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/seed-catalog.d.ts +18 -0
- package/dist/db/seed-catalog.d.ts.map +1 -0
- package/dist/db/seed-catalog.js +45 -0
- package/dist/db/seed-catalog.js.map +1 -0
- package/dist/db/seed.js +16 -8
- package/dist/db/seed.js.map +1 -1
- package/dist/documents/repository.d.ts +97 -0
- package/dist/documents/repository.d.ts.map +1 -0
- package/dist/documents/repository.js +236 -0
- package/dist/documents/repository.js.map +1 -0
- package/dist/documents/requests.d.ts +73 -0
- package/dist/documents/requests.d.ts.map +1 -0
- package/dist/documents/requests.js +127 -0
- package/dist/documents/requests.js.map +1 -0
- package/dist/index.d.ts +113 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +104 -8
- package/dist/index.js.map +1 -1
- package/dist/legal/company.d.ts +32 -0
- package/dist/legal/company.d.ts.map +1 -0
- package/dist/legal/company.js +33 -0
- package/dist/legal/company.js.map +1 -0
- package/dist/legal/documents.d.ts +30 -0
- package/dist/legal/documents.d.ts.map +1 -0
- package/dist/legal/documents.js +666 -0
- package/dist/legal/documents.js.map +1 -0
- package/dist/legal/index.d.ts +5 -0
- package/dist/legal/index.d.ts.map +1 -0
- package/dist/legal/index.js +4 -0
- package/dist/legal/index.js.map +1 -0
- package/dist/legal/types.d.ts +145 -0
- package/dist/legal/types.d.ts.map +1 -0
- package/dist/legal/types.js +31 -0
- package/dist/legal/types.js.map +1 -0
- package/dist/mail/auth-templates.d.ts +15 -0
- package/dist/mail/auth-templates.d.ts.map +1 -0
- package/dist/mail/auth-templates.js +26 -0
- package/dist/mail/auth-templates.js.map +1 -0
- package/dist/mail/dedup-store.d.ts +1 -24
- package/dist/mail/dedup-store.d.ts.map +1 -1
- package/dist/mail/dedup-store.js +9 -48
- package/dist/mail/dedup-store.js.map +1 -1
- package/dist/mail/index.d.ts +2 -1
- package/dist/mail/index.d.ts.map +1 -1
- package/dist/mail/index.js +13 -2
- package/dist/mail/index.js.map +1 -1
- package/dist/mail/provider.d.ts +2 -30
- package/dist/mail/provider.d.ts.map +1 -1
- package/dist/mail/provider.js +10 -246
- package/dist/mail/provider.js.map +1 -1
- package/dist/mail/resend.d.ts +2 -12
- package/dist/mail/resend.d.ts.map +1 -1
- package/dist/mail/resend.js +11 -17
- package/dist/mail/resend.js.map +1 -1
- package/dist/mail/sender-resolver.d.ts +1 -7
- package/dist/mail/sender-resolver.d.ts.map +1 -1
- package/dist/mail/sender-resolver.js +11 -10
- package/dist/mail/sender-resolver.js.map +1 -1
- package/dist/mail/template-renderer.d.ts +1 -9
- package/dist/mail/template-renderer.d.ts.map +1 -1
- package/dist/mail/template-renderer.js +10 -15
- package/dist/mail/template-renderer.js.map +1 -1
- package/dist/mail/transactional-outbound.d.ts +115 -0
- package/dist/mail/transactional-outbound.d.ts.map +1 -0
- package/dist/mail/transactional-outbound.js +113 -0
- package/dist/mail/transactional-outbound.js.map +1 -0
- package/dist/mail/types.d.ts +2 -142
- package/dist/mail/types.d.ts.map +1 -1
- package/dist/mail/types.js +1 -33
- package/dist/mail/types.js.map +1 -1
- package/dist/portal/provisioning.d.ts +58 -0
- package/dist/portal/provisioning.d.ts.map +1 -0
- package/dist/portal/provisioning.js +139 -0
- package/dist/portal/provisioning.js.map +1 -0
- package/dist/pricing-engine/canonical.d.ts +14 -0
- package/dist/pricing-engine/canonical.d.ts.map +1 -0
- package/dist/pricing-engine/canonical.js +39 -0
- package/dist/pricing-engine/canonical.js.map +1 -0
- package/dist/pricing-engine/errors.d.ts.map +1 -1
- package/dist/pricing-engine/errors.js +6 -1
- package/dist/pricing-engine/errors.js.map +1 -1
- package/dist/pricing-engine/index.d.ts +12 -2
- package/dist/pricing-engine/index.d.ts.map +1 -1
- package/dist/pricing-engine/index.js +6 -1
- package/dist/pricing-engine/index.js.map +1 -1
- package/dist/pricing-engine/pipeline.d.ts.map +1 -1
- package/dist/pricing-engine/pipeline.js +202 -28
- package/dist/pricing-engine/pipeline.js.map +1 -1
- package/dist/pricing-engine/shadow-adapter.d.ts +37 -0
- package/dist/pricing-engine/shadow-adapter.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-adapter.js +75 -0
- package/dist/pricing-engine/shadow-adapter.js.map +1 -0
- package/dist/pricing-engine/shadow-diff.d.ts +26 -0
- package/dist/pricing-engine/shadow-diff.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-diff.js +52 -0
- package/dist/pricing-engine/shadow-diff.js.map +1 -0
- package/dist/pricing-engine/shadow-facade.d.ts +42 -0
- package/dist/pricing-engine/shadow-facade.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-facade.js +82 -0
- package/dist/pricing-engine/shadow-facade.js.map +1 -0
- package/dist/pricing-engine/shadow-legacy-types.d.ts +64 -0
- package/dist/pricing-engine/shadow-legacy-types.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-legacy-types.js +18 -0
- package/dist/pricing-engine/shadow-legacy-types.js.map +1 -0
- package/dist/pricing-engine/shadow-metrics.d.ts +33 -0
- package/dist/pricing-engine/shadow-metrics.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-metrics.js +31 -0
- package/dist/pricing-engine/shadow-metrics.js.map +1 -0
- package/dist/pricing-engine/shadow-normalizer.d.ts +17 -0
- package/dist/pricing-engine/shadow-normalizer.d.ts.map +1 -0
- package/dist/pricing-engine/shadow-normalizer.js +39 -0
- package/dist/pricing-engine/shadow-normalizer.js.map +1 -0
- package/dist/pricing-engine/types.d.ts +2 -0
- package/dist/pricing-engine/types.d.ts.map +1 -1
- package/dist/rbac/authorize.d.ts +12 -0
- package/dist/rbac/authorize.d.ts.map +1 -1
- package/dist/rbac/authorize.js +27 -0
- package/dist/rbac/authorize.js.map +1 -1
- package/dist/rbac/catalog.d.ts.map +1 -1
- package/dist/rbac/catalog.js +67 -1
- package/dist/rbac/catalog.js.map +1 -1
- package/dist/rbac/workload-provisioning.d.ts +66 -0
- package/dist/rbac/workload-provisioning.d.ts.map +1 -0
- package/dist/rbac/workload-provisioning.js +153 -0
- package/dist/rbac/workload-provisioning.js.map +1 -0
- package/dist/rbac/workload.d.ts +124 -0
- package/dist/rbac/workload.d.ts.map +1 -0
- package/dist/rbac/workload.js +184 -0
- package/dist/rbac/workload.js.map +1 -0
- package/dist/storage/checksum.d.ts +9 -0
- package/dist/storage/checksum.d.ts.map +1 -0
- package/dist/storage/checksum.js +12 -0
- package/dist/storage/checksum.js.map +1 -0
- package/dist/storage/key.d.ts +37 -0
- package/dist/storage/key.d.ts.map +1 -0
- package/dist/storage/key.js +67 -0
- package/dist/storage/key.js.map +1 -0
- package/dist/storage/local.d.ts +40 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +96 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/r2.d.ts +43 -0
- package/dist/storage/r2.d.ts.map +1 -0
- package/dist/storage/r2.js +177 -0
- package/dist/storage/r2.js.map +1 -0
- package/dist/storage/registry.d.ts +23 -0
- package/dist/storage/registry.d.ts.map +1 -0
- package/dist/storage/registry.js +37 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/types.d.ts +67 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +45 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/subscription-billing/provider/asaas.d.ts +14 -0
- package/dist/subscription-billing/provider/asaas.d.ts.map +1 -1
- package/dist/subscription-billing/provider/asaas.js +32 -7
- package/dist/subscription-billing/provider/asaas.js.map +1 -1
- package/dist/subscription-billing/provider/disabled.d.ts +9 -0
- package/dist/subscription-billing/provider/disabled.d.ts.map +1 -1
- package/dist/subscription-billing/provider/disabled.js +8 -0
- package/dist/subscription-billing/provider/disabled.js.map +1 -1
- package/dist/subscription-billing/provider/registry.d.ts +10 -0
- package/dist/subscription-billing/provider/registry.d.ts.map +1 -1
- package/dist/subscription-billing/provider/registry.js +17 -0
- package/dist/subscription-billing/provider/registry.js.map +1 -1
- package/dist/subscription-billing/service.d.ts +17 -1
- package/dist/subscription-billing/service.d.ts.map +1 -1
- package/dist/subscription-billing/service.js +57 -8
- package/dist/subscription-billing/service.js.map +1 -1
- package/dist/subscription-billing/types.d.ts +13 -0
- package/dist/subscription-billing/types.d.ts.map +1 -1
- package/dist/subscription-billing/types.js.map +1 -1
- package/dist/tenancy/provisioning.d.ts.map +1 -1
- package/dist/tenancy/provisioning.js +32 -8
- package/dist/tenancy/provisioning.js.map +1 -1
- package/dist/users/repository.d.ts +38 -0
- package/dist/users/repository.d.ts.map +1 -1
- package/dist/users/repository.js +118 -3
- package/dist/users/repository.js.map +1 -1
- package/dist/vault/repository.d.ts +67 -0
- package/dist/vault/repository.d.ts.map +1 -1
- package/dist/vault/repository.js +78 -0
- package/dist/vault/repository.js.map +1 -1
- package/package.json +19 -4
- package/src/db/migrations/0023_user_invite_activation.sql +15 -0
- package/src/db/migrations/0024_set_user_password_function.sql +32 -0
- package/src/db/migrations/0025_revoke_user_session_function.sql +28 -0
- package/src/db/migrations/0026_documents_and_competence.sql +353 -0
- package/src/db/migrations/0027_receivable_client_isolation.sql +58 -0
- package/src/db/migrations/0028_collection_engine.sql +94 -0
- package/src/db/migrations/0029_payment_rail.sql +107 -0
- package/src/db/migrations/0030_provider_account_uniqueness.sql +22 -0
- package/src/db/migrations/0031_provider_account_environment_scope.sql +21 -0
- package/src/db/migrations/0032_settlement_reversal.sql +76 -0
- package/src/db/migrations/0033_receivable_idempotency.sql +28 -0
- package/src/db/migrations/0034_payment_customer_reconcile.sql +30 -0
- package/src/db/migrations/0035_payment_account_environments.sql +61 -0
- package/src/db/migrations/0036_primary_backfill_under_force_rls.sql +41 -0
- package/src/db/migrations/0037_receivable_generation_source.sql +29 -0
- package/src/db/migrations/0038_notification_channels.sql +175 -0
- package/src/db/migrations/0039_communication_dispatch_claim.sql +193 -0
- package/src/db/migrations/0040_communication_unknown_outcome.sql +99 -0
- package/src/db/migrations/0041_channel_binding_routing.sql +146 -0
- package/src/db/migrations/0042_channel_route_read_isolation.sql +165 -0
- package/src/db/migrations/0043_workload_identity.sql +105 -0
- package/src/db/migrations/0044_channel_onboarding_session.sql +96 -0
- package/dist/cjs/mail/resend-client.js +0 -103
- package/dist/cjs/mail/resend-client.js.map +0 -1
- package/dist/mail/resend-client.d.ts +0 -16
- package/dist/mail/resend-client.d.ts.map +0 -1
- package/dist/mail/resend-client.js +0 -100
- package/dist/mail/resend-client.js.map +0 -1
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import { ConflictError, NotFoundError, ValidationError, uuidv7 } from '@baruk/shared';
|
|
2
|
+
import { availableMeansOf } from './payment-means-catalog.js';
|
|
3
|
+
/**
|
|
4
|
+
* Accounts Receivable — M1 do Merchant Billing Core
|
|
5
|
+
* (RFC-CAPABILITY-MERCHANT-BILLING-CORE-001 §4/§20). O M0 entregou schema e
|
|
6
|
+
* contratos; aqui entram a geração por competência, a máquina de estados e as
|
|
7
|
+
* consultas financeiras.
|
|
8
|
+
*
|
|
9
|
+
* A separação do M0 continua valendo e não pode ser colapsada: `Receivable` é a
|
|
10
|
+
* DÍVIDA do cliente com o escritório; `Charge` é o INSTRUMENTO (boleto/pix) que
|
|
11
|
+
* tenta recebê-la; `Payment` é o dinheiro que chegou. Um mesmo Receivable pode
|
|
12
|
+
* ter vários Charges (reemissão) e vários Payments (parcial).
|
|
13
|
+
*
|
|
14
|
+
* Tenant-scoped por RLS: nenhuma query aqui filtra `tenant_id` na mão — quem
|
|
15
|
+
* garante o recorte é a policy, e o contexto vem de `withContext`.
|
|
16
|
+
*/
|
|
17
|
+
// ── Vencido é uma DERIVAÇÃO, não um estado que alguém precisa lembrar de gravar ──
|
|
18
|
+
//
|
|
19
|
+
// Um recebível `open` cuja data passou já está vencido — mesmo que nenhum job
|
|
20
|
+
// tenha rodado. Gravar 'overdue' numa coluna faria o número do dashboard depender
|
|
21
|
+
// de um cron ter executado; quando o cron falha, o escritório vê inadimplência
|
|
22
|
+
// menor do que a real, que é o pior erro possível neste domínio.
|
|
23
|
+
//
|
|
24
|
+
// Então o status efetivo é calculado na leitura. `markOverdueReceivables` existe
|
|
25
|
+
// para MATERIALIZAR a transição — não para descobri-la — porque a régua de
|
|
26
|
+
// cobrança precisa de um instante definido para disparar RECEIVABLE_OVERDUE.
|
|
27
|
+
// `partially_paid` entra junto de `open`: um recebível pago pela metade cujo
|
|
28
|
+
// vencimento passou TEM saldo em atraso. Considerar só `open` fazia o resumo
|
|
29
|
+
// dizer "vencido: R$ 0" enquanto o aging listava o mesmo saldo na faixa de 1-7
|
|
30
|
+
// dias — dois números verdadeiros contando coisas diferentes, e o mais visível
|
|
31
|
+
// deles subestimando a inadimplência.
|
|
32
|
+
const EFFECTIVE_STATUS_SQL = `
|
|
33
|
+
CASE WHEN r.status IN ('open','partially_paid')
|
|
34
|
+
AND r.due_date IS NOT NULL AND r.due_date < CURRENT_DATE
|
|
35
|
+
THEN 'overdue' ELSE r.status END`;
|
|
36
|
+
/** Status que ainda representam dinheiro a receber. */
|
|
37
|
+
const OPEN_STATUSES = ['open', 'partially_paid', 'overdue'];
|
|
38
|
+
const iso = (d) => (d ? d.toISOString().slice(0, 10) : null);
|
|
39
|
+
/** Texto de `metadata`, ou null. Ignora número/objeto: metadata é livre. */
|
|
40
|
+
function metaText(metadata, key) {
|
|
41
|
+
const v = metadata?.[key];
|
|
42
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
43
|
+
}
|
|
44
|
+
function mapReceivable(r) {
|
|
45
|
+
const amount = Number(r.amount);
|
|
46
|
+
const amountPaid = Number(r.amount_paid);
|
|
47
|
+
return {
|
|
48
|
+
id: r.id,
|
|
49
|
+
clientId: r.client_id,
|
|
50
|
+
clientName: r.client_name,
|
|
51
|
+
feeAgreementId: r.fee_agreement_id,
|
|
52
|
+
competence: r.competence,
|
|
53
|
+
amount,
|
|
54
|
+
status: r.status,
|
|
55
|
+
effectiveStatus: r.effective_status,
|
|
56
|
+
dueDate: iso(r.due_date),
|
|
57
|
+
issuedAt: r.issued_at.toISOString(),
|
|
58
|
+
renegotiatedFromId: r.renegotiated_from_id,
|
|
59
|
+
createdAt: r.created_at.toISOString(),
|
|
60
|
+
amountPaid,
|
|
61
|
+
outstandingAmount: Math.max(0, Number((amount - amountPaid).toFixed(2))),
|
|
62
|
+
daysOverdue: r.days_overdue,
|
|
63
|
+
// A origem é o `fee_agreement_id` e nada mais. Derivar da competência —
|
|
64
|
+
// tentador, porque quase todo recorrente tem uma — classificaria como
|
|
65
|
+
// recorrente todo avulso que o operador competenciou, que é justamente o
|
|
66
|
+
// caso em que a distinção importa.
|
|
67
|
+
origin: r.fee_agreement_id ? 'recurring' : 'one_off',
|
|
68
|
+
generatedBy: r.generated_by,
|
|
69
|
+
description: r.description,
|
|
70
|
+
notes: metaText(r.metadata, 'notes'),
|
|
71
|
+
serviceCategory: metaText(r.metadata, 'serviceCategory'),
|
|
72
|
+
activeCharge: r.charge_id
|
|
73
|
+
? {
|
|
74
|
+
id: r.charge_id,
|
|
75
|
+
status: r.charge_status,
|
|
76
|
+
means: availableMeansOf(r.charge_metadata),
|
|
77
|
+
}
|
|
78
|
+
: null,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Descrição do recebível a partir dos seus itens.
|
|
83
|
+
*
|
|
84
|
+
* Hoje tanto a geração por competência quanto o avulso criam exatamente UM
|
|
85
|
+
* item, mas o schema permite vários (RFC §4) — então junta em vez de pegar o
|
|
86
|
+
* primeiro: com dois itens, "Honorários" sozinho esconderia metade da dívida.
|
|
87
|
+
*/
|
|
88
|
+
const DESCRIPTION_SQL = `
|
|
89
|
+
(SELECT string_agg(i.description, ' · ' ORDER BY i.created_at, i.id)
|
|
90
|
+
FROM receivable_item i WHERE i.receivable_id = r.id)`;
|
|
91
|
+
const SELECT_RECEIVABLE = `
|
|
92
|
+
SELECT r.id, r.client_id, c.display_name AS client_name, r.fee_agreement_id, r.competence,
|
|
93
|
+
r.amount, r.status, ${EFFECTIVE_STATUS_SQL} AS effective_status, r.generated_by,
|
|
94
|
+
r.due_date, r.issued_at, r.renegotiated_from_id, r.created_at, r.metadata,
|
|
95
|
+
${DESCRIPTION_SQL} AS description,
|
|
96
|
+
ac.id AS charge_id, ac.status AS charge_status, ac.metadata AS charge_metadata,
|
|
97
|
+
COALESCE((SELECT sum(p.amount) FROM payment p
|
|
98
|
+
WHERE p.receivable_id = r.id AND p.status = 'confirmed'), 0) AS amount_paid,
|
|
99
|
+
GREATEST(0, COALESCE(CURRENT_DATE - r.due_date, 0))::int AS days_overdue
|
|
100
|
+
FROM receivable r
|
|
101
|
+
LEFT JOIN client c ON c.id = r.client_id AND c.deleted_at IS NULL
|
|
102
|
+
-- LATERAL, e não uma subquery por coluna: as três colunas da cobrança
|
|
103
|
+
-- precisam vir da MESMA linha. Três subqueries independentes poderiam, com
|
|
104
|
+
-- mais de uma cobrança viva, montar um Frankenstein com o id de uma e os
|
|
105
|
+
-- meios de outra.
|
|
106
|
+
LEFT JOIN LATERAL (
|
|
107
|
+
SELECT ch.id, ch.status, ch.metadata
|
|
108
|
+
FROM charge ch
|
|
109
|
+
WHERE ch.receivable_id = r.id
|
|
110
|
+
AND ch.deleted_at IS NULL
|
|
111
|
+
AND ch.status NOT IN ('canceled', 'refunded')
|
|
112
|
+
ORDER BY ch.created_at DESC
|
|
113
|
+
LIMIT 1
|
|
114
|
+
) ac ON true`;
|
|
115
|
+
// ── Competência ──────────────────────────────────────────────────────────────
|
|
116
|
+
const COMPETENCE_RE = /^\d{4}-(0[1-9]|1[0-2])$/;
|
|
117
|
+
export function assertCompetence(competence) {
|
|
118
|
+
if (!COMPETENCE_RE.test(competence)) {
|
|
119
|
+
throw new ValidationError('Competência deve estar no formato YYYY-MM, com mês entre 01 e 12');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Data de vencimento de uma competência, respeitando o `due_day` do honorário.
|
|
124
|
+
*
|
|
125
|
+
* `due_day` é limitado a 28 no schema justamente para não existir "dia 31 de
|
|
126
|
+
* fevereiro"; ainda assim clampamos ao último dia do mês, porque a regra de
|
|
127
|
+
* negócio não pode depender de uma constraint continuar existindo.
|
|
128
|
+
*/
|
|
129
|
+
export function dueDateFor(competence, dueDay) {
|
|
130
|
+
assertCompetence(competence);
|
|
131
|
+
const [year, month] = competence.split('-').map(Number);
|
|
132
|
+
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
133
|
+
const day = Math.min(Math.max(dueDay ?? lastDay, 1), lastDay);
|
|
134
|
+
return `${competence}-${String(day).padStart(2, '0')}`;
|
|
135
|
+
}
|
|
136
|
+
const DUE_DATE_RE = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
137
|
+
/**
|
|
138
|
+
* Janela tolerada entre a competência e o vencimento de um lançamento avulso.
|
|
139
|
+
*
|
|
140
|
+
* No fluxo por competência não existe folga: `dueDateFor` devolve sempre uma data
|
|
141
|
+
* DENTRO do mês da competência. O avulso relaxa isso de propósito — competência e
|
|
142
|
+
* vencimento são campos independentes do payload — e é justamente aí que um erro
|
|
143
|
+
* de digitação vira "honorários de março/2027 vencendo em agosto/2026" no e-mail
|
|
144
|
+
* que o cliente lê.
|
|
145
|
+
*
|
|
146
|
+
* A folga é assimétrica porque a prática comercial é assimétrica: faturar um mês
|
|
147
|
+
* adiantado acontece; dar três meses de prazo acontece; sete meses de distância,
|
|
148
|
+
* em qualquer direção, não é arranjo comercial — é dedo trocado.
|
|
149
|
+
*/
|
|
150
|
+
const DUE_MONTHS_BEFORE_COMPETENCE = 1;
|
|
151
|
+
const DUE_MONTHS_AFTER_COMPETENCE = 3;
|
|
152
|
+
/**
|
|
153
|
+
* A competência e o vencimento informados fazem sentido juntos?
|
|
154
|
+
*
|
|
155
|
+
* Aritmética de mês em inteiros, não em `Date`: o que importa é a distância entre
|
|
156
|
+
* dois meses do calendário, e converter para instante só introduziria fuso onde
|
|
157
|
+
* não há hora nenhuma envolvida.
|
|
158
|
+
*/
|
|
159
|
+
export function checkCompetenceDueDate(competence, dueDate) {
|
|
160
|
+
assertCompetence(competence);
|
|
161
|
+
if (!DUE_DATE_RE.test(dueDate)) {
|
|
162
|
+
throw new ValidationError('Vencimento deve estar no formato YYYY-MM-DD');
|
|
163
|
+
}
|
|
164
|
+
const monthIndex = (year, month) => year * 12 + (month - 1);
|
|
165
|
+
const [cy, cm] = competence.split('-').map(Number);
|
|
166
|
+
const [dy, dm] = dueDate.split('-').map(Number);
|
|
167
|
+
const monthsFromCompetence = monthIndex(dy, dm) - monthIndex(cy, cm);
|
|
168
|
+
const coherent = monthsFromCompetence >= -DUE_MONTHS_BEFORE_COMPETENCE &&
|
|
169
|
+
monthsFromCompetence <= DUE_MONTHS_AFTER_COMPETENCE;
|
|
170
|
+
return { coherent, monthsFromCompetence };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Mensagem do 422. Diz o que veio, qual é a distância e como confirmar — quem lê
|
|
174
|
+
* precisa conseguir decidir entre "corrigi o dedo trocado" e "é isso mesmo".
|
|
175
|
+
*/
|
|
176
|
+
function competenceMismatchMessage(competence, dueDate, monthsFromCompetence) {
|
|
177
|
+
const distance = Math.abs(monthsFromCompetence);
|
|
178
|
+
const direction = monthsFromCompetence < 0 ? 'antes' : 'depois';
|
|
179
|
+
const plural = distance === 1 ? 'mês' : 'meses';
|
|
180
|
+
return (`Competência ${competence} com vencimento em ${dueDate}: o vencimento está ` +
|
|
181
|
+
`${distance} ${plural} ${direction} do mês da competência. O esperado é entre ` +
|
|
182
|
+
`${DUE_MONTHS_BEFORE_COMPETENCE} mês antes e ${DUE_MONTHS_AFTER_COMPETENCE} meses ` +
|
|
183
|
+
`depois. Se a combinação está correta, reenvie confirmando explicitamente ` +
|
|
184
|
+
`(competenceMismatchAck).`);
|
|
185
|
+
}
|
|
186
|
+
/** Ver `FeeCompetenceVerdict`. Pura: nenhum I/O, nenhum relógio. */
|
|
187
|
+
export function classifyFeeForCompetence(fee, competence) {
|
|
188
|
+
if (fee.status === 'suspended')
|
|
189
|
+
return 'suspended';
|
|
190
|
+
if (fee.status !== 'active')
|
|
191
|
+
return 'canceled';
|
|
192
|
+
const first = `${competence}-01`;
|
|
193
|
+
const [year, month] = competence.split('-').map(Number);
|
|
194
|
+
const last = `${competence}-${String(new Date(Date.UTC(year, month, 0)).getUTCDate()).padStart(2, '0')}`;
|
|
195
|
+
// Vigência: o honorário precisa estar valendo em ALGUM dia da competência.
|
|
196
|
+
if (fee.startsOn && fee.startsOn > last)
|
|
197
|
+
return 'before_term';
|
|
198
|
+
if (fee.endsOn && fee.endsOn < first)
|
|
199
|
+
return 'after_term';
|
|
200
|
+
switch (fee.periodicity) {
|
|
201
|
+
case 'monthly':
|
|
202
|
+
return 'eligible';
|
|
203
|
+
// Trimestral/anual ancoram no mês de início da vigência — sem `startsOn` não
|
|
204
|
+
// há âncora, e gerar todo mês seria cobrar a mais. Fail-safe: não gera.
|
|
205
|
+
case 'quarterly': {
|
|
206
|
+
if (!fee.startsOn)
|
|
207
|
+
return 'no_anchor';
|
|
208
|
+
const anchor = Number(fee.startsOn.slice(5, 7));
|
|
209
|
+
return (month - anchor) % 3 === 0 ? 'eligible' : 'off_cycle';
|
|
210
|
+
}
|
|
211
|
+
case 'yearly': {
|
|
212
|
+
if (!fee.startsOn)
|
|
213
|
+
return 'no_anchor';
|
|
214
|
+
return Number(fee.startsOn.slice(5, 7)) === month ? 'eligible' : 'off_cycle';
|
|
215
|
+
}
|
|
216
|
+
// Avulso nunca é gerado em lote: nasce por lançamento manual.
|
|
217
|
+
case 'one_time':
|
|
218
|
+
return 'one_time';
|
|
219
|
+
default:
|
|
220
|
+
return 'off_cycle';
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** Um honorário vale para a competência? Considera vigência e periodicidade. */
|
|
224
|
+
export function feeAppliesTo(fee, competence) {
|
|
225
|
+
return classifyFeeForCompetence(fee, competence) === 'eligible';
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Gera os recebíveis de uma competência a partir dos honorários ativos.
|
|
229
|
+
*
|
|
230
|
+
* Idempotente por construção: o índice único `(tenant_id, fee_agreement_id,
|
|
231
|
+
* competence)` do M0 é a garantia real, e o `ON CONFLICT DO NOTHING` a
|
|
232
|
+
* transforma em "pular" em vez de "explodir". Rodar duas vezes no mesmo mês não
|
|
233
|
+
* duplica cobrança — o que aqui não é detalhe técnico: cobrar o cliente duas
|
|
234
|
+
* vezes é o erro que destrói a confiança no sistema.
|
|
235
|
+
*/
|
|
236
|
+
export async function generateReceivablesForCompetence(tx, input) {
|
|
237
|
+
assertCompetence(input.competence);
|
|
238
|
+
const source = input.source ?? 'manual';
|
|
239
|
+
const fees = await tx `
|
|
240
|
+
SELECT id, client_id, description, amount, periodicity, due_day, discount, status, starts_on, ends_on
|
|
241
|
+
FROM fee_agreement
|
|
242
|
+
WHERE deleted_at IS NULL
|
|
243
|
+
${input.feeAgreementIds?.length ? tx `AND id IN ${tx(input.feeAgreementIds)}` : tx ``}
|
|
244
|
+
`;
|
|
245
|
+
const created = [];
|
|
246
|
+
let skipped = 0;
|
|
247
|
+
let notApplicable = 0;
|
|
248
|
+
for (const fee of fees) {
|
|
249
|
+
// A decisão de gerar vem de UM lugar só.
|
|
250
|
+
//
|
|
251
|
+
// Até a fase 2.3 havia duas: um `AND status = 'active'` no SELECT acima e a
|
|
252
|
+
// checagem de status dentro de `feeAppliesTo`. As duas concordavam, e por
|
|
253
|
+
// isso o arranjo parecia inofensivo — mas um teste de mutação mostrou o
|
|
254
|
+
// custo: com a regra do status quebrada dentro da função, o SELECT continuava
|
|
255
|
+
// segurando, e nenhum teste de "suspenso não gera" reprovava. A garantia
|
|
256
|
+
// estava numa cláusula SQL que ninguém estava testando, enquanto o dry-run
|
|
257
|
+
// — que decide se o scheduler pode ser ligado — usava a OUTRA cópia.
|
|
258
|
+
//
|
|
259
|
+
// Duas cópias de uma regra financeira só precisam divergir uma vez.
|
|
260
|
+
const verdict = classifyFeeForCompetence({
|
|
261
|
+
periodicity: fee.periodicity,
|
|
262
|
+
startsOn: iso(fee.starts_on),
|
|
263
|
+
endsOn: iso(fee.ends_on),
|
|
264
|
+
status: fee.status,
|
|
265
|
+
}, input.competence);
|
|
266
|
+
if (verdict !== 'eligible') {
|
|
267
|
+
// Suspenso e encerrado ficam FORA da contagem, e não é descuido: quem lê
|
|
268
|
+
// "3 fora de vigência" na tela está perguntando quantos acordos vigentes o
|
|
269
|
+
// calendário deixou de fora nesta competência. Um acordo cancelado no ano
|
|
270
|
+
// passado não é resposta a essa pergunta, e somá-lo aqui inflaria o número
|
|
271
|
+
// todo mês, para sempre, com o acervo morto do escritório.
|
|
272
|
+
if (verdict !== 'suspended' && verdict !== 'canceled')
|
|
273
|
+
notApplicable += 1;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// O desconto acordado entra no valor do recebível: a dívida é o líquido, e
|
|
277
|
+
// guardar o bruto obrigaria toda leitura a lembrar de subtrair.
|
|
278
|
+
const amount = Math.max(0, Number(fee.amount) - Number(fee.discount));
|
|
279
|
+
const dueDate = dueDateFor(input.competence, fee.due_day);
|
|
280
|
+
const receivableId = uuidv7();
|
|
281
|
+
const rows = await tx `
|
|
282
|
+
INSERT INTO receivable (id, tenant_id, client_id, fee_agreement_id, competence,
|
|
283
|
+
amount, status, due_date, created_by, generated_by)
|
|
284
|
+
VALUES (${receivableId},
|
|
285
|
+
(NULLIF(current_setting('app.current_tenant', true), ''))::uuid,
|
|
286
|
+
${fee.client_id}, ${fee.id}, ${input.competence},
|
|
287
|
+
${amount}, 'open', ${dueDate}, ${input.createdBy ?? null}, ${source})
|
|
288
|
+
-- O predicado precisa casar EXATAMENTE com o do índice parcial do M0,
|
|
289
|
+
-- senão o Postgres não reconhece o índice e o INSERT volta a estourar.
|
|
290
|
+
ON CONFLICT (tenant_id, fee_agreement_id, competence)
|
|
291
|
+
WHERE fee_agreement_id IS NOT NULL AND competence IS NOT NULL AND deleted_at IS NULL
|
|
292
|
+
DO NOTHING
|
|
293
|
+
RETURNING id
|
|
294
|
+
`;
|
|
295
|
+
if (!rows[0]) {
|
|
296
|
+
skipped += 1;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
await tx `
|
|
300
|
+
INSERT INTO receivable_item (id, tenant_id, receivable_id, description, amount)
|
|
301
|
+
VALUES (${uuidv7()},
|
|
302
|
+
(NULLIF(current_setting('app.current_tenant', true), ''))::uuid,
|
|
303
|
+
${receivableId}, ${fee.description}, ${amount})
|
|
304
|
+
`;
|
|
305
|
+
const full = await getReceivable(tx, receivableId);
|
|
306
|
+
created.push(full);
|
|
307
|
+
}
|
|
308
|
+
return { competence: input.competence, created, skipped, notApplicable };
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Lançamento avulso — serviço extra que não vem de honorário recorrente.
|
|
312
|
+
*
|
|
313
|
+
* A guarda de coerência mora AQUI, e não na rota, porque quem chama o domínio
|
|
314
|
+
* direto (script de importação, backfill, outro serviço) precisa da mesma
|
|
315
|
+
* proteção que o operador tem pela tela. Uma validação só na borda HTTP protege
|
|
316
|
+
* a borda HTTP.
|
|
317
|
+
*/
|
|
318
|
+
export async function createOneOffReceivable(tx, input) {
|
|
319
|
+
if (input.amount <= 0)
|
|
320
|
+
throw new ValidationError('Valor deve ser maior que zero');
|
|
321
|
+
if (input.competence) {
|
|
322
|
+
const check = checkCompetenceDueDate(input.competence, input.dueDate);
|
|
323
|
+
if (!check.coherent && !input.competenceMismatchAck) {
|
|
324
|
+
throw new ValidationError(competenceMismatchMessage(input.competence, input.dueDate, check.monthsFromCompetence));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const id = uuidv7();
|
|
328
|
+
const notes = input.notes?.trim() || null;
|
|
329
|
+
const serviceCategory = input.serviceCategory?.trim() || null;
|
|
330
|
+
const metadata = {};
|
|
331
|
+
if (notes)
|
|
332
|
+
metadata.notes = notes;
|
|
333
|
+
if (serviceCategory)
|
|
334
|
+
metadata.serviceCategory = serviceCategory;
|
|
335
|
+
// `ON CONFLICT DO NOTHING` sobre a chave da tentativa: quem chega primeiro
|
|
336
|
+
// grava, quem repete não insere e é devolvido ao recebível existente. A
|
|
337
|
+
// alternativa — consultar antes e inserir depois — deixa uma janela entre as
|
|
338
|
+
// duas operações, e duplo clique acontece exatamente dentro dela.
|
|
339
|
+
const inserted = await tx `
|
|
340
|
+
INSERT INTO receivable (id, tenant_id, client_id, fee_agreement_id, competence,
|
|
341
|
+
amount, status, due_date, created_by, idempotency_key, metadata)
|
|
342
|
+
VALUES (${id},
|
|
343
|
+
(NULLIF(current_setting('app.current_tenant', true), ''))::uuid,
|
|
344
|
+
${input.clientId}, NULL, ${input.competence ?? null},
|
|
345
|
+
${input.amount}, 'open', ${input.dueDate}, ${input.createdBy ?? null},
|
|
346
|
+
${input.idempotencyKey ?? null}, ${tx.json(metadata)})
|
|
347
|
+
ON CONFLICT (tenant_id, idempotency_key) WHERE idempotency_key IS NOT NULL
|
|
348
|
+
DO NOTHING
|
|
349
|
+
RETURNING id
|
|
350
|
+
`;
|
|
351
|
+
if (!inserted[0]) {
|
|
352
|
+
// Tentativa repetida. Devolve o que a primeira criou — nunca um erro: para
|
|
353
|
+
// quem clicou duas vezes, o lançamento deu certo, e um 409 mandaria a pessoa
|
|
354
|
+
// tentar de novo.
|
|
355
|
+
const existing = await tx `
|
|
356
|
+
SELECT id FROM receivable
|
|
357
|
+
WHERE idempotency_key = ${input.idempotencyKey ?? null} AND deleted_at IS NULL
|
|
358
|
+
`;
|
|
359
|
+
if (!existing[0])
|
|
360
|
+
throw new ConflictError('Lançamento avulso em conflito');
|
|
361
|
+
return getReceivable(tx, existing[0].id);
|
|
362
|
+
}
|
|
363
|
+
await tx `
|
|
364
|
+
INSERT INTO receivable_item (id, tenant_id, receivable_id, description, amount)
|
|
365
|
+
VALUES (${uuidv7()},
|
|
366
|
+
(NULLIF(current_setting('app.current_tenant', true), ''))::uuid,
|
|
367
|
+
${id}, ${input.description}, ${input.amount})
|
|
368
|
+
`;
|
|
369
|
+
return getReceivable(tx, id);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* O WHERE compartilhado entre a página e a contagem.
|
|
373
|
+
*
|
|
374
|
+
* Escrito uma vez de propósito: quando a lista e o total repetiam os mesmos
|
|
375
|
+
* predicados lado a lado, bastava acrescentar um filtro num deles para a tela
|
|
376
|
+
* dizer "mostrando 12 de 47" com os dois números medindo carteiras diferentes.
|
|
377
|
+
*/
|
|
378
|
+
function receivableWhere(tx, filter) {
|
|
379
|
+
const search = filter.search?.trim();
|
|
380
|
+
return tx `
|
|
381
|
+
r.deleted_at IS NULL
|
|
382
|
+
${filter.clientId ? tx `AND r.client_id = ${filter.clientId}` : tx ``}
|
|
383
|
+
${filter.competence ? tx `AND r.competence = ${filter.competence}` : tx ``}
|
|
384
|
+
${filter.status ? tx `AND ${tx.unsafe(EFFECTIVE_STATUS_SQL)} = ${filter.status}` : tx ``}
|
|
385
|
+
${filter.openOnly ? tx `AND r.status IN ('open','partially_paid','overdue')` : tx ``}
|
|
386
|
+
${filter.overdueOnly ? tx `AND ${tx.unsafe(EFFECTIVE_STATUS_SQL)} = 'overdue'` : tx ``}
|
|
387
|
+
${filter.origin
|
|
388
|
+
? filter.origin === 'recurring'
|
|
389
|
+
? tx `AND r.fee_agreement_id IS NOT NULL`
|
|
390
|
+
: tx `AND r.fee_agreement_id IS NULL`
|
|
391
|
+
: tx ``}
|
|
392
|
+
${filter.dueFrom ? tx `AND r.due_date >= ${filter.dueFrom}::date` : tx ``}
|
|
393
|
+
${filter.dueTo ? tx `AND r.due_date <= ${filter.dueTo}::date` : tx ``}
|
|
394
|
+
${search
|
|
395
|
+
? tx `AND (c.display_name ILIKE ${'%' + search + '%'}
|
|
396
|
+
OR EXISTS (SELECT 1 FROM receivable_item i
|
|
397
|
+
WHERE i.receivable_id = r.id
|
|
398
|
+
AND i.description ILIKE ${'%' + search + '%'}))`
|
|
399
|
+
: tx ``}
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
export async function listReceivables(tx, filter = {}) {
|
|
403
|
+
const limit = Math.min(Math.max(filter.limit ?? 50, 1), 200);
|
|
404
|
+
const offset = Math.max(filter.offset ?? 0, 0);
|
|
405
|
+
const where = receivableWhere(tx, filter);
|
|
406
|
+
const rows = await tx `
|
|
407
|
+
${tx.unsafe(SELECT_RECEIVABLE)}
|
|
408
|
+
WHERE ${where}
|
|
409
|
+
ORDER BY r.due_date NULLS LAST, r.created_at
|
|
410
|
+
LIMIT ${limit} OFFSET ${offset}
|
|
411
|
+
`;
|
|
412
|
+
// O `LEFT JOIN client` acompanha a contagem porque a busca textual alcança o
|
|
413
|
+
// nome do cliente: sem ele, filtrar por nome quebraria o total.
|
|
414
|
+
const totals = await tx `
|
|
415
|
+
SELECT count(*) AS n FROM receivable r
|
|
416
|
+
LEFT JOIN client c ON c.id = r.client_id AND c.deleted_at IS NULL
|
|
417
|
+
WHERE ${where}
|
|
418
|
+
`;
|
|
419
|
+
return { items: rows.map(mapReceivable), total: Number(totals[0]?.n ?? 0) };
|
|
420
|
+
}
|
|
421
|
+
export async function getReceivable(tx, id) {
|
|
422
|
+
const rows = await tx `
|
|
423
|
+
${tx.unsafe(SELECT_RECEIVABLE)} WHERE r.id = ${id} AND r.deleted_at IS NULL
|
|
424
|
+
`;
|
|
425
|
+
const row = rows[0];
|
|
426
|
+
if (!row)
|
|
427
|
+
throw new NotFoundError('Recebível não encontrado');
|
|
428
|
+
return mapReceivable(row);
|
|
429
|
+
}
|
|
430
|
+
export async function listReceivableItems(tx, receivableId) {
|
|
431
|
+
const rows = await tx `
|
|
432
|
+
SELECT id, receivable_id, description, amount, service_item_ref, created_at
|
|
433
|
+
FROM receivable_item WHERE receivable_id = ${receivableId} ORDER BY created_at
|
|
434
|
+
`;
|
|
435
|
+
return rows.map((r) => ({
|
|
436
|
+
id: r.id,
|
|
437
|
+
receivableId: r.receivable_id,
|
|
438
|
+
description: r.description,
|
|
439
|
+
amount: Number(r.amount),
|
|
440
|
+
serviceItemRef: r.service_item_ref,
|
|
441
|
+
createdAt: r.created_at.toISOString(),
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
export async function listPayments(tx, receivableId) {
|
|
445
|
+
const rows = await tx `
|
|
446
|
+
SELECT id, receivable_id, charge_id, amount, method, status, paid_at, created_at
|
|
447
|
+
FROM payment WHERE receivable_id = ${receivableId} ORDER BY paid_at
|
|
448
|
+
`;
|
|
449
|
+
return rows.map((r) => ({
|
|
450
|
+
id: r.id,
|
|
451
|
+
receivableId: r.receivable_id,
|
|
452
|
+
chargeId: r.charge_id,
|
|
453
|
+
amount: Number(r.amount),
|
|
454
|
+
method: r.method,
|
|
455
|
+
status: r.status,
|
|
456
|
+
paidAt: r.paid_at.toISOString(),
|
|
457
|
+
createdAt: r.created_at.toISOString(),
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Registra dinheiro recebido e reavalia o status do recebível.
|
|
462
|
+
*
|
|
463
|
+
* A soma dos pagamentos confirmados é a fonte da verdade do que foi pago — o
|
|
464
|
+
* status é consequência dela, nunca o contrário. Por isso o recálculo lê a soma
|
|
465
|
+
* do banco em vez de somar em memória: dois lançamentos concorrentes não podem
|
|
466
|
+
* produzir um `paid` com saldo em aberto.
|
|
467
|
+
*/
|
|
468
|
+
export async function registerPayment(tx, input) {
|
|
469
|
+
if (input.amount <= 0)
|
|
470
|
+
throw new ValidationError('Valor do pagamento deve ser maior que zero');
|
|
471
|
+
const current = await getReceivable(tx, input.receivableId);
|
|
472
|
+
if (current.status === 'canceled') {
|
|
473
|
+
throw new ConflictError('Recebível cancelado não aceita baixa');
|
|
474
|
+
}
|
|
475
|
+
if (input.amount > current.outstandingAmount + 0.001) {
|
|
476
|
+
throw new ValidationError(`Valor excede o saldo em aberto (${current.outstandingAmount.toFixed(2)})`);
|
|
477
|
+
}
|
|
478
|
+
await tx `
|
|
479
|
+
INSERT INTO payment (id, tenant_id, receivable_id, charge_id, amount, method, status, paid_at, metadata)
|
|
480
|
+
VALUES (${uuidv7()},
|
|
481
|
+
(NULLIF(current_setting('app.current_tenant', true), ''))::uuid,
|
|
482
|
+
${input.receivableId}, ${input.chargeId ?? null}, ${input.amount},
|
|
483
|
+
${input.method ?? 'manual'}, 'confirmed',
|
|
484
|
+
${input.paidAt ?? new Date().toISOString()},
|
|
485
|
+
${tx.json({ note: input.note ?? null })})
|
|
486
|
+
`;
|
|
487
|
+
const [totals] = await tx `
|
|
488
|
+
SELECT COALESCE((SELECT sum(p.amount) FROM payment p
|
|
489
|
+
WHERE p.receivable_id = r.id AND p.status = 'confirmed'), 0) AS paid,
|
|
490
|
+
r.amount
|
|
491
|
+
FROM receivable r WHERE r.id = ${input.receivableId}
|
|
492
|
+
`;
|
|
493
|
+
const paid = Number(totals.paid);
|
|
494
|
+
const total = Number(totals.amount);
|
|
495
|
+
// Tolerância de um centavo: `numeric(14,2)` não sofre erro de ponto flutuante,
|
|
496
|
+
// mas o valor chega da borda HTTP como number — e 0.1+0.2 continua sendo
|
|
497
|
+
// 0.30000000000000004 em JavaScript.
|
|
498
|
+
const nextStatus = paid + 0.001 >= total ? 'paid' : 'partially_paid';
|
|
499
|
+
await tx `
|
|
500
|
+
UPDATE receivable
|
|
501
|
+
SET status = ${nextStatus},
|
|
502
|
+
updated_at = now(),
|
|
503
|
+
version = version + 1
|
|
504
|
+
WHERE id = ${input.receivableId}
|
|
505
|
+
`;
|
|
506
|
+
return getReceivable(tx, input.receivableId);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Anula a liquidação registrada para uma cobrança — o dinheiro nunca entrou.
|
|
510
|
+
*
|
|
511
|
+
* Não confundir com estorno. No estorno o dinheiro **entrou e saiu**: são dois
|
|
512
|
+
* fatos de caixa, a dívida foi quitada, e reabri-la seria cobrar de novo algo
|
|
513
|
+
* que foi pago. Aqui não há fato novo — há um fato anterior que deixou de valer,
|
|
514
|
+
* e manter o recebível quitado seria o sistema afirmando um recebimento que a
|
|
515
|
+
* própria origem já negou.
|
|
516
|
+
*
|
|
517
|
+
* **Nada é apagado.** A linha de `payment` permanece com valor, método e data em
|
|
518
|
+
* que se acreditou ter recebido; ela sai do saldo e passa a dizer quando e por
|
|
519
|
+
* que foi anulada. `charge.paid_at` também permanece — é o registro de quando a
|
|
520
|
+
* cobrança esteve liquidada.
|
|
521
|
+
*
|
|
522
|
+
* **Idempotente por construção:** o `UPDATE` só alcança pagamentos ainda
|
|
523
|
+
* `confirmed`. Uma segunda reversão do mesmo evento encontra zero linhas, não
|
|
524
|
+
* mexe em nada e devolve `reversedPayments: 0`. Não há contador a decrementar
|
|
525
|
+
* duas vezes.
|
|
526
|
+
*
|
|
527
|
+
* Só toca pagamentos **desta cobrança**. Uma baixa manual que o escritório deu
|
|
528
|
+
* por fora (`charge_id IS NULL`) é outro fato, de outra origem, e o gateway não
|
|
529
|
+
* tem autoridade sobre ela.
|
|
530
|
+
*/
|
|
531
|
+
export async function reverseSettlementForCharge(tx, chargeId, reason) {
|
|
532
|
+
const reversed = await tx `
|
|
533
|
+
UPDATE payment
|
|
534
|
+
SET status = 'reversed', reversed_at = now(), reversal_reason = ${reason}
|
|
535
|
+
WHERE charge_id = ${chargeId} AND status = 'confirmed'
|
|
536
|
+
RETURNING receivable_id, amount
|
|
537
|
+
`;
|
|
538
|
+
// A cobrança sai de `paid` mesmo quando não havia pagamento a anular: o evento
|
|
539
|
+
// é da origem, e o estado tem de refletir o que ela diz. `reversed` não entra
|
|
540
|
+
// na lista de cobranças mortas — o boleto ou o PIX continuam pagáveis, e esta
|
|
541
|
+
// segue sendo a cobrança viva do recebível.
|
|
542
|
+
await tx `
|
|
543
|
+
UPDATE charge
|
|
544
|
+
SET status = 'reversed', reversed_at = now(), updated_at = now()
|
|
545
|
+
WHERE id = ${chargeId} AND status IN ('paid', 'sent', 'pending', 'overdue')
|
|
546
|
+
`;
|
|
547
|
+
const receivableId = reversed[0]?.receivable_id ?? null;
|
|
548
|
+
const reversedAmount = reversed.reduce((sum, r) => sum + Number(r.amount), 0);
|
|
549
|
+
if (!receivableId) {
|
|
550
|
+
return {
|
|
551
|
+
reversedPayments: reversed.length,
|
|
552
|
+
reversedAmount,
|
|
553
|
+
receivableId: null,
|
|
554
|
+
receivable: null,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
// O status do recebível é recalculado a partir do que SOBROU de confirmado —
|
|
558
|
+
// nunca assumido como `open`. Uma baixa parcial anterior, ou uma baixa manual
|
|
559
|
+
// do escritório, continua valendo e o recebível continua parcialmente pago.
|
|
560
|
+
const [totals] = await tx `
|
|
561
|
+
SELECT COALESCE((SELECT sum(p.amount) FROM payment p
|
|
562
|
+
WHERE p.receivable_id = r.id AND p.status = 'confirmed'), 0) AS paid,
|
|
563
|
+
r.amount
|
|
564
|
+
FROM receivable r WHERE r.id = ${receivableId}
|
|
565
|
+
`;
|
|
566
|
+
const paid = Number(totals.paid);
|
|
567
|
+
const total = Number(totals.amount);
|
|
568
|
+
const nextStatus = paid + 0.001 >= total ? 'paid' : paid > 0 ? 'partially_paid' : 'open';
|
|
569
|
+
// `canceled`, `renegotiated` e `written_off` são decisões do escritório sobre
|
|
570
|
+
// a dívida, e um evento de gateway não as revoga.
|
|
571
|
+
await tx `
|
|
572
|
+
UPDATE receivable
|
|
573
|
+
SET status = ${nextStatus}, updated_at = now(), version = version + 1
|
|
574
|
+
WHERE id = ${receivableId}
|
|
575
|
+
AND status IN ('open', 'partially_paid', 'paid', 'overdue')
|
|
576
|
+
`;
|
|
577
|
+
return {
|
|
578
|
+
reversedPayments: reversed.length,
|
|
579
|
+
reversedAmount,
|
|
580
|
+
receivableId,
|
|
581
|
+
receivable: await getReceivable(tx, receivableId),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
export async function cancelReceivable(tx, id) {
|
|
585
|
+
const current = await getReceivable(tx, id);
|
|
586
|
+
if (current.amountPaid > 0) {
|
|
587
|
+
throw new ConflictError('Recebível com pagamento registrado não pode ser cancelado');
|
|
588
|
+
}
|
|
589
|
+
await tx `
|
|
590
|
+
UPDATE receivable SET status = 'canceled', updated_at = now(), version = version + 1
|
|
591
|
+
WHERE id = ${id} AND deleted_at IS NULL
|
|
592
|
+
`;
|
|
593
|
+
return getReceivable(tx, id);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Materializa a transição para `overdue`.
|
|
597
|
+
*
|
|
598
|
+
* A leitura já deriva o vencido (ver nota no topo) — esta função existe para dar
|
|
599
|
+
* um INSTANTE à transição, que é o que a régua de cobrança precisa para disparar
|
|
600
|
+
* `RECEIVABLE_OVERDUE` uma única vez. Retorna os ids afetados justamente para
|
|
601
|
+
* quem for emitir os eventos na próxima onda.
|
|
602
|
+
*/
|
|
603
|
+
export async function markOverdueReceivables(tx) {
|
|
604
|
+
const rows = await tx `
|
|
605
|
+
UPDATE receivable
|
|
606
|
+
SET status = 'overdue', updated_at = now(), version = version + 1
|
|
607
|
+
WHERE deleted_at IS NULL
|
|
608
|
+
AND status = 'open'
|
|
609
|
+
AND due_date IS NOT NULL
|
|
610
|
+
AND due_date < CURRENT_DATE
|
|
611
|
+
RETURNING id
|
|
612
|
+
`;
|
|
613
|
+
return rows.map((r) => r.id);
|
|
614
|
+
}
|
|
615
|
+
export async function financialSummary(tx) {
|
|
616
|
+
const [r] = await tx `
|
|
617
|
+
WITH base AS (
|
|
618
|
+
SELECT r.*, ${tx.unsafe(EFFECTIVE_STATUS_SQL)} AS eff,
|
|
619
|
+
COALESCE((SELECT sum(p.amount) FROM payment p
|
|
620
|
+
WHERE p.receivable_id = r.id AND p.status = 'confirmed'), 0) AS paid
|
|
621
|
+
FROM receivable r WHERE r.deleted_at IS NULL AND r.status <> 'canceled'
|
|
622
|
+
)
|
|
623
|
+
SELECT
|
|
624
|
+
COALESCE(sum(amount), 0) AS total_receivable,
|
|
625
|
+
COALESCE(sum(amount - paid) FILTER (WHERE eff IN ('open','partially_paid','overdue')), 0) AS total_open,
|
|
626
|
+
COALESCE(sum(amount - paid) FILTER (WHERE eff = 'overdue'), 0) AS total_overdue,
|
|
627
|
+
COALESCE(sum(paid), 0) AS total_paid,
|
|
628
|
+
COALESCE(sum(amount - paid) FILTER (
|
|
629
|
+
WHERE eff IN ('open','partially_paid')
|
|
630
|
+
AND due_date BETWEEN CURRENT_DATE AND CURRENT_DATE + 7), 0) AS total_due_soon,
|
|
631
|
+
COALESCE(count(DISTINCT client_id) FILTER (WHERE eff = 'overdue'), 0)::int AS overdue_client_count,
|
|
632
|
+
COALESCE(count(*) FILTER (WHERE eff IN ('open','partially_paid','overdue')), 0)::int AS open_count,
|
|
633
|
+
COALESCE(count(*) FILTER (WHERE eff = 'overdue'), 0)::int AS overdue_count
|
|
634
|
+
FROM base
|
|
635
|
+
`;
|
|
636
|
+
return {
|
|
637
|
+
totalReceivable: Number(r.total_receivable),
|
|
638
|
+
totalOpen: Number(r.total_open),
|
|
639
|
+
totalOverdue: Number(r.total_overdue),
|
|
640
|
+
totalPaid: Number(r.total_paid),
|
|
641
|
+
totalDueSoon: Number(r.total_due_soon),
|
|
642
|
+
overdueClientCount: r.overdue_client_count,
|
|
643
|
+
openCount: r.open_count,
|
|
644
|
+
overdueCount: r.overdue_count,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Faixas de aging. Padrão do produto, **não** política imutável: as faixas são
|
|
649
|
+
* parâmetro justamente para que um escritório possa usar as suas sem alteração
|
|
650
|
+
* de código.
|
|
651
|
+
*/
|
|
652
|
+
export const DEFAULT_AGING_BUCKETS = [
|
|
653
|
+
{ label: '1-7', from: 1, to: 7 },
|
|
654
|
+
{ label: '8-15', from: 8, to: 15 },
|
|
655
|
+
{ label: '16-30', from: 16, to: 30 },
|
|
656
|
+
{ label: '31-60', from: 31, to: 60 },
|
|
657
|
+
{ label: '61-90', from: 61, to: 90 },
|
|
658
|
+
{ label: '90+', from: 91, to: null },
|
|
659
|
+
];
|
|
660
|
+
export async function agingBuckets(tx, buckets = DEFAULT_AGING_BUCKETS) {
|
|
661
|
+
const rows = await tx `
|
|
662
|
+
SELECT (CURRENT_DATE - r.due_date)::int AS days_overdue,
|
|
663
|
+
(r.amount - COALESCE((SELECT sum(p.amount) FROM payment p
|
|
664
|
+
WHERE p.receivable_id = r.id AND p.status = 'confirmed'), 0)) AS outstanding,
|
|
665
|
+
r.client_id
|
|
666
|
+
FROM receivable r
|
|
667
|
+
WHERE r.deleted_at IS NULL
|
|
668
|
+
AND r.status IN ('open','partially_paid','overdue')
|
|
669
|
+
AND r.due_date IS NOT NULL
|
|
670
|
+
AND r.due_date < CURRENT_DATE
|
|
671
|
+
`;
|
|
672
|
+
return buckets.map((b) => {
|
|
673
|
+
const inBucket = rows.filter((r) => r.days_overdue >= b.from && (b.to === null || r.days_overdue <= b.to));
|
|
674
|
+
return {
|
|
675
|
+
...b,
|
|
676
|
+
amount: Number(inBucket.reduce((s, r) => s + Number(r.outstanding), 0).toFixed(2)),
|
|
677
|
+
count: inBucket.length,
|
|
678
|
+
clientCount: new Set(inBucket.map((r) => r.client_id)).size,
|
|
679
|
+
};
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/** Situação financeira de um cliente — usada no console e no portal. */
|
|
683
|
+
export async function clientFinancialSummary(tx, clientId) {
|
|
684
|
+
const [r] = await tx `
|
|
685
|
+
WITH base AS (
|
|
686
|
+
SELECT r.*, ${tx.unsafe(EFFECTIVE_STATUS_SQL)} AS eff,
|
|
687
|
+
COALESCE((SELECT sum(p.amount) FROM payment p
|
|
688
|
+
WHERE p.receivable_id = r.id AND p.status = 'confirmed'), 0) AS paid
|
|
689
|
+
FROM receivable r
|
|
690
|
+
WHERE r.deleted_at IS NULL AND r.client_id = ${clientId} AND r.status <> 'canceled'
|
|
691
|
+
)
|
|
692
|
+
SELECT
|
|
693
|
+
COALESCE(sum(amount - paid) FILTER (WHERE eff IN ('open','partially_paid','overdue')), 0) AS open,
|
|
694
|
+
COALESCE(sum(amount - paid) FILTER (WHERE eff = 'overdue'), 0) AS overdue,
|
|
695
|
+
COALESCE(sum(paid), 0) AS paid,
|
|
696
|
+
min(due_date) FILTER (WHERE eff IN ('open','partially_paid')) AS next_due
|
|
697
|
+
FROM base
|
|
698
|
+
`;
|
|
699
|
+
return {
|
|
700
|
+
open: Number(r.open),
|
|
701
|
+
overdue: Number(r.overdue),
|
|
702
|
+
paid: Number(r.paid),
|
|
703
|
+
nextDueDate: iso(r.next_due),
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
export { OPEN_STATUSES };
|
|
707
|
+
//# sourceMappingURL=ar-repository.js.map
|