Reputation: 5
I have a problem, which I solved already but I feel like my implementation is very basic and could definitely learn a better way to do this. Given 2 array of objects, one for the sales that my customers are demanding, and one for the purchases that I'm doing to my provider, I need to be able to place the orders and know when I will be able to satisfy them. I will only work with one product, to make it simpler. I'm very new at coding this case of problems, so I would really apreciate a point in the right direction. Maybe there's a data structure that I haven't used that could help here.
Properties: 'created': when the sales order was created 'quantity': how many items the customer wants
const sales= [{
'id': 'S1',
'created': '2020-01-02',
'quantity': 6
}, {
'id': 'S2',
'created': '2020-11-05',
'quantity': 2
}, {
'id': 'S3',
'created': '2019-12-04',
'quantity': 3
}, {
'id': 'S4',
'created': '2020-01-20',
'quantity': 2
}, {
'id': 'S5',
'created': '2019-12-15',
'quantity': 9
}];
Properties: 'receiving': when we expect to receive the product 'quantity': how many we will be receiving
const purchases= [{
'id': 'P1',
'receiving': '2020-01-04',
'quantity': 4
}, {
'id': 'P2',
'receiving': '2020-01-05',
'quantity': 3
}, {
'id': 'P3',
'receiving': '2020-02-01',
'quantity': 5
}, {
'id': 'P4',
'receiving': '2020-03-05',
'quantity': 1
}, {
'id': 'P5',
'receiving': '2020-02-20',
'quantity': 7
}];
My code so far. I'm returnign an array that for reach sales, it shows when I will be able to satisfy it. The problem that I'm running with the current implementation is that I cannot cover all the cases.
function allocate(salesOrders, purchaseOrders) {
//ordering sales and purchases by date
const orderedSales = salesOrders.sort((a, b) => a.created.localeCompare(b.created));
const orderedPurchases = purchaseOrders.sort((a, b) => a.receiving.localeCompare(b.receiving));
console.log(orderedSales)
console.log(orderedPurchases)
let stock = 0;
const result = [];
purchaseIndex = 0;
orderedSales.forEach((sale, index) => {
const order = orderedPurchases[purchaseIndex];
if (order) {
console.log("Processing order", sale.id)
console.log(`Leftover stock = ${stock}`)
stock += order.quantity
console.log(`new stock = ${stock}`)
stock = stock - sale.quantity;
console.log(`Sustracting = ${sale.quantity}`)
console.log(`Remaining = ${stock}`)
while (stock < 0) {
purchaseIndex++
console.log(`Demand NOT satified, moving to next purchase order with index ${purchaseIndex}`)
stock += order.quantity
console.log(`Current stock = ${stock}`)
increaseOrder = false;
}
//demand has been satisfied
console.log(`Demand for ${sale.id} was satified with purchase ${order.id}, time is ${order.receiving}, moving to next purchase order`)
result.push({
id: sale.id,
availabilityDate: order.receiving
})
purchaseIndex++
console.log("Next sale ++++++++")
console.log(" ++++++++")
}
});
console.log(result);
}
allocate(salesOrders, purchaseOrders)
Upvotes: 0
Views: 648
Reputation: 50807
I would do it like this. First I would create a collection of events that have type
, date
, id
, and quantity
fields, then sort them by date. This intermediate format might look like this:
[
{type: "sale", date: "2019-12-04", id: "S3", quantity: 3},
{type: "sale", date: "2019-12-15", id: "S5", quantity: 9},
{type: "sale", date: "2020-01-02", id: "S1", quantity: 6},
{type: "purchase", date: "2020-01-04", id: "P1", quantity: 4},
{type: "purchase", date: "2020-01-05", id: "P2", quantity: 3},
{type: "sale", date: "2020-01-20", id: "S4", quantity: 2},
{type: "purchase", date: "2020-02-01", id: "P3", quantity: 5},
{type: "purchase", date: "2020-02-20", id: "P5", quantity: 7},
{type: "purchase", date: "2020-03-05", id: "P4", quantity: 1},
{type: "sale", date: "2020-11-05", id: "S2", quantity: 2}
]
Then I would fold this list of events into a structure with onHand
, completed
and open
properties by checking each event. If it's a purchase
, then we add its quantity to onHand
. Then we loop through the existing open events (plus the current one if it's a sale
), creating new entries to add to the existing completed
array if the quantity
is not bigger than onHand
, and adding to a new open
array if it's too large. The code could look like this:
const process = (initial) => (sales, purchases) => [
...sales.map (({created, ...rest}) => ({type: 'sale', date: created, ...rest})),
...purchases.map (({receiving, ...rest}) => ({type: 'purchase', date: receiving, ...rest})),
] .sort (({date: d1}, {date: d2}) => d1 < d2 ? -1 : d1 > d2 ? 1 : 0) .reduce ((
{onHand, open: o, completed},
{type, date, quantity, id, open = [...o, ... (type == 'sale' ? [{quantity, date, id}] : [])]}
) => open .reduce (
({onHand, open, completed}, {quantity, date: saleDate, id}) => quantity <= onHand
? {onHand: onHand - quantity, open, completed: completed .concat ({date, id, quantity})}
: {onHand, open: open .concat ({quantity, date: saleDate, id}), completed},
{onHand: onHand + (type == 'purchase' ? quantity : 0) , open: [], completed}
), initial)
const allocate = process ({onHand: 0, open: [], completed: []})
const salesOrders = [{id: "S1", created: "2020-01-02", quantity: 6}, {id: "S2", created: "2020-11-05", quantity: 2}, {id: "S3", created: "2019-12-04", quantity: 3}, {id: "S4", created: "2020-01-20", quantity: 2}, {id: "S5", created: "2019-12-15", quantity: 9}]
const purchaseOrders = [{id: "P1", receiving: "2020-01-04", quantity: 4}, {id: "P2", receiving: "2020-01-05", quantity: 3}, {id: "P3", receiving: "2020-02-01", quantity: 5}, {id: "P4", receiving: "2020-03-05", quantity: 1}, {id: "P5", receiving: "2020-02-20", quantity: 7}]
console .log (allocate (salesOrders, purchaseOrders))
.as-console-wrapper {max-height: 100% !important; top: 0}
This structure we folded to serves as our overall output, something like this:
{
onHand: 0,
completed: [
{date: "2020-01-04", id: "S3", quantity: 3},
{date: "2020-01-20", id: "S4", quantity: 2},
{date: "2020-02-01", id: "S1", quantity: 6},
{date: "2020-03-05", id: "S5", quantity: 9}
],
open: [
{date: "2020-11-05", id: "S2", quantity: 2}
]
}
But that suggests an improvement. We could make this function reentrant. We could save the output, then next time we need to add events, we could simply pass that back through the function with new sales and purchases. This would give you an updated list. It seems like a handy feature, and it's not any more difficult to add. The only real API change is that you now pass the current value in every call, with some default values for the initial call. Here is one version:
const process = (initial, sales, purchases) => [
...sales.map (({created, ...rest}) => ({type: 'sale', date: created, ...rest})),
...purchases.map (({receiving, ...rest}) => ({type: 'purchase', date: receiving, ...rest})),
] .sort (({date: d1}, {date: d2}) => d1 < d2 ? -1 : d1 > d2 ? 1 : 0) .reduce ((
{onHand, open: o, completed},
{type, date, quantity, id, open = [...o, ... (type == 'sale' ? [{quantity, date, id}] : [])]}
) => open .reduce (
({onHand, open, completed}, {quantity, date: saleDate, id}) => quantity <= onHand
? {onHand: onHand - quantity, open, completed: completed .concat ({date, id, quantity})}
: {onHand, open: open .concat ({date: saleDate, id, quantity}), completed},
{onHand: onHand + (type == 'purchase' ? quantity : 0) , open: [], completed}
), initial)
const salesOrders = [{id: "S1", created: "2020-01-02", quantity: 6}, {id: "S2", created: "2020-11-05", quantity: 2}, {id: "S3", created: "2019-12-04", quantity: 3}, {id: "S4", created: "2020-01-20", quantity: 2}, {id: "S5", created: "2019-12-15", quantity: 9}]
const purchaseOrders = [{id: "P1", receiving: "2020-01-04", quantity: 4}, {id: "P2", receiving: "2020-01-05", quantity: 3}, {id: "P3", receiving: "2020-02-01", quantity: 5}, {id: "P4", receiving: "2020-03-05", quantity: 1}, {id: "P5", receiving: "2020-02-20", quantity: 7}]
const initialValues = {onHand: 0, open: [], completed: []}
const currentState = process (initialValues, salesOrders, purchaseOrders)
console .log ('initial load: ', currentState)
const additionalSalesOrders = [{id: "S6", created: "2021-03-07", quantity: 3}, {id: "S7", created: "2021-04-21", quantity: 10}, {id: "S3", created: "2021-06-14", quantity: 5}]
const additionalPurchaseOrders = [{id: "P6", receiving: "2021-05-20", quantity: 8}]
const nextState = process (currentState, additionalSalesOrders, additionalPurchaseOrders)
console .log ('after new events: ', nextState)
.as-console-wrapper {max-height: 100% !important; top: 0}
Upvotes: 0
Reputation: 627
I think your approach is mostly ok. I would just have some remarks/questions:
Here is how I would tackle the problem:
console.log(allocate(salesOrders, purchaseOrders));
function allocate(_sales, _purchases) {
const sales = structureData(_sales, "created");
const purchases = structureData(_purchases, "receiving");
const LAST_PURCHASE_INDEX = purchases.length - 1;
let stock = 0; // in real life, maybe this should be an input as well since it might not always start from 0?
let availabilityDate = sales[0].date; // timestamp of stock availability, initialized to first sale timestamp
let availabilityDateString = sales[0].created; // date in string format of stock availability, initialized to first sale created date
let purchaseIndex = 0; // index of the next purchase to process
const result = [];
// loop on sales
for (let sale of sales) {
const requiredQuantity = sale.quantity;
const saleId = sale.id;
// As long as we don't have enough stock, process the next purchase if there is any
while (stock < requiredQuantity && purchaseIndex <= LAST_PURCHASE_INDEX) {
const purchase = purchases[purchaseIndex];
stock += purchase.quantity;
availabilityDate = purchase.date;
availabilityDateString = purchase.receiving;
purchaseIndex++;
}
if (stock >= requiredQuantity) { // we have enough stock and push the availability date
result.push({
id: saleId,
availabilityDate:
availabilityDate > sale.date ? availabilityDateString : sale.created, // It could be simplified to availabilityDate if it's ok to have an availabilityDate before the sales creation
});
stock -= sale.quantity;
} else { // we don't have enough stock and there are no purchases left, so we need more purchases
result.push({ id: saleId , availabilityDateString: "Not anytime soon, need more purchases"});
}
}
return result;
}
// utils to sort orders and add a date timesteamp for easier date comparison
function structureData(orders, dateField) {
return orders
.map((order) => ({ ...order, date: new Date(order[dateField]).getTime() }))
.sort((o1, o2) => o1.date - o2.date);
}
Upvotes: 1