mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-21 22:19:18 +00:00
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
(cherry picked from commit 5506b44b6f)
This commit is contained in:
@@ -170,7 +170,7 @@ class AccountsController(TransactionBase):
|
|||||||
self.validate_qty_is_not_zero()
|
self.validate_qty_is_not_zero()
|
||||||
|
|
||||||
if (
|
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("is_return")
|
||||||
and self.get("update_stock")
|
and self.get("update_stock")
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ def get_already_returned_items(doc):
|
|||||||
|
|
||||||
field = (
|
field = (
|
||||||
frappe.scrub(doc.doctype) + "_item"
|
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"
|
else "dn_detail"
|
||||||
)
|
)
|
||||||
data = frappe.db.sql(
|
data = frappe.db.sql(
|
||||||
@@ -770,6 +770,7 @@ def get_return_against_item_fields(voucher_type):
|
|||||||
"Delivery Note": "dn_detail",
|
"Delivery Note": "dn_detail",
|
||||||
"Sales Invoice": "sales_invoice_item",
|
"Sales Invoice": "sales_invoice_item",
|
||||||
"Subcontracting Receipt": "subcontracting_receipt_item",
|
"Subcontracting Receipt": "subcontracting_receipt_item",
|
||||||
|
"POS Invoice": "sales_invoice_item",
|
||||||
}
|
}
|
||||||
return return_against_item_fields[voucher_type]
|
return return_against_item_fields[voucher_type]
|
||||||
|
|
||||||
@@ -1162,3 +1163,29 @@ def get_available_serial_nos(serial_nos, warehouse):
|
|||||||
def get_payment_data(invoice):
|
def get_payment_data(invoice):
|
||||||
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
|
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
|
||||||
return payment
|
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
|
||||||
|
|||||||
@@ -1087,34 +1087,49 @@
|
|||||||
|
|
||||||
> .item-row-wrapper {
|
> .item-row-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 2px;
|
||||||
|
flex-direction: column;
|
||||||
padding: var(--padding-sm) var(--padding-md);
|
padding: var(--padding-sm) var(--padding-md);
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--bg-light-gray);
|
||||||
|
|
||||||
> .item-name {
|
> .item-row-data {
|
||||||
@extend .nowrap;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-right: var(--margin-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .item-qty {
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .item-rate-disc {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: right;
|
align-items: center;
|
||||||
margin-left: var(--margin-md);
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
> .item-disc {
|
> .item-name {
|
||||||
color: var(--dark-green-500);
|
@extend .nowrap;
|
||||||
}
|
|
||||||
|
|
||||||
> .item-rate {
|
|
||||||
font-weight: 500;
|
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 {
|
> .summary-btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -459,6 +459,8 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
() => this.make_return_invoice(doc),
|
() => this.make_return_invoice(doc),
|
||||||
() => this.cart.load_invoice(),
|
() => this.cart.load_invoice(),
|
||||||
() => this.item_selector.toggle_component(true),
|
() => 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.frm.call("reset_mode_of_payments"),
|
||||||
() => this.cart.load_invoice(),
|
() => this.cart.load_invoice(),
|
||||||
() => this.item_selector.toggle_component(true),
|
() => this.item_selector.toggle_component(true),
|
||||||
|
() => this.item_selector.resize_selector(false),
|
||||||
|
() => this.item_details.toggle_component(false),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
delete_order: (name) => {
|
delete_order: (name) => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
<div class="abs-container">
|
<div class="abs-container">
|
||||||
<div class="upper-section"></div>
|
<div class="upper-section"></div>
|
||||||
<div class="label">${__("Items")}</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="label">${__("Totals")}</div>
|
||||||
<div class="totals-container summary-container"></div>
|
<div class="totals-container summary-container"></div>
|
||||||
<div class="label">${__("Payments")}</div>
|
<div class="label">${__("Payments")}</div>
|
||||||
@@ -90,12 +90,18 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
</div>`;
|
</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">
|
return `<div class="item-row-wrapper">
|
||||||
|
<div class="item-row-data">
|
||||||
<div class="item-name">${item_data.item_name}</div>
|
<div class="item-name">${item_data.item_name}</div>
|
||||||
<div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
|
<div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
|
||||||
<div class="item-rate-disc">${get_rate_discount_html()}</div>
|
<div class="item-rate-disc">${get_rate_discount_html()}</div>
|
||||||
</div>`;
|
</div>
|
||||||
|
|
||||||
|
${item_refund_data}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
function get_rate_discount_html() {
|
function get_rate_discount_html() {
|
||||||
if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
|
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>`;
|
)}</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) {
|
get_discount_html(doc) {
|
||||||
@@ -166,7 +191,16 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind_events() {
|
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.events.process_return(this.doc.name);
|
||||||
this.toggle_component(false);
|
this.toggle_component(false);
|
||||||
this.$component.find(".no-summary-placeholder").css("display", "flex");
|
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("");
|
this.$items_container.html("");
|
||||||
doc.items.forEach((item) => {
|
for (const item of doc.items) {
|
||||||
const item_dom = this.get_item_html(doc, item);
|
const item_dom = await this.get_item_html(doc, item);
|
||||||
this.$items_container.append(item_dom);
|
this.$items_container.append(item_dom);
|
||||||
this.set_dynamic_rate_header_width();
|
this.set_dynamic_rate_header_width();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_dynamic_rate_header_width() {
|
set_dynamic_rate_header_width() {
|
||||||
@@ -438,4 +472,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
this.print_receipt();
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user