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:
co-authored by
Claude Opus 4.6
parent
a72fa607a5
commit
19d490e790
@@ -1,13 +1,20 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { AccountingHooksService } from '../accounting-sync/accounting-hooks.service';
|
||||
|
||||
@Injectable()
|
||||
export class CatalogService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly accounting: AccountingHooksService,
|
||||
) {}
|
||||
|
||||
listProducts() {
|
||||
return this.prisma.product.findMany({
|
||||
where: { isActive: true },
|
||||
async listProducts() {
|
||||
const products = await this.prisma.product.findMany({
|
||||
// D27 — a product with no image is never shown in the app. Products now
|
||||
// also arrive from the accounting software, which carries no pictures;
|
||||
// they appear once an admin photographs them.
|
||||
where: { isActive: true, images: { some: {} } },
|
||||
include: {
|
||||
// Only orderable variants reach the storefront. An inactive one used to
|
||||
// be listed and then rejected by getVariantOrThrow at checkout, so the
|
||||
@@ -26,13 +33,49 @@ export class CatalogService {
|
||||
collections: { include: { collection: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const out = [];
|
||||
for (const p of products) {
|
||||
const { accountingStockWeight, accountingStockCount, accountingStockAt, ...product } = p;
|
||||
// D27 — out of stock in the accounting software: every variant reads as
|
||||
// qty 0, which the apps already render as «ناموجود» and refuse to add to
|
||||
// the cart unless it is a pre-order variant. The stock figures themselves
|
||||
// stay server-side; this endpoint is public.
|
||||
const blocked = await this.accounting.stockBlocked({ accountingStockWeight, accountingStockCount, accountingStockAt });
|
||||
out.push(blocked ? { ...product, variants: product.variants.map((v) => (v.isPreOrder ? v : { ...v, qty: 0 })) } : product);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async getVariantOrThrow(variantId: string) {
|
||||
const variant = await this.prisma.productVariant.findUnique({ where: { id: variantId } });
|
||||
const variant = await this.prisma.productVariant.findUnique({
|
||||
where: { id: variantId },
|
||||
include: {
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
accountingStockWeight: true,
|
||||
accountingStockCount: true,
|
||||
accountingStockAt: true,
|
||||
_count: { select: { images: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!variant || !variant.isActive) {
|
||||
throw new NotFoundException(`Product variant ${variantId} not found or inactive`);
|
||||
}
|
||||
return variant;
|
||||
// The same two rules the listing applies, so a stale client cannot order
|
||||
// around them.
|
||||
if (variant.product._count.images === 0) {
|
||||
throw new BadRequestException('این محصول هنوز تصویر ندارد و قابل سفارش نیست');
|
||||
}
|
||||
// Live from the accounting software at order time; on no answer the last
|
||||
// stored figure decides (liveStock).
|
||||
if (!variant.isPreOrder && (await this.accounting.stockBlocked(await this.accounting.liveStock(variant.product)))) {
|
||||
throw new BadRequestException('این محصول در انبار موجود نیست');
|
||||
}
|
||||
const { product: _product, ...plain } = variant;
|
||||
return plain;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user