feat: accounting integration (D27), SMS relay driver (D26), rebuild user apps

- Add Tahesab/Zarrin accounting sync module with admin panel UI
- Add SMS relay driver as default provider, refactor SMS into driver pattern
- Update customer services, orders, products, and catalog queries
- Rebuild user-app and user-app-v2 dist assets
- Add Prisma migration for accounting tables
- Update docker-compose and env examples
- Update parity docs (DECISIONS, ISSUES, PROGRESS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-06 16:48:36 +03:30
co-authored by Claude Opus 4.6
parent a72fa607a5
commit 19d490e790
179 changed files with 12538 additions and 370 deletions
@@ -0,0 +1,48 @@
import { Controller, Get, HttpCode, Post } from '@nestjs/common';
import { CurrentCustomer, CustomerAuth } from '../customer-auth/customer.guards';
import type { AuthenticatedCustomer } from '../customer-auth/customer-jwt.strategy';
import { RequirePermission } from '../auth/guards';
import { AccountingAdminService } from './accounting-admin.service';
import { AccountingSyncService } from './accounting-sync.service';
/**
* D27, plan §12 — sync where people work, on top of the «اتصال حسابداری»
* screen: a button on the wholesale-customer list and on the product list,
* and the customer's own balance read live each time the app's profile opens.
* Each is behind the permission of the screen it sits on, and works whenever a
* provider is connected, whatever the feature switches say (owner's ruling).
*/
@Controller()
export class AccountingQuickController {
constructor(
private readonly admin: AccountingAdminService,
private readonly sync: AccountingSyncService,
) {}
/** Whether to show the sync buttons. Any signed-in admin; reveals no settings. */
@Get('admin/accounting-status')
status() {
return this.admin.connectionStatus();
}
@Post('admin/customers-omde/accounting-sync')
@HttpCode(200)
@RequirePermission('persons.list.client.omde')
syncCustomers() {
return this.admin.syncNow(['customers', 'balances']);
}
@Post('admin/products/accounting-sync')
@HttpCode(200)
@RequirePermission('product.list')
syncProducts() {
return this.admin.syncNow(['products']);
}
/** The app's profile: this customer's balance, read from the software now. */
@CustomerAuth()
@Get('customer/me/accounting-balance')
myBalance(@CurrentCustomer() me: AuthenticatedCustomer) {
return this.sync.customerBalanceLive(me.id);
}
}