Design Overview:
I've an application with Invoice creation and Inventory management features in it. Let's first understand the database design with 2 entities that we have as below:
Now, here I've a M:N relationship between these 2 entities because one invoice can contain multiple items and one item can be included in many such invoices.
So, I've created a 3rd table which we call joining table to associate these entities as shown in the image below,
Problem Statemet:
I'm unable to insert model in the child table(invoice_items) using include attribute. Look at the code below to understand what's wrong happening here?
3 Model Classes as below:
1. Invoice:
Note:
Providing with fewer attributes to keep it short.
module.exports = (sequelize, DataTypes) => { const Invoice = sequelize.define('Invoice', { invoiceId: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, autoIncrement: true, primaryKey: true }, invoiceNumber: { type: DataTypes.INTEGER(6).UNSIGNED.ZEROFILL, allowNull: false, unique: true }, invoiceTotal: { type: DataTypes.DECIMAL(9,2), allowNull: false, defaultValue: 0.00 }, paymentTotal: { type: DataTypes.DECIMAL(9,2), allowNull: false, defaultValue: 0.00 }, invoiceDate: { type: DataTypes.DATEONLY, defaultValue: DataTypes.NOW, allowNull: false } }, { underscored: true }); Invoice.associate = function (model) { Invoice.belongsTo(model.Customer, { as: 'customer', foreignKey: { name: "cust_id", allowNull: false } }); // association with 3rd table Invoice.hasMany(model.InvoiceItem, { as: 'invoice_item', constraints: true, onDelete: 'NO ACTION', foreignKey: { name: "invoice_id", allowNull: false } }); }; return Invoice; }
2. Item:
Note:
Providing with fewer attributes to keep it short.
module.exports = (sequelize, DataTypes) => { const Item = sequelize.define('Item', { itemId: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, autoIncrement: true, primaryKey: true }, itemName: { type: DataTypes.TEXT, allowNull: false, defaultValue: '' }, // this is a opening stock quantityInStock: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, defaultValue: 0, }, unitPrice: { type: DataTypes.DECIMAL(9,2), allowNull: false, defaultValue: 0.00 } }, { underscored: true }); Item.associate = function (model) { // association with 3rd table Item.hasMany(model.InvoiceItem, { as: 'invoice_item', // alias name of a model constraints: true, onDelete: 'NO ACTION', foreignKey: { name: "item_id", allowNull: false } }); }; return Item; }
3. Invoice_Item:
Note:
Providing with fewer attributes to keep it short.
module.exports = (sequelize, DataTypes) => { const InvoiceItem = sequelize.define('InvoiceItem', { invoiceItemId: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, autoIncrement: true, primaryKey: true }, quantity: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, defaultValue: 0, }, rate: { type: DataTypes.DECIMAL(9,2), allowNull: false, defaultValue: 0.00 } }, { underscored: true }); InvoiceItem.associate = function(model) { InvoiceItem.belongsTo(model.Invoice, { as: 'invoice', foreignKey: { name: "invoice_id", allowNull: false } }); InvoiceItem.belongsTo(model.Item, { as: 'item', foreignKey: { name: "item_id", allowNull: false } }); } return InvoiceItem; }
Now, I'm using below code to create an invoice with the list of items in it. But, this is not inserting the child records in the joining table( invoice_items
). What's wrong here in the code below?
invoice = await Invoice.create({ "invoiceNumber": req.body.invoiceNumber, "invoiceDate": req.body.invoiceDate, "invoiceTotal": req.body.invoiceTotal, "paymentTotal": req.body.paymentTotal, "cust_id": req.body.customer.custId, invoice_items: [{ item_id: 1, quantity: 2, rate: 300 }] }, { include: [{ association: InvoiceItem, as: 'invoice_item' }] });
After trying so many variations, I understand that there was a problem in the association of my model classes. And, below is the way of associating both Invoice
and Item
model classes for M:N(Many to Many) relationships. I can now update the join table(invoice_items) by inserting the record in it for each invoice we create in the system with the items in it.
Invoice.associate = function (model) { // association in Invoice model class Invoice.belongsToMany(model.Item, { through: 'InvoiceItem', constraints: true, onDelete: 'NO ACTION', foreignKey: { name: "invoice_id", // foreign key column name in a table invoice_items table allowNull: false } }); };
Item.associate = function (model) { // association in Item model class Item.belongsToMany(model.Invoice, { through: 'InvoiceItem', constraints: true, onDelete: 'NO ACTION', foreignKey: { name: "item_id", // foreign key column name in a table invoice_items allowNull: false } }); };
Create Invoice with Items in it:
Note:
Passing itemId (1) as a parameter in the addItems() method. If you have multiple items in an invoice then you can add forEach
loop here to iterate over each item and individually pass the itemId
, quantity
and rate
for an item sold to the customer.
// first create the invoice invoice = await Invoice.create(invoice); // Next, add record in the join table await invoice.addItems([1], { through: { quantity: item.quantity, rate: item.rate } });
Database Tables with one Test Result:
1. Invoice Table:
2. Invoice_items Table(Join Table):
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.