246 lines
8.3 KiB
TypeScript
246 lines
8.3 KiB
TypeScript
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
|
import { Prisma } from '@prisma/client';
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
|
import { TableService } from '../admin-table/table.service';
|
|
import type { TableQueryDto } from '../admin-table/table-query.dto';
|
|
|
|
/**
|
|
* Branch stock allocations — legacy views/invoice/* ("فاکتور انتقال"), served by
|
|
* BranchController as `branch/product`:
|
|
* POST branch/product/table -> the grid
|
|
* POST branch/product -> create { branch_id, products: [id...] }
|
|
* GET branch/product/{id} -> one allocation
|
|
* DELETE branch/product/{id} -> delete
|
|
*
|
|
* Despite the screen being labelled an invoice, it is not a sales document: it
|
|
* records which products head office allocated to a branch, and who did it.
|
|
*/
|
|
@Injectable()
|
|
export class AdminInvoicesService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly table: TableService,
|
|
) {}
|
|
|
|
private static readonly ALLOWED = [
|
|
'id',
|
|
'note',
|
|
'createdAt',
|
|
'branchId',
|
|
'branch.name',
|
|
'allocatorAdminUser.fullName',
|
|
// legacy filter keys
|
|
'name',
|
|
'caj',
|
|
];
|
|
|
|
private static readonly FIELD_ALIASES: Record<string, string> = {
|
|
name: 'branch.name',
|
|
caj: 'createdAt',
|
|
'allocator_user.name': 'allocatorAdminUser.fullName',
|
|
};
|
|
|
|
/** Legacy `POST branch/product/table`. */
|
|
async list(query: TableQueryDto) {
|
|
this.table.assertNotExport(query);
|
|
|
|
const mapField = (f?: string) => (f ? (AdminInvoicesService.FIELD_ALIASES[f] ?? f) : f);
|
|
const aliased: TableQueryDto = {
|
|
...query,
|
|
filters: (query.filters ?? []).map((f) => ({ ...f, field: mapField(f.field) })),
|
|
order: query.order
|
|
? `${query.order.startsWith('-') ? '-' : '+'}${mapField(query.order.replace(/^[+-]/, ''))}`
|
|
: undefined,
|
|
};
|
|
|
|
const { where, orderBy, skip, take, page, limit } = this.table.build(aliased, {
|
|
searchable: ['note', 'branch.name'],
|
|
allowed: AdminInvoicesService.ALLOWED,
|
|
defaultOrderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
const [rows, total] = await Promise.all([
|
|
this.prisma.branchAllocation.findMany({
|
|
where: where as Prisma.BranchAllocationWhereInput,
|
|
orderBy: orderBy as Prisma.BranchAllocationOrderByWithRelationInput,
|
|
skip,
|
|
take,
|
|
include: {
|
|
branch: true,
|
|
allocatorAdminUser: true,
|
|
items: { include: { product: true } },
|
|
},
|
|
}),
|
|
this.prisma.branchAllocation.count({ where: where as Prisma.BranchAllocationWhereInput }),
|
|
]);
|
|
|
|
return this.table.respond(rows.map((r) => this.toRow(r)), total, query, page, limit);
|
|
}
|
|
|
|
private toRow(
|
|
a: Prisma.BranchAllocationGetPayload<{
|
|
include: {
|
|
branch: true;
|
|
allocatorAdminUser: true;
|
|
items: { include: { product: true } };
|
|
};
|
|
}>,
|
|
) {
|
|
const sumWeight = a.items.reduce(
|
|
(sum, it) => sum.add(it.weightGrams ?? new Prisma.Decimal(0)),
|
|
new Prisma.Decimal(0),
|
|
);
|
|
|
|
return {
|
|
id: a.id,
|
|
// legacy column key is `name` — the receiving branch
|
|
name: a.branch.name,
|
|
branch_id: a.branchId,
|
|
branch: { id: a.branch.id, name: a.branch.name },
|
|
sum_weight: sumWeight,
|
|
count_item: a.items.length,
|
|
note: a.note,
|
|
allocator_user: a.allocatorAdminUser
|
|
? { id: a.allocatorAdminUser.id, name: a.allocatorAdminUser.fullName }
|
|
: null,
|
|
caj: a.createdAt,
|
|
products: a.items.map((it) => ({
|
|
id: it.productId,
|
|
pid: it.productId,
|
|
title: it.product.title,
|
|
product_code: it.product.productCode,
|
|
grams: it.weightGrams,
|
|
})),
|
|
};
|
|
}
|
|
|
|
/** Legacy `GET branch/product/{id}`. */
|
|
async findOne(id: string) {
|
|
const a = await this.prisma.branchAllocation.findUnique({
|
|
where: { id },
|
|
include: {
|
|
branch: true,
|
|
allocatorAdminUser: true,
|
|
items: { include: { product: true } },
|
|
},
|
|
});
|
|
if (!a) throw new NotFoundException('فاکتور یافت نشد');
|
|
return this.toRow(a);
|
|
}
|
|
|
|
/**
|
|
* Legacy `POST branch/product` with `{ branch_id, products: [id...] }`.
|
|
*
|
|
* Each product's current lightest variant weight is snapshotted, so a later
|
|
* catalog edit does not silently rewrite what this document says was moved.
|
|
*/
|
|
async create(data: { branch_id?: string; products?: string[]; note?: string }, actorId: string) {
|
|
const branchId = String(data.branch_id ?? '');
|
|
if (!branchId) throw new BadRequestException('شعبه انتخاب نشده است');
|
|
|
|
const branch = await this.prisma.branch.findUnique({ where: { id: branchId } });
|
|
if (!branch) throw new NotFoundException('شعبه یافت نشد');
|
|
|
|
const productIds = [...new Set((data.products ?? []).map((p) => String(p)).filter(Boolean))];
|
|
if (productIds.length === 0) throw new BadRequestException('هیچ محصولی انتخاب نشده است');
|
|
|
|
const products = await this.prisma.product.findMany({
|
|
where: { id: { in: productIds } },
|
|
include: { variants: { where: { isActive: true } } },
|
|
});
|
|
if (products.length !== productIds.length) {
|
|
throw new BadRequestException('برخی از محصولات انتخابشده یافت نشدند');
|
|
}
|
|
|
|
const allocation = await this.prisma.branchAllocation.create({
|
|
data: {
|
|
branchId,
|
|
allocatorAdminUserId: actorId,
|
|
note: data.note ?? null,
|
|
items: {
|
|
create: products.map((p) => ({
|
|
productId: p.id,
|
|
weightGrams: p.variants.length
|
|
? p.variants.reduce(
|
|
(min, v) => (v.rashWeightGrams.lt(min) ? v.rashWeightGrams : min),
|
|
p.variants[0].rashWeightGrams,
|
|
)
|
|
: null,
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
|
|
return this.findOne(allocation.id);
|
|
}
|
|
|
|
/** Legacy `PUT branch/product/{id}` — the edit screen replaces the product set. */
|
|
async update(id: string, data: { branch_id?: string; products?: string[]; note?: string }) {
|
|
const existing = await this.prisma.branchAllocation.findUnique({ where: { id } });
|
|
if (!existing) throw new NotFoundException('فاکتور یافت نشد');
|
|
|
|
if (data.branch_id) {
|
|
const branch = await this.prisma.branch.findUnique({
|
|
where: { id: String(data.branch_id) },
|
|
});
|
|
if (!branch) throw new NotFoundException('شعبه یافت نشد');
|
|
}
|
|
|
|
if (Array.isArray(data.products)) {
|
|
const productIds = [...new Set(data.products.map((p) => String(p)).filter(Boolean))];
|
|
if (productIds.length === 0) {
|
|
throw new BadRequestException('هیچ محصولی انتخاب نشده است');
|
|
}
|
|
const products = await this.prisma.product.findMany({
|
|
where: { id: { in: productIds } },
|
|
include: { variants: { where: { isActive: true } } },
|
|
});
|
|
|
|
await this.prisma.$transaction([
|
|
this.prisma.branchAllocationItem.deleteMany({ where: { allocationId: id } }),
|
|
this.prisma.branchAllocationItem.createMany({
|
|
data: products.map((p) => ({
|
|
allocationId: id,
|
|
productId: p.id,
|
|
weightGrams: p.variants.length
|
|
? p.variants.reduce(
|
|
(min, v) => (v.rashWeightGrams.lt(min) ? v.rashWeightGrams : min),
|
|
p.variants[0].rashWeightGrams,
|
|
)
|
|
: null,
|
|
})),
|
|
skipDuplicates: true,
|
|
}),
|
|
]);
|
|
}
|
|
|
|
await this.prisma.branchAllocation.update({
|
|
where: { id },
|
|
data: {
|
|
...(data.branch_id ? { branchId: String(data.branch_id) } : {}),
|
|
...(data.note !== undefined ? { note: data.note } : {}),
|
|
},
|
|
});
|
|
|
|
return this.findOne(id);
|
|
}
|
|
|
|
/** Legacy `DELETE branch/product/{id}`. Items cascade. */
|
|
async remove(id: string) {
|
|
const a = await this.prisma.branchAllocation.findUnique({ where: { id } });
|
|
if (!a) throw new NotFoundException('فاکتور یافت نشد');
|
|
await this.prisma.branchAllocation.delete({ where: { id } });
|
|
return { success: true };
|
|
}
|
|
|
|
/** Legacy `GET branch/list` — the branch picker for the invoice form. */
|
|
async branchList() {
|
|
const rows = await this.prisma.branch.findMany({
|
|
where: { isActive: true },
|
|
orderBy: { name: 'asc' },
|
|
select: { id: true, name: true },
|
|
});
|
|
return rows;
|
|
}
|
|
}
|