Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -1785,29 +1785,52 @@ func (r *orderRepository) resolveOfflineCustomer(tx *gorm.DB, ctx *offlineLocati
|
||||
// instead of a race: two uploads of the same file arriving together would
|
||||
// otherwise both read "not yet imported" and both commit.
|
||||
func (r *orderRepository) UploadOfflineSales(input models.OfflineSalesUpload) (*models.OfflineSalesUploadResponse, error) {
|
||||
if input.Tenantid <= 0 {
|
||||
return nil, errors.New("tenantid is required")
|
||||
}
|
||||
if len(input.Bills) == 0 {
|
||||
return nil, errors.New("no sales rows found in the upload")
|
||||
}
|
||||
|
||||
ctx, err := r.resolveOfflineLocationContext(input.Tenantid, input.Locationid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// When the caller pins the upload to one branch, that branch is resolved
|
||||
// (and authorised) up front so an outlet the merchant does not own fails
|
||||
// the whole request rather than each bill in turn.
|
||||
if input.Locationid > 0 {
|
||||
if _, err := r.resolveOfflineLocationContext(input.Tenantid, input.Locationid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
products, err := r.loadOfflineProducts(input.Tenantid, input.Locationid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(products) == 0 {
|
||||
return nil, fmt.Errorf("outlet '%s' has no products stocked against it", ctx.Locationname)
|
||||
// Branch context and catalogue are resolved once per branch and reused. A
|
||||
// workbook covering six outlets would otherwise re-run both queries for
|
||||
// every bill in it.
|
||||
contexts := make(map[int]*offlineLocationContext)
|
||||
catalogues := make(map[int]map[int]offlineProduct)
|
||||
|
||||
resolve := func(locationID int) (*offlineLocationContext, map[int]offlineProduct, error) {
|
||||
if ctx, ok := contexts[locationID]; ok {
|
||||
return ctx, catalogues[locationID], nil
|
||||
}
|
||||
ctx, err := r.resolveOfflineLocationContext(input.Tenantid, locationID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
products, err := r.loadOfflineProducts(input.Tenantid, locationID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(products) == 0 {
|
||||
return nil, nil, fmt.Errorf("outlet '%s' has no products stocked against it", ctx.Locationname)
|
||||
}
|
||||
contexts[locationID] = ctx
|
||||
catalogues[locationID] = products
|
||||
return ctx, products, nil
|
||||
}
|
||||
|
||||
resp := &models.OfflineSalesUploadResponse{Results: make([]models.OfflineSaleResult, 0, len(input.Bills))}
|
||||
|
||||
for _, bill := range input.Bills {
|
||||
result := r.importOfflineBill(ctx, products, input.Userid, bill)
|
||||
record := func(result models.OfflineSaleResult) {
|
||||
resp.Results = append(resp.Results, result)
|
||||
|
||||
switch result.Status {
|
||||
case models.OfflineSaleImported:
|
||||
resp.Imported++
|
||||
@@ -1819,6 +1842,49 @@ func (r *orderRepository) UploadOfflineSales(input models.OfflineSalesUpload) (*
|
||||
}
|
||||
}
|
||||
|
||||
for _, bill := range input.Bills {
|
||||
billLocation := bill.Locationid
|
||||
if billLocation <= 0 {
|
||||
billLocation = input.Locationid
|
||||
}
|
||||
|
||||
if billLocation <= 0 {
|
||||
record(models.OfflineSaleResult{
|
||||
Billno: strings.TrimSpace(bill.Billno),
|
||||
Status: models.OfflineSaleFailed,
|
||||
Message: "no locationid on these rows — the sheet must say which branch the sale belongs to",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// A pinned upload refuses bills for anywhere else. This is what keeps a
|
||||
// store user inside their own branch: editing the locationid column in
|
||||
// the spreadsheet changes nothing, because the pin is set from their
|
||||
// session and not from the file.
|
||||
if input.Locationid > 0 && billLocation != input.Locationid {
|
||||
record(models.OfflineSaleResult{
|
||||
Locationid: billLocation,
|
||||
Billno: strings.TrimSpace(bill.Billno),
|
||||
Status: models.OfflineSaleFailed,
|
||||
Message: fmt.Sprintf("this upload is limited to outlet %d, but these rows are for outlet %d", input.Locationid, billLocation),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, products, err := resolve(billLocation)
|
||||
if err != nil {
|
||||
record(models.OfflineSaleResult{
|
||||
Locationid: billLocation,
|
||||
Billno: strings.TrimSpace(bill.Billno),
|
||||
Status: models.OfflineSaleFailed,
|
||||
Message: err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
record(r.importOfflineBill(ctx, products, input.Userid, bill))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -1836,9 +1902,11 @@ func (r *orderRepository) importOfflineBill(
|
||||
|
||||
fail := func(format string, args ...any) models.OfflineSaleResult {
|
||||
return models.OfflineSaleResult{
|
||||
Billno: billLabel,
|
||||
Status: models.OfflineSaleFailed,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
Locationid: ctx.Locationid,
|
||||
Locationname: ctx.Locationname,
|
||||
Billno: billLabel,
|
||||
Status: models.OfflineSaleFailed,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1968,9 +2036,11 @@ func (r *orderRepository) importOfflineBill(
|
||||
if already > 0 {
|
||||
tx.Rollback()
|
||||
return models.OfflineSaleResult{
|
||||
Billno: billLabel,
|
||||
Status: models.OfflineSaleDuplicate,
|
||||
Message: fmt.Sprintf("bill %s was already imported for this outlet; stock was not deducted again", billLabel),
|
||||
Locationid: ctx.Locationid,
|
||||
Locationname: ctx.Locationname,
|
||||
Billno: billLabel,
|
||||
Status: models.OfflineSaleDuplicate,
|
||||
Message: fmt.Sprintf("bill %s was already imported for %s; stock was not deducted again", billLabel, ctx.Locationname),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2021,13 +2091,15 @@ func (r *orderRepository) importOfflineBill(
|
||||
}
|
||||
|
||||
return models.OfflineSaleResult{
|
||||
Locationid: ctx.Locationid,
|
||||
Locationname: ctx.Locationname,
|
||||
Billno: billLabel,
|
||||
Status: models.OfflineSaleImported,
|
||||
Orderid: created.Orderid,
|
||||
Orderheaderid: created.Orderheaderid,
|
||||
Itemcount: len(items),
|
||||
Amount: orderAmount,
|
||||
Message: fmt.Sprintf("imported as order %s", created.Orderid),
|
||||
Message: fmt.Sprintf("imported as order %s at %s", created.Orderid, ctx.Locationname),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user