@vendure/core
Version:
A modern, headless ecommerce framework
281 lines • 15.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StockMovementService = void 0;
const common_1 = require("@nestjs/common");
const generated_types_1 = require("@vendure/common/lib/generated-types");
const typeorm_1 = require("typeorm");
const instrument_decorator_1 = require("../../common/instrument-decorator");
const utils_1 = require("../../common/utils");
const transactional_connection_1 = require("../../connection/transactional-connection");
const order_line_entity_1 = require("../../entity/order-line/order-line.entity");
const product_variant_entity_1 = require("../../entity/product-variant/product-variant.entity");
const allocation_entity_1 = require("../../entity/stock-movement/allocation.entity");
const cancellation_entity_1 = require("../../entity/stock-movement/cancellation.entity");
const release_entity_1 = require("../../entity/stock-movement/release.entity");
const sale_entity_1 = require("../../entity/stock-movement/sale.entity");
const stock_adjustment_entity_1 = require("../../entity/stock-movement/stock-adjustment.entity");
const stock_movement_entity_1 = require("../../entity/stock-movement/stock-movement.entity");
const event_bus_1 = require("../../event-bus/event-bus");
const stock_movement_event_1 = require("../../event-bus/events/stock-movement-event");
const list_query_builder_1 = require("../helpers/list-query-builder/list-query-builder");
const global_settings_service_1 = require("./global-settings.service");
const stock_level_service_1 = require("./stock-level.service");
const stock_location_service_1 = require("./stock-location.service");
/**
* @description
* Contains methods relating to {@link StockMovement} entities.
*
* @docsCategory services
*/
let StockMovementService = class StockMovementService {
constructor(connection, listQueryBuilder, globalSettingsService, stockLevelService, eventBus, stockLocationService) {
this.connection = connection;
this.listQueryBuilder = listQueryBuilder;
this.globalSettingsService = globalSettingsService;
this.stockLevelService = stockLevelService;
this.eventBus = eventBus;
this.stockLocationService = stockLocationService;
}
/**
* @description
* Returns a {@link PaginatedList} of all StockMovements associated with the specified ProductVariant.
*/
getStockMovementsByProductVariantId(ctx, productVariantId, options) {
const qb = this.listQueryBuilder
.build(stock_movement_entity_1.StockMovement, options, { ctx })
.leftJoin('stockmovement.productVariant', 'productVariant')
.andWhere('productVariant.id = :productVariantId', { productVariantId });
if (options === null || options === void 0 ? void 0 : options.type) {
qb.andWhere('stockmovement.type = :type', { type: options.type });
}
return qb.getManyAndCount().then(([items, totalItems]) => ({
items,
totalItems,
}));
}
/**
* @description
* Adjusts the stock level of the ProductVariant, creating a new {@link StockAdjustment} entity
* in the process.
*/
async adjustProductVariantStock(ctx, productVariantId, stockOnHandNumberOrInput) {
let stockOnHandInputs;
if (typeof stockOnHandNumberOrInput === 'number') {
const defaultStockLocation = await this.stockLocationService.defaultStockLocation(ctx);
stockOnHandInputs = [
{ stockLocationId: defaultStockLocation.id, stockOnHand: stockOnHandNumberOrInput },
];
}
else {
stockOnHandInputs = stockOnHandNumberOrInput;
}
const adjustments = [];
for (const input of stockOnHandInputs) {
const stockLevel = await this.stockLevelService.getStockLevel(ctx, productVariantId, input.stockLocationId);
const oldStockLevel = stockLevel.stockOnHand;
const newStockLevel = input.stockOnHand;
if (oldStockLevel === newStockLevel) {
continue;
}
const delta = newStockLevel - oldStockLevel;
const adjustment = await this.connection.getRepository(ctx, stock_adjustment_entity_1.StockAdjustment).save(new stock_adjustment_entity_1.StockAdjustment({
quantity: delta,
stockLocation: { id: input.stockLocationId },
productVariant: { id: productVariantId },
}));
await this.stockLevelService.updateStockOnHandForLocation(ctx, productVariantId, input.stockLocationId, delta);
await this.eventBus.publish(new stock_movement_event_1.StockMovementEvent(ctx, [adjustment]));
adjustments.push(adjustment);
}
return adjustments;
}
/**
* @description
* Creates a new {@link Allocation} for each OrderLine in the Order. For ProductVariants
* which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
* increased, indicating that this quantity of stock is allocated and cannot be sold.
*/
async createAllocationsForOrder(ctx, order) {
const lines = order.lines.map(orderLine => ({
orderLineId: orderLine.id,
quantity: orderLine.quantity,
}));
return this.createAllocationsForOrderLines(ctx, lines);
}
/**
* @description
* Creates a new {@link Allocation} for each of the given OrderLines. For ProductVariants
* which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
* increased, indicating that this quantity of stock is allocated and cannot be sold.
*/
async createAllocationsForOrderLines(ctx, lines) {
const allocations = [];
const globalTrackInventory = (await this.globalSettingsService.getSettings(ctx)).trackInventory;
for (const { orderLineId, quantity } of lines) {
const orderLine = await this.connection.getEntityOrThrow(ctx, order_line_entity_1.OrderLine, orderLineId);
const productVariant = await this.connection.getEntityOrThrow(ctx, product_variant_entity_1.ProductVariant, orderLine.productVariantId, { includeSoftDeleted: true });
const allocationLocations = await this.stockLocationService.getAllocationLocations(ctx, orderLine, quantity);
for (const allocationLocation of allocationLocations) {
const allocation = new allocation_entity_1.Allocation({
productVariant: new product_variant_entity_1.ProductVariant({ id: orderLine.productVariantId }),
stockLocation: allocationLocation.location,
quantity: allocationLocation.quantity,
orderLine,
});
allocations.push(allocation);
if (this.trackInventoryForVariant(productVariant, globalTrackInventory)) {
await this.stockLevelService.updateStockAllocatedForLocation(ctx, orderLine.productVariantId, allocationLocation.location.id, allocationLocation.quantity);
}
}
}
const savedAllocations = await this.connection.getRepository(ctx, allocation_entity_1.Allocation).save(allocations);
if (savedAllocations.length) {
await this.eventBus.publish(new stock_movement_event_1.StockMovementEvent(ctx, savedAllocations));
}
return savedAllocations;
}
/**
* @description
* Creates {@link Sale}s for each OrderLine in the Order. For ProductVariants
* which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
* reduced and the `stockOnHand` value is also reduced by the OrderLine quantity, indicating
* that the stock is no longer allocated, but is actually sold and no longer available.
*/
async createSalesForOrder(ctx, lines) {
const sales = [];
const globalTrackInventory = (await this.globalSettingsService.getSettings(ctx)).trackInventory;
const orderLines = await this.connection
.getRepository(ctx, order_line_entity_1.OrderLine)
.find({ where: { id: (0, typeorm_1.In)(lines.map(line => line.orderLineId)) } });
for (const lineRow of lines) {
const orderLine = orderLines.find(line => (0, utils_1.idsAreEqual)(line.id, lineRow.orderLineId));
if (!orderLine) {
continue;
}
const productVariant = await this.connection.getEntityOrThrow(ctx, product_variant_entity_1.ProductVariant, orderLine.productVariantId, { includeSoftDeleted: true });
const saleLocations = await this.stockLocationService.getSaleLocations(ctx, orderLine, lineRow.quantity);
for (const saleLocation of saleLocations) {
const sale = new sale_entity_1.Sale({
productVariant,
quantity: lineRow.quantity * -1,
orderLine,
stockLocation: saleLocation.location,
});
sales.push(sale);
if (this.trackInventoryForVariant(productVariant, globalTrackInventory)) {
await this.stockLevelService.updateStockAllocatedForLocation(ctx, orderLine.productVariantId, saleLocation.location.id, -saleLocation.quantity);
await this.stockLevelService.updateStockOnHandForLocation(ctx, orderLine.productVariantId, saleLocation.location.id, -saleLocation.quantity);
}
}
}
const savedSales = await this.connection.getRepository(ctx, sale_entity_1.Sale).save(sales);
if (savedSales.length) {
await this.eventBus.publish(new stock_movement_event_1.StockMovementEvent(ctx, savedSales));
}
return savedSales;
}
/**
* @description
* Creates a {@link Cancellation} for each of the specified OrderItems. For ProductVariants
* which are configured to track stock levels, the `ProductVariant.stockOnHand` value is
* increased for each Cancellation, allowing that stock to be sold again.
*/
async createCancellationsForOrderLines(ctx, lineInputs) {
const orderLines = await this.connection.getRepository(ctx, order_line_entity_1.OrderLine).find({
where: {
id: (0, typeorm_1.In)(lineInputs.map(l => l.orderLineId)),
},
relations: ['productVariant'],
});
const cancellations = [];
const globalTrackInventory = (await this.globalSettingsService.getSettings(ctx)).trackInventory;
for (const orderLine of orderLines) {
const lineInput = lineInputs.find(l => (0, utils_1.idsAreEqual)(l.orderLineId, orderLine.id));
if (!lineInput) {
continue;
}
const cancellationLocations = await this.stockLocationService.getCancellationLocations(ctx, orderLine, lineInput.quantity);
for (const cancellationLocation of cancellationLocations) {
const cancellation = new cancellation_entity_1.Cancellation({
productVariant: orderLine.productVariant,
quantity: lineInput.quantity,
orderLine,
stockLocation: cancellationLocation.location,
});
cancellations.push(cancellation);
if (this.trackInventoryForVariant(orderLine.productVariant, globalTrackInventory)) {
await this.stockLevelService.updateStockOnHandForLocation(ctx, orderLine.productVariantId, cancellationLocation.location.id, cancellationLocation.quantity);
}
}
}
const savedCancellations = await this.connection.getRepository(ctx, cancellation_entity_1.Cancellation).save(cancellations);
if (savedCancellations.length) {
await this.eventBus.publish(new stock_movement_event_1.StockMovementEvent(ctx, savedCancellations));
}
return savedCancellations;
}
/**
* @description
* Creates a {@link Release} for each of the specified OrderItems. For ProductVariants
* which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
* reduced, indicating that this stock is once again available to buy.
*/
async createReleasesForOrderLines(ctx, lineInputs) {
const releases = [];
const orderLines = await this.connection.getRepository(ctx, order_line_entity_1.OrderLine).find({
where: { id: (0, typeorm_1.In)(lineInputs.map(l => l.orderLineId)) },
relations: ['productVariant'],
});
const globalTrackInventory = (await this.globalSettingsService.getSettings(ctx)).trackInventory;
const variantsMap = new Map();
for (const orderLine of orderLines) {
const lineInput = lineInputs.find(l => (0, utils_1.idsAreEqual)(l.orderLineId, orderLine.id));
if (!lineInput) {
continue;
}
const releaseLocations = await this.stockLocationService.getReleaseLocations(ctx, orderLine, lineInput.quantity);
for (const releaseLocation of releaseLocations) {
const release = new release_entity_1.Release({
productVariant: orderLine.productVariant,
quantity: lineInput.quantity,
orderLine,
stockLocation: releaseLocation.location,
});
releases.push(release);
if (this.trackInventoryForVariant(orderLine.productVariant, globalTrackInventory)) {
await this.stockLevelService.updateStockAllocatedForLocation(ctx, orderLine.productVariantId, releaseLocation.location.id, -releaseLocation.quantity);
}
}
}
const savedReleases = await this.connection.getRepository(ctx, release_entity_1.Release).save(releases);
if (savedReleases.length) {
await this.eventBus.publish(new stock_movement_event_1.StockMovementEvent(ctx, savedReleases));
}
return savedReleases;
}
trackInventoryForVariant(variant, globalTrackInventory) {
return (variant.trackInventory === generated_types_1.GlobalFlag.TRUE ||
(variant.trackInventory === generated_types_1.GlobalFlag.INHERIT && globalTrackInventory));
}
};
exports.StockMovementService = StockMovementService;
exports.StockMovementService = StockMovementService = __decorate([
(0, common_1.Injectable)(),
(0, instrument_decorator_1.Instrument)(),
__metadata("design:paramtypes", [transactional_connection_1.TransactionalConnection,
list_query_builder_1.ListQueryBuilder,
global_settings_service_1.GlobalSettingsService,
stock_level_service_1.StockLevelService,
event_bus_1.EventBus,
stock_location_service_1.StockLocationService])
], StockMovementService);
//# sourceMappingURL=stock-movement.service.js.map