QR-Code Invoice
QR-Code Invoice Implementation Guide
Overview
This document outlines the functional and technical flow for implementing QR-based import in the Invoice module. The feature is controlled by the system parameter EnableQR. When this parameter is ON, additional QR-specific logic will be executed during Invoice line import operations.
Changes in Invoice1Index
- Need to Hide Create Line and Create From Order button if Enable QR is Y
- Need to add Import Button for Importing Order
Invoice1Import New Page
- It will have Invoice No, Invoice Date , Party Name as readonly
- It will have Import, Download Model and Pending Order Report Button
- Use of every feature is as follow
Import - For Importing the Model
Download Model - It will download the model with 3 columns IssueQty, Orders1id, QrDescription
Pending Order Report - It will ask user for Order No, Po No, YearLabel and then download a Pending Order report with Po No and Order No at top and ItemId, Orders1Id, Qty columns in report (column name need to be confirmed)
Pending Order Report
Functionality:
- User fills Order No / PO No in the input field
- Upon clicking “Pending Order Report” button, generates Excel report with:
- PO No and/or Order No displayed at the top of the report
- Report body with columns: Orders1Id,OrderNo,OrderDate, Qty
- Data filtered based on provided Order No / PO No parameter
- We will use Invoice1CreateIndex
Flow of Import
- User Creates main Level for Invoice
- Data Preparation: User fills Order1Id From the pending order report and then they will scan the QR and add in QR descriptions and if they want to issue fullqty then they will keep the issue qty as blanck if partial qty needs to be used then tey will fill IssueQty cant be 0 and there is no provision of Deletions marked with ‘D’ in IssueQty column
- They have to upload the scanned Qr Description and if they want partial they can enter in Issue Qty, if they want full they can leave it blank
- They can upload J-Trnid, G-Trnid, S-Trnid, M-Trnid
- They can Upload for multiple Orders also and single orders also
- Grouping Logic: Records inserted based on ItemId and Trnid grouping (GROUP BY ItemId). If there is 2 trnid like M-000001 and S-000001 but both is of ItemId 000001 then we will have 1 row only by groupping by itemid and sumofqty will be qty
- ItemId and Order No combination must exist in System
- Print Label feature will not be there in Invoice
- Other columns of Invoice we can take from the Orders1Id
- Need to handle all the table changes we have done in Invoice1Create
- Sub-Transaction Logic: Scanned sub-trn logic based on doctype and Doc Id. IN ScannedSubTrn we will add 2 new column doctype and DocId and no chnages is needed in the Invocie2table and we will insert the created InvoiceId and doctype as I in ScannedSubTrn like M-000001-001 and S-000001-001 has same ItemId 000001 so one line in invoice2 will be created so we will take that id and we will insert 2 line in ScannedSubTrn and for both docid will be invoice2table id and if J-000001 is one more line in that case same id of the 2nd lineof invoice2 will be inserted in with doctype as I
- Rollback Handling : for eg the the id 1034 of invoice2 table which is invoiceline is delted then we will update the both subtrn balace M-000001-001 and S-000001-001 with whatever ussed in that invocie
- Button condition : Invoice is not cancelled, Invoice is not E-Invoice, QR is Enable
Validations
Z-Series Validations (Controller/Excel Validations)
| Error Code | Description |
|---|---|
| Z1 | Issue Qty must be a numeric value. If you want to issue the full quantity, leave it blank. Enter D if you do not want to issue that Trn Id. |
| Z2 | Orders1id must be Number |
| Z3 | QR Description can be upto 500 characters only |
Note: File format validation (column count != 4 → “Please upload correct model”) and empty file validation (rowcount == 1 → “Uploaded XL File does not contain any data”) are handled in the UI like JobWorkController.cs, not as error codes.
A-Series Validations (Repository Validations)
| Error Code | Description |
|---|---|
| A | Only GRN, JobWork, Material, and Stock transactions can be imported |
| B | Invalid Trn Id or Sub Trn Id |
| C | Entered Qty exceeds available balance |
| D | Issue Qty must be between 0.001 and 999999.999 |
| E | Multiple Sales Orders in a Single Invoice is disabled for this Party |
| F | GST tax code for all lines must be same |
| G | Invoiced Qty can not exceed pending Order qty |
| H | Currency as per Sales order and currency as per Invoice is mismatched. |
| I | Invalid Order No or does not belong to this party/branch/unit |
| J | Invalid Orders Id |
| K | Order date should be less than or equal to Invoice date. |
| L | Order type must be Direct / Confirmed Order. |
| M | Status of all lines in this Order must be OK. |
| N | One or more items in this order are not yet released for sale. |
| O | Payment Terms must be same for all the lines |
| P | Qty can not be more than {excessOrderQty} i.e. Pending Order Qty plus 10% |
| Q | QR Item and Order Item are mismatching |
| R | Item belongs to Freight Item |
| S | Trn Id belongs to rejected item |
Invoice1Import Method Implementation
public async Task<Tuple<List<InvoiceLine>, string>> Invoice1Import(List<InvoiceLine> listdata, string dbname, string userName, string yearLabel, int mainId)
{
var tempcreation = @"CREATE TABLE #invoice
(
id INT PRIMARY KEY IDENTITY(1,1),
QrDescription NVARCHAR(500) COLLATE DATABASE_DEFAULT,
qty NUMERIC(10,3),
orders1id INT,
trnid NVARCHAR(7) COLLATE DATABASE_DEFAULT,
subtrnid NVARCHAR(3) COLLATE DATABASE_DEFAULT,
itemid NVARCHAR(6) COLLATE DATABASE_DEFAULT,
errorcode NVARCHAR(20) COLLATE DATABASE_DEFAULT
)";
var inserttemp = @"INSERT INTO #invoice(QrDescription, qty, orders1id, trnid, subtrnid)
VALUES(@QrDescription, @qty, @orders1id, @trnid, @subtrnid)";
var updateQty = @"UPDATE #invoice SET qty = Q.balanceqty
FROM QrSubTrn Q
WHERE #invoice.trnid = Q.trnid AND #invoice.subtrnid = Q.subtrnid AND COALESCE(#invoice.qty, 0) = 0";
var updateItemId = @"WITH A(trnid, itemid) AS
(
SELECT I2.TrnId, I2.ItemId
FROM Invoice2 I2
LEFT JOIN #invoice ON I2.TrnId = #invoice.TrnId
UNION ALL
SELECT G1.TrnId, G1.ItemId
FROM Grn1 G1
LEFT JOIN #invoice ON G1.TrnId = #invoice.TrnId
UNION ALL
SELECT Q.TrnId, Q.ItemId
FROM QRStock Q
LEFT JOIN #invoice ON Q.TrnId = #invoice.TrnId
UNION ALL
SELECT J1.TrnId, J1.ItemId
FROM JobWork1 J1
LEFT JOIN #invoice ON J1.TrnId = #invoice.TrnId
UNION ALL
SELECT M1.TrnId, M1.ItemId
FROM Mirs1 M1
LEFT JOIN #invoice ON M1.TrnId = #invoice.TrnId
)
UPDATE #invoice SET itemid = A.itemid
FROM A
WHERE #invoice.trnid = A.trnid";
// Error Code A: Only GRN, JobWork, Material, and Stock transactions
var errorcodeA = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode),'')+ ' A'
WHERE LEFT(trnid, 1) NOT IN ('G','J','M','S')";
// Error Code B: Invalid Trn Id or Sub Trn Id
var errorcodeB = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' B'
WHERE NOT EXISTS (SELECT trnid + subtrnid FROM QrSubTrn WHERE #invoice.trnid + #invoice.subtrnid = QrSubTrn.trnid + QrSubTrn.subtrnid)";
// Error Code C: Entered Qty exceeds available balance
var errorcodeC = @"WITH A(trnid, subtrnid, balanceqty) AS
(
SELECT Q.trnid, Q.subtrnid, Q.balanceqty FROM #invoice I
LEFT JOIN QrSubTrn Q ON I.trnid = Q.trnid AND I.subtrnid = Q.subtrnid
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' C'
FROM A
WHERE #invoice.trnid = A.trnid AND #invoice.subtrnid = A.subtrnid AND #invoice.qty > A.balanceqty";
// Error Code D: Issue Qty must be between 0.001 and 999999.999
var errorcodeD = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' D'
FROM #invoice
WHERE (qty NOT BETWEEN 0.001 AND 999999.999)";
// Error Code E: Multiple Sales Orders in a Single Invoice is disabled for this Party
var errorcodeE = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' E'
WHERE COALESCE(multipleorders, 'N') = 'Y' AND partyid = @partyid ";
// Error Code F: GST tax code for all lines must be same
var errorcodeF = @"WITH A(gstcode) AS
(
SELECT O1.gstcode
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
),
B(gstcode, gstcount) AS
(
SELECT gstcode, COUNT(*) FROM A GROUP BY gstcode
),
C(existinggstcode) AS
(
SELECT TOP 1 gstcode FROM Invoice2 WHERE yearinvoiceno = @yearinvoiceno
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' F'
WHERE (SELECT COUNT(*) FROM B) > 1 OR EXISTS
(SELECT gstcode FROM B WHERE gstcode != (SELECT existinggstcode FROM C))";
// Error Code G: Invoiced Qty can not exceed pending Order qty
var errorcodeG = @"WITH A(orders1id, pendingqty) AS
(
SELECT O1.id, O1.qty
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' G'
FROM A
WHERE #invoice.orders1id = A.orders1id AND #invoice.qty > A.pendingqty";
// Error Code H: Currency as per Sales order and currency as per Invoice is mismatched
var errorcodeH = @"WITH A(orders1id, ordercurrency) AS
(
SELECT orders1id, O.currency
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' H'
FROM A
WHERE #invoice.orders1id = A.orders1id AND A.ordercurrency != @currency";
// Error Code I: Invalid Order No or does not belong to this party/branch/unit
// We don't need to cheeck the party/branch/unit mismatch from trnid's as invoice and order should match ,doesnt matter the trnid belogs to which party/branch/unit as we can procure the items as GRN/Jobwork/Material/Stock from any party. So only checking the orders1id's party/branch/unit with invoice party/branch/unit
var errorcodeI = @"WITH A(orders1id, partybranchid, unitcode) AS
(
SELECT I.orders1id, O.partyid + O.branchid, O.unitcode
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
),
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' I'
FROM A
WHERE #invoice.orders1id = A.orders1id AND (A.partybranchid != @partybranchid OR A.unitcode != @unitcode)";
// Error Code J: Invalid Orders Id
var errorcodeJ = @"UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' J'
WHERE NOT EXISTS (SELECT id FROM Orders1 WHERE #invoice.orders1id = Orders1.id)";
// Error Code K: Order date should be less than or equal to Invoice date
var errorcodeK = @"WITH A(orders1id, orderdate) AS
(
SELECT Orders1.id, O.dated
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' K'
FROM A
WHERE #invoice.orders1id = A.orders1id AND A.orderdate > @invoicedate";
// Error Code L: Order type must be Direct / Confirmed Order
var errorcodeL = @"WITH A(orders1id, isconfirmed) AS
(
SELECT O1.id, O.IsConfirmedOrder
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
WHERE isconfirmed = 'N'
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' L'
FROM A
WHERE #invoice.orders1id = A.orders1id";
// Error Code M: Status of all lines in this Order must be OK
var errorcodeM = @"WITH A(orders1id, orderstatus) AS
(
SELECT O1.id, O1.orderstatus
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
WHERE orderstatus != '00'
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' M'
FROM A
WHERE #invoice.orders1id = A.orders1id";
// Error Code N: One or more items in this order are not yet released for sale
var errorcodeN = @"WITH A(orders1id, releasestatus) AS
(
SELECT Orders1id, IM.ReleaseStatus
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN InventoryMst IM ON O1.itemid = IM.itemid
WHERE releasestatus = 'N'
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' N'
FROM A
WHERE #invoice.orders1id = A.orders1id ";
// Error Code O: Payment Terms must be same for all the lines
var errorcodeO = @"WITH A(orders1id, paymentterm) AS
(
SELECT Orders1id, O.otermcode
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN Order O ON O1.yearorderno = O.yearorderno
),
B(paymentterm, termcount) AS
(
SELECT paymentterm, COUNT(*) FROM A GROUP BY paymentterm
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(errorcode), '') + ' O'
WHERE (SELECT COUNT(*) FROM B) > 1";
// Error Code P: Qty can not be more than {excessOrderQty} i.e. Pending Order Qty plus 10%
var errorcodeP = @"WITH A(orders1id, pendingqty) AS
(
SELECT Orders1id, O1.qty
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' P'
FROM A
WHERE #invoice.orders1id = A.orders1id AND #invoice.qty > (A.pendingqty * 1.1)";
// Error Code Q: QR Item and Order Item are mismatching
var errorcodeQ = @"WITH A(orders1id, orderitemid) AS
(
SELECT Orders1id, O1.itemid
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
)
UPDATE #invoice SET errorcode = COALESCE(RTRIM(ERRORCODE), '') + ' Q'
FROM A
WHERE #invoice.orders1id = A.orders1id AND #invoice.itemid != A.orderitemid";
// Error Code R: Item belongs to Freight Item
var errorcodeR = @"
WITH A (orders1id) AS
(
SELECT DISTINCT O1.id
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
LEFT JOIN InventoryMst IM ON O1.itemid = IM.itemid
WHERE IM.classid = 'FP'
)
UPDATE #invoice
SET errorcode = COALESCE(RTRIM(errorcode), '') + ' R'
FROM A
WHERE #invoice.orders1id = A.orders1id;";
// Error Code S: Trn Id belongs to rejected item
var errorcodeS = @"
WITH A (trnid) AS
(
SELECT DISTINCT I.trnid
FROM #invoice I
INNER JOIN QRStock Q ON Q.trnid = I.trnid
WHERE LEFT(I.trnid, 1) = 'S'
AND Q.itemstate = 'R'
)
UPDATE #invoice
SET errorcode = COALESCE(RTRIM(errorcode), '') + ' S'
FROM A
WHERE #invoice.trnid = A.trnid;";
var cnterror = @"SELECT COUNT(Errorcode) AS CNT FROM #invoice
WHERE COALESCE(ERRORCODE,'') != ''";
var data = @"SELECT I.QrDescription, I.qty, I.orders1id,
COALESCE(I.errorcode,'') AS errorcode
FROM #invoice I
ORDER BY I.id ASC";
var invoiceinsert = @"WITH A (itemid, totalqty, orders1id, rate, gstcode, stage, storecode, brandid) AS
(
SELECT O1.itemid, SUM(I.qty) AS totalqty, I.orders1id,
CAST(ROUND(O1.rate * @exchangeRate,3) AS NUMERIC (12, 4)) AS Rate, O1.gstcode, O1.stage, O1.storecode, O1.brandid
FROM #invoice I
LEFT JOIN Orders1 O1 ON I.orders1id = O1.id
GROUP BY O1.itemid, I.orders1id, O1.rate, O1.gstcode, O1.stage, O1.storecode, O1.brandid
ORDER BY O1.itemid, O1.storecode, O1.brandid
),
B(itemid, totalqty, orders1id, rate, gstcode, stage, storecode, brandid, value) AS
(
SELECT itemid, totalqty, orders1id, rate, gstcode, stage, storecode, brandid, CAST(ROUND(rate * totalqty,2) AS NUMERIC (11,2)) AS value
FROM A
)
INSERT INTO Invoice2
(
invoiceno, itemid, qty, orders1id, rate,
value, yearinvoiceno, Itemstate, stage, storecode, gstcode, packingslip,noofpacket
)
SELECT @invoiceno, B.itemid, B.totalqty, B.orders1id, B.rate, B.value, @yearinvoiceno, 'G' AS Itemstate,
B.stage, B.storecode, B.gstcode, '', 0
FROM B;";
// Order quantity management after invoice insertion
var updateOrderQtyValue = @"WITH A(orders1id, totalinvoicedqty) AS
(
SELECT orders1id, SUM(qty) AS totalinvoicedqty
FROM #invoice
GROUP BY orders1id
),
B (orders1id,newqty,newvalue) AS
(
SELECT
O1.id AS orders1id,
IIF(O1.qty > A.totalinvoicedqty, O1.qty - A.totalinvoicedqty, 0) AS newqty,
IIF(O1.qty > A.totalinvoicedqty, O1.value - (A.totalinvoicedqty * O1.rate), 0) AS newvalue
FROM A
INNER JOIN orders1 O1 ON A.orders1id = O1.id
)
UPDATE O1
SET qty = B.newqty,
value = B.newvalue
FROM orders1 O1
LEFT JOIN B ON O1.id = B.orders1id";
var createNewOrders1 = @"WITH A(orders1id, totalinvoicedqty) AS
(
SELECT orders1id, SUM(qty) as totalinvoicedqty
FROM #invoice
GROUP BY orders1id
),
B(orders1id, remainingqty, remainingvalue) AS
(
SELECT O1.id, O1.qty - A.totalinvoicedqty AS remainingqty,
CAST(O1.value - ROUND((A.totalinvoicedqty * O1.rate),2) AS NUMERIC(11,2)) AS remainingvalue
FROM Orders1 O1
LEFT JOIN A ON O1.id = A.orders1id
WHERE O1.qty > A.totalinvoicedqty
),
C(orderno, itemid, qty,
rate, value, reqdby, planneddate, isnonstd, nonstdnote, orderstatus, effectivedate, notes,
yearorderno, brandid, MRP, leadtime, gstcode, UserQty, bookingno) AS
(
SELECT O1.orderno, O1.itemid, B.remainingqty, O1.rate, B.remainingvalue, O1.reqdby,
O1.planneddate, O1.isnonstd, O1.nonstdnote, O1.orderstatus, O1.effectivedate, O1.notes,
O1.yearorderno, O1.brandid, O1.MRP, O1.leadtime, O1.gstcode, O1.UserQty, O1.bookingno
FROM Orders1 O1
LEFT JOIN B ON O1.id = B.orders1id
)
INSERT INTO Orders1 (orderno, itemid, qty,
rate, value, reqdby, planneddate, isnonstd, nonstdnote, orderstatus, effectivedate, notes,
yearorderno, brandid, MRP, leadtime, gstcode, UserQty, bookingno)
SELECT orderno, itemid, qty,
rate, value, reqdby, planneddate, isnonstd, nonstdnote, orderstatus, effectivedate, notes,
yearorderno, brandid, MRP, leadtime, gstcode, UserQty, bookingno
FROM C";
// Needs to be discussed UD Point
var updateAlOrdersLink = @"WITH A(orders1id, totalinvoicedqty) AS
(
SELECT orders1id, SUM(qty) as totalinvoicedqty
FROM #invoice
GROUP BY orders1id
)
UPDATE ALOrdersLink
SET qty = A.totalinvoicedqty
FROM ALOrdersLink AL
INNER JOIN A ON AL.orders1id = A.orders1id";
// Needs to be discussed UD Point
var insertNewAlOrdersLink = @"WITH A(orders1id, totalinvoicedqty) AS
(
SELECT orders1id, SUM(qty) as totalinvoicedqty
FROM #invoice
GROUP BY orders1id
),
B(oldorders1id, new_orders1id) AS
(
SELECT O1.id as oldorders1id, NO1.id as new_orders1id
FROM Orders1 O1
INNER JOIN Orders1 NO1 ON O1.orderno = NO1.orderno AND O1.itemid = NO1.itemid
AND O1.yearorderno = NO1.yearorderno
INNER JOIN A ON O1.id = A.orders1id
WHERE O1.qty = 0 AND NO1.qty > 0
)
INSERT INTO ALOrdersLink (al1id, orders1id, qty)
SELECT AL.al1id, B.new_orders1id, AL.qty - A.totalinvoicedqty
FROM ALOrdersLink AL
INNER JOIN A ON AL.orders1id = A.orders1id
INNER JOIN B ON AL.orders1id = B.oldorders1id";
var scannedsubtrninsert = @"WITH A(trnid, subtrnid, qty, yeardocno, isrolledback, docid, doctype) AS
(
SELECT I.trnid, I.subtrnid, I.qty, @yearinvoiceno AS yeardocno, 'N' AS isrolledback,
I2.id AS docid, 'I' AS doctype
FROM #invoice I
LEFT JOIN Invoice2 I2 ON I.orders1id = I2.orders1id
)
INSERT INTO ScannedSubTrn (trnid, subtrnid, qty, yeardocno, isrolledback, docid, doctype)
SELECT trnid, subtrnid, qty, yeardocno, isrolledback, docid, doctype FROM A";
var updateQrSubTrnQty = @"WITH A(TrnId,SubTrnId,qty) AS
(SELECT TrnId,SubTrnId,qty FROM #invoice)
UPDATE QrSubTrn SET BalanceQty = BalanceQty - A.qty FROM A
WHERE QrSubTrn.TrnId = A.TrnId AND QrSubTrn.SubTrnId = A.SubTrnId";
var expInvoiceCntQuery = @"SELECT COUNT(*) AS Cnt1 FROM InvoiceE1 WHERE Yearinvoiceno=@yearinvoiceno";
// need to discuss
var insertInvoiceE2 = @"INSERT INTO InvoiceE2 (yearinvoiceno,invoice2id,Palletno,Series,Qtyperbox,Boxes,Netweight,Grossweight)
VALUES(@yearinvoiceno, @invoice2id, @Palletno, @Palletno, @Series, @Series, @Series, @Series)";
// need to discuss
var updateDueDate = @"UPDATE Invoice SET Duedate=@duedate
WHERE Yearinvoiceno=@yearinvoiceno";
var updateEInSupType = @"UPDATE Invoice1 SET EIn_SupType=@eInSupType
WHERE Yearinvoiceno=@yearinvoiceno";
var lognotes = @"WITH A (trnid) AS
(
SELECT DISTINCT trnid FROM #invoice
)
SELECT STRING_AGG(trnid,' ') AS notes FROM A";
using var connection = _DapperContext.SetClientConnection(dbname);
connection.Open();
var invoiceDetails = GetInvoiceInfo(mainId, dbname);
connection.Execute(tempcreation);
connection.Execute(inserttemp,
listdata.Select(dataLine => new
{
dataLine.QrDescription,
SubTrnId = dataLine.QrDescription.Trim().Substring(8, 3),
TrnId = dataLine.QrDescription.Trim()[..7],
qty = dataLine.Qty,
orders1id = dataLine.Orders1Id
})
);
connection.Execute(updateQty);
connection.Execute(updateItemId);
connection.Execute(errorcodeA);
connection.Execute(errorcodeB);
connection.Execute(errorcodeC);
connection.Execute(errorcodeD);
connection.Execute(errorcodeE, new { partyid = invoiceDetails.PartyId });
connection.Execute(errorcodeF, new{yearinvoiceno = invoiceDetails.YearInvoiceNo});
connection.Execute(errorcodeG);
connection.Execute(errorcodeH, new { currency = invoiceDetails.Currency });
connection.Execute(errorcodeI, new { partybranchid = invoiceDetails.PartyId + invoiceDetails.BranchId, unitcode = invoiceDetails.UnitCode });
connection.Execute(errorcodeJ);
connection.Execute(errorcodeK, new { invoicedate = invoiceDetails.Dated });
connection.Execute(errorcodeL);
connection.Execute(errorcodeM);
connection.Execute(errorcodeN);
connection.Execute(errorcodeO);
connection.Execute(errorcodeP);
connection.Execute(errorcodeQ);
var errorcount = connection.QuerySingleOrDefault<int>(cnterror);
string? msg;
msg = "Import could not succeed because of errors in input";
if (errorcount == 0)
{
connection.Execute(invoiceinsert,
new
{
yearinvoiceno = invoiceDetails.YearInvoiceNo,
invoiceno = invoiceDetails.InvoiceNo
});
connection.Execute(updateOrderQtyValue);
connection.Execute(createNewOrders1);
connection.Execute(updateAlOrdersLink);
connection.Execute(insertNewAlOrdersLink);
connection.Execute(scannedsubtrninsert, new { yearinvoiceno = invoiceDetails.YearInvoiceNo });
connection.Execute(updateQrSubTrnQty);
connection.Execute(updateDueDate,new{duedate = DateTime.Now.Date, yearinvoiceno = invoiceDetails.YearInvoiceNo});
var expcnt = connection.QuerySingleOrDefault<int>(expInvoiceCntQuery, new { yearinvoiceno = invoiceDetails.YearInvoiceNo });
if (expcnt == 0 )
{
connection.Execute(insertInvoiceE2, new { yearinvoiceno = invoiceDetails.YearInvoiceNo, Palletno = "0", Series = 0, Qtyperbox = 0, Boxes = 0, Netweight = 0, Grossweight = 0 });
}
var firstLineInfo = Get1stLineInvoiceInfo(invoiceDetails.YearInvoiceNo, dbname);
var gstRate = GetGstRate(firstLineInfo.GstRateCode, dbname);
var eInSupType = gstRate > 0 ? "WP" : "WOP";
connection.Execute(updateEInSupType, new { eInSupType, yearinvoiceno = invoiceDetails.YearInvoiceNo });
ComputeInvoiceValues(mainId, dbname);
var notes = connection.QuerySingleOrDefault<string>(lognotes);
_IUtilityMethodsRepository.InsertMFGLog(dbname, userName, "000000", "Invoice", "Import", yearLabel, notes);
msg = "Invoice import succeeded.";
}
var finaldata = connection.Query<InvoiceLine>(data);
var data1 = finaldata.ToList();
return new Tuple<List<InvoiceLine>, string>(data1, msg);
}Database Schema Considerations
New Tables Required
- ScannedSubTrn: For rollback logic tracking scanned sub-transactions (add docid INT and doctype NVARCHAR(2))
API Endpoints to Add
Invoice Controller Extensions
POST /api/Invoice/Invoice1Import: Process uploaded QR import fileGET /api/Invoice/DownloadModel: Generate and return Excel templatePOST /api/Invoice/PendingOrderReport: Create and return pending order Excel reportGET /api/Invoice/Invoice1ValidateQrImport: Pre-validate import data before processing
Repository Methods Required
Invoice1Import(): Main import processing method with validation logicPendingOrderReport(): Build pending order reportInvoice1QrRollback(): Rollback method for delete operations
Processing Logic
Import Execution Steps
- File Validation: Check format, columns, and data types (handled like JobWorkController.cs - columncount != 4 → “Please upload correct model”, rowcount == 1 → “Uploaded XL File does not contain any data”)
- Data Parsing: Extract TrnId, SubTrnId from QR descriptions, Orders1Id comes as separate column data from the import
- Order Validation: Verify Orders1Id exists and belongs to correct order
- Quantity Resolution: Use full quantity if IssueQty blank, validate partial quantities, handle ‘D’ for skipping transactions
- Grouping: Group by ItemId, sum quantities for same ItemId across different TrnIds
- Business Rules: Apply all A-series validations
- Data Insertion: Create Invoice2 records with grouped data
- Sub-Transaction Tracking: Insert ScannedSubTrn records with docid (invoice2 id) and doctype (‘I’)
- Balance Updates: Update QR sub-transaction balances
- Audit Logging: Log import operation with transaction details
Rollback Handling
- Track imported transactions in ScannedSubTrn table with docid and doctype
- When invoice2 line is deleted, restore QR balances for all related sub-transactions
- Mark rollback status to prevent duplicate processing
UI/UX Considerations
User Workflow
- Download template then Download pending order report
- Fill Orders1Id From pending order report to Download template
- Leave IssueQty blank for full quantity or enter partial quantity or enter D if
Error Handling
- Clear error codes and descriptions
- Allow re-upload after fixing issues
- Provide detailed validation feedback
- Support partial success scenarios
This documentation prioritizes the Invoice QR approach as specified in InvoiceQr.md, highlighting the key differences from Jobwork QR implementation.
Methods / File Name
- Invoice1Import
- RollBack
- PendingOrderReport
- Invoice1ImportDownloadModel
// New property in InvoiceLine
public string QrDescription { get; set; } = string.Empty;
public string XlFileName { get; set; } = string.Empty;
public string ErrorCode { get; set; } = string.Empty;
public int Orders1Id { get; set; }