fix: improve pos return (#45671)

* fix: pos return validation for zero qty item

* fix: pos return invoice onload ui

* feat: added item qty returned in pos ui

* refactor: removed console log statement

* feat: check return can be made before loading it on pos

* fix: pos edit invoice onload ui

* fix: returned

* refactor: code cleanup
This commit is contained in:
Diptanil Saha
2025-02-17 15:00:56 +05:30
committed by GitHub
parent d94802067b
commit 5506b44b6f
5 changed files with 128 additions and 32 deletions

View File

@@ -173,7 +173,7 @@ class AccountsController(TransactionBase):
self.validate_qty_is_not_zero()
if (
self.doctype in ["Sales Invoice", "Purchase Invoice"]
self.doctype in ["Sales Invoice", "Purchase Invoice", "POS Invoice"]
and self.get("is_return")
and self.get("update_stock")
):

View File

@@ -258,7 +258,7 @@ def get_already_returned_items(doc):
field = (
frappe.scrub(doc.doctype) + "_item"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice"]
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice", "POS Invoice"]
else "dn_detail"
)
data = frappe.db.sql(
@@ -770,6 +770,7 @@ def get_return_against_item_fields(voucher_type):
"Delivery Note": "dn_detail",
"Sales Invoice": "sales_invoice_item",
"Subcontracting Receipt": "subcontracting_receipt_item",
"POS Invoice": "sales_invoice_item",
}
return return_against_item_fields[voucher_type]
@@ -1162,3 +1163,29 @@ def get_available_serial_nos(serial_nos, warehouse):
def get_payment_data(invoice):
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
return payment
@frappe.whitelist()
def get_pos_invoice_item_returned_qty(pos_invoice, customer, item_row_name):
is_return, docstatus = frappe.db.get_value("POS Invoice", pos_invoice, ["is_return", "docstatus"])
if not is_return and docstatus == 1:
return get_returned_qty_map_for_row(pos_invoice, customer, item_row_name, "POS Invoice")
@frappe.whitelist()
def is_pos_invoice_returnable(pos_invoice):
is_return, docstatus, customer = frappe.db.get_value(
"POS Invoice", pos_invoice, ["is_return", "docstatus", "customer"]
)
if is_return or docstatus == 0:
return False
invoice_item_qty = frappe.db.get_all("POS Invoice Item", {"parent": pos_invoice}, ["name", "qty"])
already_full_returned = 0
for d in invoice_item_qty:
returned_qty = get_returned_qty_map_for_row(pos_invoice, customer, d.name, "POS Invoice")
if returned_qty.qty == d.qty:
already_full_returned += 1
return len(invoice_item_qty) != already_full_returned

View File

@@ -1087,34 +1087,49 @@
> .item-row-wrapper {
display: flex;
align-items: center;
gap: 2px;
flex-direction: column;
padding: var(--padding-sm) var(--padding-md);
border: 1px solid lightgray;
border-radius: 10px;
background: var(--bg-light-gray);
> .item-name {
@extend .nowrap;
font-weight: 500;
margin-right: var(--margin-md);
}
> .item-qty {
font-weight: 500;
margin-left: auto;
}
> .item-rate-disc {
> .item-row-data {
display: flex;
text-align: right;
margin-left: var(--margin-md);
justify-content: flex-end;
align-items: center;
> .item-disc {
color: var(--dark-green-500);
}
> .item-rate {
> .item-name {
@extend .nowrap;
font-weight: 500;
margin-left: var(--margin-md);
margin-right: var(--margin-md);
}
> .item-qty {
font-weight: 500;
margin-left: auto;
font-size: small;
}
> .item-rate-disc {
display: flex;
text-align: right;
margin-left: var(--margin-md);
justify-content: flex-end;
font-size: small;
> .item-disc {
color: var(--dark-green-500);
}
> .item-rate {
font-weight: 500;
margin-left: var(--margin-md);
}
}
}
> .item-row-refund {
font-size: x-small;
}
}
@@ -1127,6 +1142,12 @@
}
}
> .order-summary-container {
display: flex;
background: white;
gap: 8px;
}
> .summary-btns {
display: flex;
justify-content: space-between;

View File

@@ -459,6 +459,8 @@ erpnext.PointOfSale.Controller = class {
() => this.make_return_invoice(doc),
() => this.cart.load_invoice(),
() => this.item_selector.toggle_component(true),
() => this.item_selector.resize_selector(false),
() => this.item_details.toggle_component(false),
]);
});
},
@@ -469,6 +471,8 @@ erpnext.PointOfSale.Controller = class {
() => this.frm.call("reset_mode_of_payments"),
() => this.cart.load_invoice(),
() => this.item_selector.toggle_component(true),
() => this.item_selector.resize_selector(false),
() => this.item_details.toggle_component(false),
]);
},
delete_order: (name) => {

View File

@@ -24,7 +24,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
<div class="abs-container">
<div class="upper-section"></div>
<div class="label">${__("Items")}</div>
<div class="items-container summary-container"></div>
<div class="items-container summary-container order-summary-container"></div>
<div class="label">${__("Totals")}</div>
<div class="totals-container summary-container"></div>
<div class="label">${__("Payments")}</div>
@@ -90,12 +90,18 @@ erpnext.PointOfSale.PastOrderSummary = class {
</div>`;
}
get_item_html(doc, item_data) {
async get_item_html(doc, item_data) {
const item_refund_data = doc.is_return || doc.docstatus === 0 ? "" : await get_returned_qty();
return `<div class="item-row-wrapper">
<div class="item-row-data">
<div class="item-name">${item_data.item_name}</div>
<div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
<div class="item-rate-disc">${get_rate_discount_html()}</div>
</div>`;
</div>
${item_refund_data}
</div>`;
function get_rate_discount_html() {
if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
@@ -108,6 +114,25 @@ erpnext.PointOfSale.PastOrderSummary = class {
)}</div>`;
}
}
async function get_returned_qty() {
const r = await frappe.call({
method: "erpnext.controllers.sales_and_purchase_return.get_pos_invoice_item_returned_qty",
args: {
pos_invoice: doc.name,
customer: doc.customer,
item_row_name: item_data.name,
},
});
if (!r.message.qty) {
return "";
}
return `<div class="item-row-refund">
<strong>${r.message.qty}</strong> ${__("Returned")}
</div>`;
}
}
get_discount_html(doc) {
@@ -166,7 +191,16 @@ erpnext.PointOfSale.PastOrderSummary = class {
}
bind_events() {
this.$summary_container.on("click", ".return-btn", () => {
this.$summary_container.on("click", ".return-btn", async () => {
const r = await this.is_pos_invoice_returnable(this.doc.name);
if (!r) {
frappe.msgprint({
title: __("Invalid Return"),
indicator: "orange",
message: __("All the items have been already returned."),
});
return;
}
this.events.process_return(this.doc.name);
this.toggle_component(false);
this.$component.find(".no-summary-placeholder").css("display", "flex");
@@ -370,13 +404,13 @@ erpnext.PointOfSale.PastOrderSummary = class {
});
}
attach_items_info(doc) {
async attach_items_info(doc) {
this.$items_container.html("");
doc.items.forEach((item) => {
const item_dom = this.get_item_html(doc, item);
for (const item of doc.items) {
const item_dom = await this.get_item_html(doc, item);
this.$items_container.append(item_dom);
this.set_dynamic_rate_header_width();
});
}
}
set_dynamic_rate_header_width() {
@@ -438,4 +472,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.print_receipt();
}
}
async is_pos_invoice_returnable(invoice) {
const r = await frappe.call({
method: "erpnext.controllers.sales_and_purchase_return.is_pos_invoice_returnable",
args: {
pos_invoice: invoice,
},
});
return r.message;
}
};