OBPIH-6044 Export invoice lines details on invoices list page#4484
Conversation
b927232 to
1a1c187
Compare
| controller = "invoiceApi" | ||
| action = [GET: "getAllInvoiceItems"] | ||
| } | ||
|
|
There was a problem hiding this comment.
I don't mind it, but I think we used to do it via the export mapping, with a proper param (e.g. "orderItems").
There was a problem hiding this comment.
yes this is probably the way to go, this should be on /api/invoiceItems/
| def getAllInvoiceItems() { | ||
| Location location = Location.get(session.warehouse.id) | ||
| params.partyFromId = location?.organization?.id | ||
| List<InvoiceList> invoices = invoiceService.getInvoices(params) |
There was a problem hiding this comment.
I spoke about that with @drodzewicz, but I want to make my point here, so that everyone can give their opinion - in my opinion it should be dealt directly via InvoiceItem.createCriteria() and
invoice {
'in'("id" ,params.invoicesIds)
}clause.
Plus I would monitor the performance here, because we might run into many N+1 - if we do, we could play with the data services and joins, but for now, I'd like to get others opinion about the approach of first getting the invoices and then getting the invoice items.
There was a problem hiding this comment.
I don't think we are at risk of N+1 queries, if I am not missing anything this should be max 2 queries one for invoices and one for invoiceItems.
My goal was to reuse the query filter logic on invoiceService.getInvoices(params) since we are expecting to get only those InvoiceItems that are a part of filtered invoices from invoice/list page, basically aggregate these queries.
First get invoices (filtered if necessary) and then based on these invoices get all of the invoiceItems
There was a problem hiding this comment.
@drodzewicz On the other hand if this is for "import all" case you could also just do: get invoices -> return invoices.invoiceItems. Is the pagination required here? However if this is going to land in its own api controller, or the pagination params are required, then:
I agree with @kchelstowski on that one in the long term. If you need to do some further invoice filtering you still can do it in the suggested by Kacper way as a:
invoice {
'in'('id', params.invoiceIds) // if there are invoice ids passed
whatever other invoice filter you have there
}Instead of doing get invoices -> get invoice items by these invoices.
| invoiceItem.invoice?.currencyUom.name, | ||
| invoiceItem.invoice?.status?.name(), | ||
| "${invoiceItem.invoice?.partyFrom?.code} ${invoiceItem.invoice?.partyFrom?.name}", | ||
| invoiceItem.invoice?.invoiceType?.code?.name() ?: "INVOICE", |
There was a problem hiding this comment.
let's use the enum from InvoiceTypeCode in the fallback
| invoiceItem.invoice?.status?.name(), | ||
| "${invoiceItem.invoice?.partyFrom?.code} ${invoiceItem.invoice?.partyFrom?.name}", | ||
| invoiceItem.invoice?.invoiceType?.code?.name() ?: "INVOICE", | ||
| invoiceItem.product?.productCode ?: "all", |
There was a problem hiding this comment.
what does "all" mean here?
There was a problem hiding this comment.
on invoice show page we have a similar case where we label adjustments as all since adjustments don't have a product code
openboxes/grails-app/views/invoice/_invoiceItems.gsp
Lines 23 to 25 in 3627a1d
src/js/api/urls.js
Outdated
|
|
||
| // INVOICE | ||
| export const INVOICE_API = `${API}/invoices`; | ||
| export const INVOICE_ITEMS_API = `${API}/invoices/items`; |
There was a problem hiding this comment.
if we agree this should be the url, then I think this could be shortened to:
export const INVOICE_ITEMS_API = `${INVOICE_API}/items`;| type="button" | ||
| className="dropdown-item" | ||
| onClick={() => downloadInvoices()} | ||
| onClick={downloadInvoices} |
There was a problem hiding this comment.
I said it once to @alannadolny , but we need to be aware, that in this case, if we added any argument to the downloadInvoices method, this would be run with the event from the button.
| invoiceItem?.quantity, | ||
| invoiceItem?.quantityPerUom, | ||
| invoiceItem?.unitPrice ?: 0, | ||
| invoiceItem?.totalAmount ?: 0, |
There was a problem hiding this comment.
@drodzewicz I meant you might run into N+1 in this place, where you refer to many different entities and this would run N+1 for every invoice item. So my N+1 comment from above was not directly refering to the "two queries" you made, but overall.
There was a problem hiding this comment.
@kchelstowski Yup, you're right, I also prefer to use our data services here (or just measure the performance and make a decision based on that)
- change endpoint for exporting invoiceItems - fixes for const values
|
Pushed some requested changes suggetsed by @kchelstowski and waiting for other developers (@alannadolny @awalkowiak), to review this PR and give an opinion on Kacpers comments |
| def getAllInvoiceItems() { | ||
| Location location = Location.get(session.warehouse.id) | ||
| params.partyFromId = location?.organization?.id | ||
| List<InvoiceList> invoices = invoiceService.getInvoices(params) |
There was a problem hiding this comment.
One more point here - should we pass the whole params object as an argument as it is done here?
List<InvoiceList> invoices = invoiceService.getInvoices(params)
| } | ||
|
|
||
| CSVPrinter getInvoiceItemsCsv(List<InvoiceItem> invoiceItems) { | ||
| def g = Holders.grailsApplication.mainContext.getBean('org.grails.plugins.web.taglib.ApplicationTagLib') |
There was a problem hiding this comment.
Maybe it is worth to use move this to a function (like it is already done in some places)
def getApplicationTagLib() {
return Holders.grailsApplication.mainContext.getBean(ApplicationTagLib)
}
| invoiceItem?.quantity, | ||
| invoiceItem?.quantityPerUom, | ||
| invoiceItem?.unitPrice ?: 0, | ||
| invoiceItem?.totalAmount ?: 0, |
There was a problem hiding this comment.
@kchelstowski Yup, you're right, I also prefer to use our data services here (or just measure the performance and make a decision based on that)
|
|
||
| "/api/invoiceItems"(parseRequest: true) { | ||
| controller = "invoiceApi" | ||
| action = [GET: "getAllInvoiceItems"] |
There was a problem hiding this comment.
I think this should be in a separate InvoiceItemApiController, because what I see the getInvoiceItems in the InvoiceAPIController is meant to get the invoice items from a specific invoice item, so if this one is meant to pull all invoice items from different invoices, then this should land in a separate controller.
There was a problem hiding this comment.
we also have
"/api/invoiceItems/$id/validation" {
controller = "invoiceApi"
action = [POST: "validateInvoiceItem"]
}so this will also have to end up in invoiceItemApicontroller
There was a problem hiding this comment.
Not really, I think this one can stay where it is, at least for now
2ada6eb to
2fee9b2
Compare
|
So after looking through the problem again, I came to the conclusion that my initial approach was wrong and this exact export should not have bee handled by a different endpoint targeting a list of invoiceItems but instead I should have treated it as an expansion to invoice/list since we are interested in all of the filter parameters to persist from the invoice list to invoiceItem. An exact case is handled in Purchase Order list So following the example of PO list I did the same thing, which is exporting a different document based on passed parameter Additionally as Kacper mentioned in previous comments, it would be nice to fetch invoice items eagerly for this export to avoid unnecessary N+1 queries to the database, so I added a parameter
|
|
|
||
| import { hideSpinner, showSpinner } from 'actions'; | ||
| import invoiceApi from 'api/services/InvoiceApi'; | ||
| import invoiceItemApi from 'api/services/InvoiceItemApi'; |
There was a problem hiding this comment.
I think it is not used?
There was a problem hiding this comment.
it's not, thanks
- add invoiceItems parameter to specify invoiceItem export - rollback previous changes - add fetchItemsEagerly parameter to fetch invoiceitems eagerly
2fee9b2 to
c86f6c3
Compare
kchelstowski
left a comment
There was a problem hiding this comment.
thanks for the times @drodzewicz . Even though the difference is not that huge right now, we will get the benefit from it when the invoice table becomes bigger and bigger.
No description provided.