feat(sync): synchronize customer adresses and orders from Hiboutik sales (#1)
Co-authored-by: MastersGame <quentin.duwa1210@gmail.com> Reviewed-on: #1
This commit is contained in:
27
src/database/customerAddress.database.repository.ts
Normal file
27
src/database/customerAddress.database.repository.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { CustomerAddress } from '../../prisma/generated/client';
|
||||
import { CustomerAddressCreateInput } from '../../prisma/generated/models/CustomerAddress';
|
||||
|
||||
/** Database access to customer addresses. Rows are matched with Hiboutik by `hiboutikId`. */
|
||||
@Injectable()
|
||||
export class CustomerAddressDatabaseRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/** Creates or updates the address with the same `hiboutikId`. */
|
||||
async upsert(address: CustomerAddressCreateInput): Promise<CustomerAddress> {
|
||||
return this.prisma.customerAddress.upsert({
|
||||
where: { hiboutikId: address.hiboutikId },
|
||||
create: address,
|
||||
update: address,
|
||||
});
|
||||
}
|
||||
|
||||
/** Deletes the addresses of a customer that no longer exist in Hiboutik (all of them if `keptHiboutikIds` is empty). */
|
||||
async deleteMissing(customerId: string, keptHiboutikIds: number[]): Promise<number> {
|
||||
const result = await this.prisma.customerAddress.deleteMany({
|
||||
where: { customerId, hiboutikId: { notIn: keptHiboutikIds } },
|
||||
});
|
||||
return result.count;
|
||||
}
|
||||
}
|
||||
61
src/database/order.database.repository.ts
Normal file
61
src/database/order.database.repository.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { Order } from '../../prisma/generated/client';
|
||||
import { OrderWithLinesInput } from '../../prisma/customObjects/prisma.custom.object';
|
||||
import { OrderStatus } from '../models/orderStatus';
|
||||
|
||||
/** Database access to orders. Hiboutik sales are matched by `hiboutikSaleId`. */
|
||||
@Injectable()
|
||||
export class OrderDatabaseRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/** Among `hiboutikSaleIds`, the ones already stored as completed orders. */
|
||||
async getCompletedSaleIds(hiboutikSaleIds: number[]): Promise<Set<number>> {
|
||||
if (hiboutikSaleIds.length === 0) return new Set();
|
||||
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: { hiboutikSaleId: { in: hiboutikSaleIds }, status: OrderStatus.COMPLETED },
|
||||
select: { hiboutikSaleId: true },
|
||||
});
|
||||
return new Set(orders.map((order) => order.hiboutikSaleId).filter((id): id is number => id !== null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates the order with the same `hiboutikSaleId`, replacing all its lines (atomically).
|
||||
* Each line is linked to the current product version matching its Hiboutik product and size, if it still exists.
|
||||
* On update, `source` is kept: a website order sent to Hiboutik stays WEB.
|
||||
*/
|
||||
async upsert(order: OrderWithLinesInput): Promise<Order> {
|
||||
if (order.hiboutikSaleId == null) throw new Error('Cannot upsert an order without hiboutikSaleId');
|
||||
|
||||
const { lines, source, ...orderFields } = order;
|
||||
const linkedLines = await this.linkToProductVersions(lines);
|
||||
return this.prisma.order.upsert({
|
||||
where: { hiboutikSaleId: order.hiboutikSaleId },
|
||||
create: { ...orderFields, source, lines: { create: linkedLines } },
|
||||
update: {
|
||||
...orderFields,
|
||||
// an undefined relation is ignored by Prisma on update: explicitly unlink a customer removed from the sale
|
||||
customer: orderFields.customer ?? { disconnect: true },
|
||||
lines: { deleteMany: {}, create: linkedLines },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async linkToProductVersions(lines: OrderWithLinesInput['lines']): Promise<OrderWithLinesInput['lines']> {
|
||||
if (lines.length === 0) return lines;
|
||||
|
||||
const versions = await this.prisma.productVersion.findMany({
|
||||
where: {
|
||||
OR: lines.map((line) => ({ hiboutikSizeId: line.hiboutikSizeId, product: { hiboutikId: line.hiboutikProductId } })),
|
||||
},
|
||||
select: { id: true, hiboutikSizeId: true, product: { select: { hiboutikId: true } } },
|
||||
});
|
||||
const versionIds = new Map(versions.map((version) => [`${version.product.hiboutikId}:${version.hiboutikSizeId}`, version.id]));
|
||||
|
||||
return lines.map((line) => {
|
||||
const versionId = versionIds.get(`${line.hiboutikProductId}:${line.hiboutikSizeId}`);
|
||||
return versionId ? { ...line, productVersion: { connect: { id: versionId } } } : line;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user