Merge branch 'rebrand-ui' of https://github.com/frappe/erpnext into rebrand-ui

This commit is contained in:
Shivam Mishra 2020-11-26 13:41:52 +05:30
commit 94ebbc550e
63 changed files with 1759 additions and 1665 deletions

View File

@ -64,11 +64,11 @@ class OpeningInvoiceCreationTool(Document):
prepare_invoice_summary(doctype, invoices)
return invoices_summary, max_count
def validate_company(self):
if not self.company:
frappe.throw(_("Please select the Company"))
def set_missing_values(self, row):
row.qty = row.qty or 1.0
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
@ -209,7 +209,7 @@ def start_import(invoices):
frappe.db.commit()
if errors:
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
.format(errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
return names
def publish(index, total, doctype):

View File

@ -471,7 +471,7 @@ class Asset(AccountsController):
asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
fixed_asset_account = self.get_fixed_asset_account()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled)
@ -503,10 +503,10 @@ class Asset(AccountsController):
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
return purchase_document
def get_fixed_asset_account(self):
return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
def get_cwip_account(self, cwip_enabled=False):
cwip_account = None
try:
@ -659,7 +659,7 @@ def transfer_asset(args):
frappe.db.commit()
frappe.msgprint(_("Asset Movement record {0} created").format("<a href='#Form/Asset Movement/{0}'>{0}</a>").format(movement_entry.name))
frappe.msgprint(_("Asset Movement record {0} created").format("<a href='/app/Form/Asset Movement/{0}'>{0}</a>").format(movement_entry.name))
@frappe.whitelist()
def get_item_details(item_code, asset_category):

View File

@ -71,7 +71,7 @@ class SupplierQuotation(BuyingController):
doc_sup = doc_sup[0] if doc_sup else None
if not doc_sup:
frappe.throw(_("Supplier {0} not found in {1}").format(self.supplier,
"<a href='desk#Form/Request for Quotation/{0}'> Request for Quotation {0} </a>".format(doc.name)))
"<a href='desk/app/Form/Request for Quotation/{0}'> Request for Quotation {0} </a>".format(doc.name)))
quote_status = _('Received')
for item in doc.items:

View File

@ -173,7 +173,7 @@ def get_data():
{
"type": "doctype",
"name": "Course Schedule",
"route": "#List/Course Schedule/Calendar"
"route": "/app/List/Course Schedule/Calendar"
},
{
"type": "doctype",

View File

@ -16,13 +16,13 @@ def get_data():
{
"type": "doctype",
"name": "Task",
"route": "#List/Task",
"route": "/app/List/Task",
"description": _("Project activity / task."),
"onboard": 1,
},
{
"type": "report",
"route": "#List/Task/Gantt",
"route": "/app/List/Task/Gantt",
"doctype": "Task",
"name": "Gantt Chart",
"description": _("Gantt chart of all tasks."),
@ -97,5 +97,5 @@ def get_data():
},
]
},
]

View File

@ -128,7 +128,7 @@ frappe.ui.form.on('Assessment Result Tool', {
result_table.find(`span[data-student=${assessment_result.student}].total-score-grade`).html(assessment_result.grade);
let link_span = result_table.find(`span[data-student=${assessment_result.student}].total-result-link`);
$(link_span).css("display", "block");
$(link_span).find("a").attr("href", "#Form/Assessment Result/"+assessment_result.name);
$(link_span).find("a").attr("href", "/desk/Form/Assessment Result/"+assessment_result.name);
}
});
}

View File

@ -25,7 +25,7 @@ frappe.ui.form.on('Course Scheduling Tool', {
<thead><tr><th>${__("Course")}</th><th>${__("Date")}</th></tr></thead>
<tbody>
${course_schedules.map(
c => `<tr><td><a href="#Form/Course Schedule/${c.name}">${c.name}</a></td>
c => `<tr><td><a href="/desk/Form/Course Schedule/${c.name}">${c.name}</a></td>
<td>${c.schedule_date}</td></tr>`
).join('')}
</tbody>

View File

@ -87,7 +87,7 @@ class ProgramEnrollment(Document):
fees.submit()
fee_list.append(fees.name)
if fee_list:
fee_list = ["""<a href="#Form/Fees/%s" target="_blank">%s</a>""" % \
fee_list = ["""<a href="/app/Form/Fees/%s" target="_blank">%s</a>""" % \
(fee, fee) for fee in fee_list]
msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))

View File

@ -23,10 +23,10 @@ frappe.ui.form.on("Tally Migration", {
frappe.msgprint({
message: __("An error has occurred during {0}. Check {1} for more details",
[
repl("<a href='#Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
repl("<a href='/desk/Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
tally_document: frm.docname
}),
"<a href='#List/Error Log' class='variant-click'>Error Log</a>"
"<a href='/desk/List/Error Log' class='variant-click'>Error Log</a>"
]
),
title: __("Tally Migration Error"),

View File

@ -86,7 +86,7 @@ frappe.ui.form.on('Clinical Procedure', {
if (r.message) {
frappe.show_alert({
message: __('Stock Entry {0} created',
['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
['<a class="bold" href="/desk/Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
indicator: 'green'
});
}

View File

@ -50,7 +50,7 @@ class InpatientRecord(Document):
if ip_record:
msg = _(("Already {0} Patient {1} with Inpatient Record ").format(ip_record[0].status, self.patient) \
+ """ <b><a href="#Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
+ """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
frappe.throw(msg)
def admit(self, service_unit, check_in, expected_discharge=None):

View File

@ -63,7 +63,7 @@ class PatientAppointment(Document):
if overlaps:
overlapping_details = _('Appointment overlaps with ')
overlapping_details += "<b><a href='#Form/Patient Appointment/{0}'>{0}</a></b><br>".format(overlaps[0][0])
overlapping_details += "<b><a href='/app/Form/Patient Appointment/{0}'>{0}</a></b><br>".format(overlaps[0][0])
overlapping_details += _('{0} has appointment scheduled with {1} at {2} having {3} minute(s) duration.').format(
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
@ -75,7 +75,7 @@ class PatientAppointment(Document):
if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'):
if not frappe.db.get_value('Patient', self.patient, 'customer'):
msg = _("Please set a Customer linked to the Patient")
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(self.patient)
msg += " <b><a href='/app/Form/Patient/{0}'>{0}</a></b>".format(self.patient)
frappe.throw(msg, title=_('Customer Not Found'))
def update_prescription_details(self):

View File

@ -32,7 +32,7 @@ def get_healthcare_services_to_invoice(patient, company):
def validate_customer_created(patient):
if not frappe.db.get_value('Patient', patient.name, 'customer'):
msg = _("Please set a Customer linked to the Patient")
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
msg += " <b><a href='/app/Form/Patient/{0}'>{0}</a></b>".format(patient.name)
frappe.throw(msg, title=_('Customer Not Found'))
@ -169,7 +169,7 @@ def get_clinical_procedures_to_invoice(patient, company):
service_item = get_healthcare_service_item('clinical_procedure_consumable_item')
if not service_item:
msg = _('Please Configure Clinical Procedure Consumable Item in ')
msg += '''<b><a href='#Form/Healthcare Settings'>Healthcare Settings</a></b>'''
msg += '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>'''
frappe.throw(msg, title=_('Missing Configuration'))
clinical_procedures_to_invoice.append({
@ -324,7 +324,7 @@ def throw_config_service_item(is_inpatient):
service_item_label = _('Inpatient Visit Charge Item')
msg = _(('Please Configure {0} in ').format(service_item_label) \
+ '''<b><a href='#Form/Healthcare Settings'>Healthcare Settings</a></b>''')
+ '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>''')
frappe.throw(msg, title=_('Missing Configuration'))
@ -334,7 +334,7 @@ def throw_config_practitioner_charge(is_inpatient, practitioner):
charge_name = _('Inpatient Visit Charge')
msg = _(('Please Configure {0} for Healthcare Practitioner').format(charge_name) \
+ ''' <b><a href='#Form/Healthcare Practitioner/{0}'>{0}</a></b>'''.format(practitioner))
+ ''' <b><a href='/app/Form/Healthcare Practitioner/{0}'>{0}</a></b>'''.format(practitioner))
frappe.throw(msg, title=_('Missing Configuration'))
@ -654,6 +654,6 @@ def render_doc_as_html(doctype, docname, exclude_fields = []):
><div class='col-md-12 col-sm-12'>" \
+ section_html + html +'</div></div>'
if doc_html:
doc_html = "<div class='small'><div class='col-md-12 text-right'><a class='btn btn-default btn-xs' href='#Form/%s/%s'></a></div>" %(doctype, docname) + doc_html + '</div>'
doc_html = "<div class='small'><div class='col-md-12 text-right'><a class='btn btn-default btn-xs' href='/app/Form/%s/%s'></a></div>" %(doctype, docname) + doc_html + '</div>'
return {'html': doc_html}

View File

@ -50,7 +50,7 @@ class EmployeeTransfer(Document):
employee = frappe.get_doc("Employee", self.employee)
if self.create_new_employee_id:
if self.new_employee_id:
frappe.throw(_("Please delete the Employee <a href='#Form/Employee/{0}'>{0}</a>\
frappe.throw(_("Please delete the Employee <a href='/app/Form/Employee/{0}'>{0}</a>\
to cancel this document").format(self.new_employee_id))
#mark the employee as active
employee.status = "Active"

View File

@ -82,7 +82,7 @@ class LeaveAllocation(Document):
frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}")
.format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date)))
frappe.throw(_('Reference') + ': <a href="#Form/Leave Allocation/{0}">{0}</a>'
frappe.throw(_('Reference') + ': <a href="/app/Form/Leave Allocation/{0}">{0}</a>'
.format(leave_allocation[0][0]), OverlapError)
def validate_back_dated_allocation(self):

View File

@ -246,7 +246,7 @@ class LeaveApplication(Document):
def throw_overlap_error(self, d):
msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
+ """ <b><a href="#Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
+ """ <b><a href="/app/Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
frappe.throw(msg, OverlapError)
def get_total_leaves_on_half_day(self):

View File

@ -87,5 +87,5 @@ class ShiftRequest(Document):
def throw_overlap_error(self, d):
msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
+ """ <b><a href="#Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
+ """ <b><a href="/app/Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
frappe.throw(msg, OverlapError)

View File

@ -211,7 +211,7 @@ def get_doc_condition(doctype):
def throw_overlap_error(doc, exists_for, overlap_doc, from_date, to_date):
msg = _("A {0} exists between {1} and {2} (").format(doc.doctype,
formatdate(from_date), formatdate(to_date)) \
+ """ <b><a href="#Form/{0}/{1}">{1}</a></b>""".format(doc.doctype, overlap_doc) \
+ """ <b><a href="/app/Form/{0}/{1}">{1}</a></b>""".format(doc.doctype, overlap_doc) \
+ _(") for {0}").format(exists_for)
frappe.throw(msg)

View File

@ -134,7 +134,7 @@ frappe.ui.form.on("BOM", {
frm.set_intro(__('This is a Template BOM and will be used to make the work order for {0} of the item {1}',
[
`<a class="variants-intro">variants</a>`,
`<a href="#Form/Item/${frm.doc.item}">${frm.doc.item}</a>`,
`<a href="/desk/Form/Item/${frm.doc.item}">${frm.doc.item}</a>`,
]), true);
frm.$wrapper.find(".variants-intro").on("click", () => {

View File

@ -12,11 +12,11 @@
<hr style="margin: 15px -15px;">
<p>
{% if data.value %}
<a style="margin-right: 7px; margin-bottom: 7px" class="btn btn-default btn-xs" href="#Form/BOM/{{ data.value }}">
<a style="margin-right: 7px; margin-bottom: 7px" class="btn btn-default btn-xs" href="/app/Form/BOM/{{ data.value }}">
{{ __("Open BOM {0}", [data.value.bold()]) }}</a>
{% endif %}
{% if data.item_code %}
<a class="btn btn-default btn-xs" href="#Form/Item/{{ data.item_code }}">
<a class="btn btn-default btn-xs" href="/app/Form/Item/{{ data.item_code }}">
{{ __("Open Item {0}", [data.item_code.bold()]) }}</a>
{% endif %}
</p>

View File

@ -319,7 +319,7 @@ class ProductionPlan(Document):
frappe.flags.mute_messages = False
if wo_list:
wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
wo_list = ["""<a href="/app/Form/Work Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in wo_list]
msgprint(_("{0} created").format(comma_and(wo_list)))
else :
@ -423,7 +423,7 @@ class ProductionPlan(Document):
frappe.flags.mute_messages = False
if material_request_list:
material_request_list = ["""<a href="#Form/Material Request/{0}">{1}</a>""".format(m.name, m.name) \
material_request_list = ["""<a href="/app/Form/Material Request/{0}">{1}</a>""".format(m.name, m.name) \
for m in material_request_list]
msgprint(_("{0} created").format(comma_and(material_request_list)))
else :

View File

@ -27,9 +27,9 @@ frappe.query_reports["BOM Stock Report"] = {
value = default_formatter(value, row, column, data);
if (column.id == "Item"){
if (data["Enough Parts to Build"] > 0){
value = `<a style='color:green' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
value = `<a style='color:green' href="/desk/Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
} else {
value = `<a style='color:red' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
value = `<a style='color:red' href="/desk/Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
}
}
return value

View File

@ -41,7 +41,7 @@ class PayrollPeriod(Document):
if overlap_doc:
msg = _("A {0} exists between {1} and {2} (").format(self.doctype,
formatdate(self.start_date), formatdate(self.end_date)) \
+ """ <b><a href="#Form/{0}/{1}">{1}</a></b>""".format(self.doctype, overlap_doc[0].name) \
+ """ <b><a href="/app/Form/{0}/{1}">{1}</a></b>""".format(self.doctype, overlap_doc[0].name) \
+ _(") for {0}").format(self.company)
frappe.throw(msg)

View File

@ -26,7 +26,7 @@ frappe.listview_settings['Task'] = {
},
gantt_custom_popup_html: function(ganttobj, task) {
var html = `<h5><a style="text-decoration:underline"\
href="#Form/Task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
href="/desk/Form/Task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
if(task.project) html += `<p>Project: ${task.project}</p>`;
html += `<p>Progress: ${ganttobj.progress}</p>`;

View File

@ -2,7 +2,8 @@
"css/erpnext.css": [
"public/less/erpnext.less",
"public/less/hub.less",
"public/less/call_popup.less"
"public/less/call_popup.less",
"public/scss/point-of-sale.scss"
],
"css/marketplace.css": [
"public/less/hub.less"
@ -27,16 +28,6 @@
"public/js/payment/payments.js",
"public/js/controllers/taxes_and_totals.js",
"public/js/controllers/transaction.js",
"public/js/pos/pos.html",
"public/js/pos/pos_bill_item.html",
"public/js/pos/pos_bill_item_new.html",
"public/js/pos/pos_selected_item.html",
"public/js/pos/pos_item.html",
"public/js/pos/pos_tax_row.html",
"public/js/pos/customer_toolbar.html",
"public/js/pos/pos_invoice_list.html",
"public/js/payment/pos_payment.html",
"public/js/payment/payment_details.html",
"public/js/templates/item_selector.html",
"public/js/templates/employees_to_mark_attendance.html",
"public/js/utils/item_selector.js",
@ -55,5 +46,15 @@
"stock/dashboard/item_dashboard.html",
"stock/dashboard/item_dashboard_list.html",
"stock/dashboard/item_dashboard.js"
],
"js/point-of-sale.min.js": [
"selling/page/point_of_sale/pos_item_selector.js",
"selling/page/point_of_sale/pos_item_cart.js",
"selling/page/point_of_sale/pos_item_details.js",
"selling/page/point_of_sale/pos_number_pad.js",
"selling/page/point_of_sale/pos_payment.js",
"selling/page/point_of_sale/pos_past_order_list.js",
"selling/page/point_of_sale/pos_past_order_summary.js",
"selling/page/point_of_sale/pos_controller.js"
]
}

View File

@ -1,217 +0,0 @@
[data-route="point-of-sale"] .layout-main-section { border: none; font-size: 12px; }
[data-route="point-of-sale"] .layout-main-section-wrapper { margin-bottom: 0; }
[data-route="point-of-sale"] .pos-items-wrapper { max-height: calc(100vh - 210px); }
:root { --border-color: #d1d8dd; --text-color: #8d99a6; --primary: #5e64ff; }
[data-route="point-of-sale"] .flex { display: flex; }
[data-route="point-of-sale"] .grid { display: grid; }
[data-route="point-of-sale"] .absolute { position: absolute; }
[data-route="point-of-sale"] .relative { position: relative; }
[data-route="point-of-sale"] .abs-center { top: 50%; left: 50%; transform: translate(-50%, -50%); }
[data-route="point-of-sale"] .inline { display: inline; }
[data-route="point-of-sale"] .float-right { float: right; }
[data-route="point-of-sale"] .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
[data-route="point-of-sale"] .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
[data-route="point-of-sale"] .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
[data-route="point-of-sale"] .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
[data-route="point-of-sale"] .grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
[data-route="point-of-sale"] .grid-cols-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
[data-route="point-of-sale"] .gap-2 { grid-gap: 0.5rem; gap: 0.5rem; }
[data-route="point-of-sale"] .gap-4 { grid-gap: 1rem; gap: 1rem; }
[data-route="point-of-sale"] .gap-6 { grid-gap: 1.25rem; gap: 1.25rem; }
[data-route="point-of-sale"] .gap-8 { grid-gap: 1.5rem; gap: 1.5rem; }
[data-route="point-of-sale"] .row-gap-2 { grid-row-gap: 0.5rem; row-gap: 0.5rem; }
[data-route="point-of-sale"] .col-gap-4 { grid-column-gap: 1rem; column-gap: 1rem; }
[data-route="point-of-sale"] .col-span-2 { grid-column: span 2 / span 2; }
[data-route="point-of-sale"] .col-span-3 { grid-column: span 3 / span 3; }
[data-route="point-of-sale"] .col-span-4 { grid-column: span 4 / span 4; }
[data-route="point-of-sale"] .col-span-6 { grid-column: span 6 / span 6; }
[data-route="point-of-sale"] .col-span-10 { grid-column: span 10 / span 10; }
[data-route="point-of-sale"] .row-span-2 { grid-row: span 2 / span 2; }
[data-route="point-of-sale"] .grid-auto-row { grid-auto-rows: 5.5rem; }
[data-route="point-of-sale"] .d-none { display: none; }
[data-route="point-of-sale"] .flex-wrap { flex-wrap: wrap; }
[data-route="point-of-sale"] .flex-row { flex-direction: row; }
[data-route="point-of-sale"] .flex-col { flex-direction: column; }
[data-route="point-of-sale"] .flex-row-rev { flex-direction: row-reverse; }
[data-route="point-of-sale"] .flex-col-rev { flex-direction: column-reverse; }
[data-route="point-of-sale"] .flex-1 { flex: 1 1 0%; }
[data-route="point-of-sale"] .items-center { align-items: center; }
[data-route="point-of-sale"] .items-end { align-items: flex-end; }
[data-route="point-of-sale"] .f-grow-1 { flex-grow: 1; }
[data-route="point-of-sale"] .f-grow-2 { flex-grow: 2; }
[data-route="point-of-sale"] .f-grow-3 { flex-grow: 3; }
[data-route="point-of-sale"] .f-grow-4 { flex-grow: 4; }
[data-route="point-of-sale"] .f-shrink-0 { flex-shrink: 0; }
[data-route="point-of-sale"] .f-shrink-1 { flex-shrink: 1; }
[data-route="point-of-sale"] .f-shrink-2 { flex-shrink: 2; }
[data-route="point-of-sale"] .f-shrink-3 { flex-shrink: 3; }
[data-route="point-of-sale"] .shadow { box-shadow: 0 0px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 2px 0 rgba(0, 0, 0, 0.06); }
[data-route="point-of-sale"] .shadow-sm { box-shadow: 0 0.5px 3px 0 rgba(0, 0, 0, 0.125); }
[data-route="point-of-sale"] .shadow-inner { box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); }
[data-route="point-of-sale"] .rounded { border-radius: 0.3rem; }
[data-route="point-of-sale"] .rounded-b { border-bottom-left-radius: 0.3rem; border-bottom-right-radius: 0.3rem; }
[data-route="point-of-sale"] .p-8 { padding: 2rem; }
[data-route="point-of-sale"] .p-16 { padding: 4rem; }
[data-route="point-of-sale"] .p-32 { padding: 8rem; }
[data-route="point-of-sale"] .p-6 { padding: 1.5rem; }
[data-route="point-of-sale"] .p-4 { padding: 1rem; }
[data-route="point-of-sale"] .p-3 { padding: 0.75rem; }
[data-route="point-of-sale"] .p-2 { padding: 0.5rem; }
[data-route="point-of-sale"] .m-8 { margin: 2rem; }
[data-route="point-of-sale"] .p-1 { padding: 0.25rem; }
[data-route="point-of-sale"] .pr-0 { padding-right: 0rem; }
[data-route="point-of-sale"] .pl-0 { padding-left: 0rem; }
[data-route="point-of-sale"] .pt-0 { padding-top: 0rem; }
[data-route="point-of-sale"] .pb-0 { padding-bottom: 0rem; }
[data-route="point-of-sale"] .mr-0 { margin-right: 0rem; }
[data-route="point-of-sale"] .ml-0 { margin-left: 0rem; }
[data-route="point-of-sale"] .mt-0 { margin-top: 0rem; }
[data-route="point-of-sale"] .mb-0 { margin-bottom: 0rem; }
[data-route="point-of-sale"] .pr-2 { padding-right: 0.5rem; }
[data-route="point-of-sale"] .pl-2 { padding-left: 0.5rem; }
[data-route="point-of-sale"] .pt-2 { padding-top: 0.5rem; }
[data-route="point-of-sale"] .pb-2 { padding-bottom: 0.5rem; }
[data-route="point-of-sale"] .pr-3 { padding-right: 0.75rem; }
[data-route="point-of-sale"] .pl-3 { padding-left: 0.75rem; }
[data-route="point-of-sale"] .pt-3 { padding-top: 0.75rem; }
[data-route="point-of-sale"] .pb-3 { padding-bottom: 0.75rem; }
[data-route="point-of-sale"] .pr-4 { padding-right: 1rem; }
[data-route="point-of-sale"] .pl-4 { padding-left: 1rem; }
[data-route="point-of-sale"] .pt-4 { padding-top: 1rem; }
[data-route="point-of-sale"] .pb-4 { padding-bottom: 1rem; }
[data-route="point-of-sale"] .mr-4 { margin-right: 1rem; }
[data-route="point-of-sale"] .ml-4 { margin-left: 1rem; }
[data-route="point-of-sale"] .mt-4 { margin-top: 1rem; }
[data-route="point-of-sale"] .mb-4 { margin-bottom: 1rem; }
[data-route="point-of-sale"] .mr-2 { margin-right: 0.5rem; }
[data-route="point-of-sale"] .ml-2 { margin-left: 0.5rem; }
[data-route="point-of-sale"] .mt-2 { margin-top: 0.5rem; }
[data-route="point-of-sale"] .mb-2 { margin-bottom: 0.5rem; }
[data-route="point-of-sale"] .mr-1 { margin-right: 0.25rem; }
[data-route="point-of-sale"] .ml-1 { margin-left: 0.25rem; }
[data-route="point-of-sale"] .mt-1 { margin-top: 0.25rem; }
[data-route="point-of-sale"] .mb-1 { margin-bottom: 0.25rem; }
[data-route="point-of-sale"] .mr-auto { margin-right: auto; }
[data-route="point-of-sale"] .ml-auto { margin-left: auto; }
[data-route="point-of-sale"] .mt-auto { margin-top: auto; }
[data-route="point-of-sale"] .mb-auto { margin-bottom: auto; }
[data-route="point-of-sale"] .pr-6 { padding-right: 1.5rem; }
[data-route="point-of-sale"] .pl-6 { padding-left: 1.5rem; }
[data-route="point-of-sale"] .pt-6 { padding-top: 1.5rem; }
[data-route="point-of-sale"] .pb-6 { padding-bottom: 1.5rem; }
[data-route="point-of-sale"] .mr-6 { margin-right: 1.5rem; }
[data-route="point-of-sale"] .ml-6 { margin-left: 1.5rem; }
[data-route="point-of-sale"] .mt-6 { margin-top: 1.5rem; }
[data-route="point-of-sale"] .mb-6 { margin-bottom: 1.5rem; }
[data-route="point-of-sale"] .mr-8 { margin-right: 2rem; }
[data-route="point-of-sale"] .ml-8 { margin-left: 2rem; }
[data-route="point-of-sale"] .mt-8 { margin-top: 2rem; }
[data-route="point-of-sale"] .mb-8 { margin-bottom: 2rem; }
[data-route="point-of-sale"] .pr-8 { padding-right: 2rem; }
[data-route="point-of-sale"] .pl-8 { padding-left: 2rem; }
[data-route="point-of-sale"] .pt-8 { padding-top: 2rem; }
[data-route="point-of-sale"] .pb-8 { padding-bottom: 2rem; }
[data-route="point-of-sale"] .pr-16 { padding-right: 4rem; }
[data-route="point-of-sale"] .pl-16 { padding-left: 4rem; }
[data-route="point-of-sale"] .pt-16 { padding-top: 4rem; }
[data-route="point-of-sale"] .pb-16 { padding-bottom: 4rem; }
[data-route="point-of-sale"] .w-full { width: 100%; }
[data-route="point-of-sale"] .h-full { height: 100%; }
[data-route="point-of-sale"] .w-quarter { width: 25%; }
[data-route="point-of-sale"] .w-half { width: 50%; }
[data-route="point-of-sale"] .w-66 { width: 66.66%; }
[data-route="point-of-sale"] .w-33 { width: 33.33%; }
[data-route="point-of-sale"] .w-60 { width: 60%; }
[data-route="point-of-sale"] .w-40 { width: 40%; }
[data-route="point-of-sale"] .w-fit { width: fit-content; }
[data-route="point-of-sale"] .w-6 { width: 2rem; }
[data-route="point-of-sale"] .h-6 { min-height: 2rem; height: 2rem; }
[data-route="point-of-sale"] .w-8 { width: 2.5rem; }
[data-route="point-of-sale"] .h-8 { min-height: 2.5rem; height: 2.5rem; }
[data-route="point-of-sale"] .w-10 { width: 3rem; }
[data-route="point-of-sale"] .h-10 { min-height:3rem; height: 3rem; }
[data-route="point-of-sale"] .h-12 { min-height: 3.3rem; height: 3.3rem; }
[data-route="point-of-sale"] .w-12 { width: 3.3rem; }
[data-route="point-of-sale"] .h-14 { min-height: 4.2rem; height: 4.2rem; }
[data-route="point-of-sale"] .h-16 { min-height: 4.6rem; height: 4.6rem; }
[data-route="point-of-sale"] .h-18 { min-height: 5rem; height: 5rem; }
[data-route="point-of-sale"] .w-18 { width: 5.4rem; }
[data-route="point-of-sale"] .w-24 { width: 7.2rem; }
[data-route="point-of-sale"] .w-26 { width: 8.4rem; }
[data-route="point-of-sale"] .h-24 { min-height: 7.2rem; height: 7.2rem; }
[data-route="point-of-sale"] .h-32 { min-height: 9.6rem; height: 9.6rem; }
[data-route="point-of-sale"] .w-46 { width: 15rem; }
[data-route="point-of-sale"] .h-46 { min-height:15rem; height: 15rem; }
[data-route="point-of-sale"] .h-100 { height: 100vh; }
[data-route="point-of-sale"] .mx-h-70 { max-height: 67rem; }
[data-route="point-of-sale"] .border-grey-300 { border-color: #e2e8f0; }
[data-route="point-of-sale"] .border-grey { border: 1px solid #d1d8dd; }
[data-route="point-of-sale"] .border-white { border: 1px solid #fff; }
[data-route="point-of-sale"] .border-b-grey { border-bottom: 1px solid #d1d8dd; }
[data-route="point-of-sale"] .border-t-grey { border-top: 1px solid #d1d8dd; }
[data-route="point-of-sale"] .border-r-grey { border-right: 1px solid #d1d8dd; }
[data-route="point-of-sale"] .text-dark-grey { color: #5f5f5f; }
[data-route="point-of-sale"] .text-grey { color: #8d99a6; }
[data-route="point-of-sale"] .text-grey-100 { color: #d1d8dd; }
[data-route="point-of-sale"] .text-grey-200 { color: #a0aec0; }
[data-route="point-of-sale"] .bg-green-200 { background-color: #c6f6d5; }
[data-route="point-of-sale"] .text-bold { font-weight: bold; }
[data-route="point-of-sale"] .italic { font-style: italic; }
[data-route="point-of-sale"] .font-weight-450 { font-weight: 450; }
[data-route="point-of-sale"] .justify-around { justify-content: space-around; }
[data-route="point-of-sale"] .justify-between { justify-content: space-between; }
[data-route="point-of-sale"] .justify-center { justify-content: center; }
[data-route="point-of-sale"] .justify-end { justify-content: flex-end; }
[data-route="point-of-sale"] .bg-white { background-color: white; }
[data-route="point-of-sale"] .bg-light-grey { background-color: #f0f4f7; }
[data-route="point-of-sale"] .bg-grey-100 { background-color: #f7fafc; }
[data-route="point-of-sale"] .bg-grey-200 { background-color: #edf2f7; }
[data-route="point-of-sale"] .bg-grey { background-color: #f4f5f6; }
[data-route="point-of-sale"] .text-center { text-align: center; }
[data-route="point-of-sale"] .text-right { text-align: right; }
[data-route="point-of-sale"] .text-sm { font-size: 1rem; }
[data-route="point-of-sale"] .text-md-0 { font-size: 1.25rem; }
[data-route="point-of-sale"] .text-md { font-size: 1.4rem; }
[data-route="point-of-sale"] .text-lg { font-size: 1.6rem; }
[data-route="point-of-sale"] .text-xl { font-size: 2.2rem; }
[data-route="point-of-sale"] .text-2xl { font-size: 2.8rem; }
[data-route="point-of-sale"] .text-2-5xl { font-size: 3rem; }
[data-route="point-of-sale"] .text-3xl { font-size: 3.8rem; }
[data-route="point-of-sale"] .text-6xl { font-size: 4.8rem; }
[data-route="point-of-sale"] .line-through { text-decoration: line-through; }
[data-route="point-of-sale"] .text-primary { color: #5e64ff; }
[data-route="point-of-sale"] .text-white { color: #fff; }
[data-route="point-of-sale"] .text-green-500 { color: #48bb78; }
[data-route="point-of-sale"] .bg-primary { background-color: #5e64ff; }
[data-route="point-of-sale"] .border-primary { border-color: #5e64ff; }
[data-route="point-of-sale"] .text-danger { color: #e53e3e; }
[data-route="point-of-sale"] .scroll-x { overflow-x: scroll;overflow-y: hidden; }
[data-route="point-of-sale"] .scroll-y { overflow-y: scroll;overflow-x: hidden; }
[data-route="point-of-sale"] .overflow-hidden { overflow: hidden; }
[data-route="point-of-sale"] .whitespace-nowrap { white-space: nowrap; }
[data-route="point-of-sale"] .sticky { position: sticky; top: -1px; }
[data-route="point-of-sale"] .bg-white { background-color: #fff; }
[data-route="point-of-sale"] .bg-selected { background-color: #fffdf4; }
[data-route="point-of-sale"] .border-dashed { border-width:1px; border-style: dashed; }
[data-route="point-of-sale"] .z-100 { z-index: 100; }
[data-route="point-of-sale"] .frappe-control { margin: 0 !important; width: 100%; }
[data-route="point-of-sale"] .form-control { font-size: 12px; }
[data-route="point-of-sale"] .form-group { margin: 0 !important; }
[data-route="point-of-sale"] .pointer { cursor: pointer; }
[data-route="point-of-sale"] .no-select { user-select: none; }
[data-route="point-of-sale"] .item-wrapper:hover { transform: scale(1.02, 1.02); }
[data-route="point-of-sale"] .hover-underline:hover { text-decoration: underline; }
[data-route="point-of-sale"] .item-wrapper { transition: scale 0.2s ease-in-out; }
[data-route="point-of-sale"] .cart-items-section .cart-item-wrapper:not(:first-child) { border-top: none; }
[data-route="point-of-sale"] .customer-transactions .invoice-wrapper:not(:first-child) { border-top: none; }
[data-route="point-of-sale"] .payment-summary-wrapper:last-child { border-bottom: none; }
[data-route="point-of-sale"] .item-summary-wrapper:last-child { border-bottom: none; }
[data-route="point-of-sale"] .total-summary-wrapper:last-child { border-bottom: none; }
[data-route="point-of-sale"] .invoices-container .invoice-wrapper:last-child { border-bottom: none; }
[data-route="point-of-sale"] .new-btn { background-color: #5e64ff; color: white; border: none;}
[data-route="point-of-sale"] .summary-btns:last-child { margin-right: 0px; }
[data-route="point-of-sale"] ::-webkit-scrollbar { width: 1px }
[data-route="point-of-sale"] .indicator.grey::before { background-color: #8d99a6; }

View File

@ -85,7 +85,7 @@ class CallPopup {
<br>
<a
class="text-small text-muted"
href="#Form/Call Log/${this.call_log.name}">
href="/desk/Form/Call Log/${this.call_log.name}">
${__('View call log')}
</a>
`,
@ -167,7 +167,7 @@ class CallPopup {
const issue_field = this.dialog.get_field("last_issue");
issue_field.set_value(issue.subject);
issue_field.$wrapper.append(`
<a class="text-medium" href="#List/Issue?customer=${issue.customer}">
<a class="text-medium" href="/desk/List/Issue?customer=${issue.customer}">
${__('View all issues from {0}', [issue.customer])}
</a>
`);

View File

@ -84,7 +84,7 @@ frappe.ui.form.on("Communication", {
frm.reload_doc();
frappe.show_alert({
message: __("Opportunity {0} created",
['<a href="#Form/Opportunity/'+r.message+'">' + r.message + '</a>']),
['<a href="/desk/Form/Opportunity/'+r.message+'">' + r.message + '</a>']),
indicator: 'green'
});
}

View File

@ -19,7 +19,7 @@
</thead>
<tbody>
{% for s in students %}
<tr
<tr
{% if(s.assessment_details && s.docstatus && s.docstatus == 1) { %} class="text-muted" {% } %}
data-student="{{s.student}}">
@ -29,7 +29,7 @@
<td>
<span data-student="{{s.student}}" data-criteria="{{c.assessment_criteria}}" class="student-result-grade badge" >
{% if(s.assessment_details) { %}
{{s.assessment_details[c.assessment_criteria][1]}}
{{s.assessment_details[c.assessment_criteria][1]}}
{% } %}
</span>
<input type="number" class="student-result-data" style="width:70%; float:right;"
@ -61,7 +61,7 @@
{% } %}
</span>
<span data-student="{{s.student}}" class="total-result-link" style="width: 10%; display:{% if(!s.assessment_details) { %}None{% } %}; float:right;">
<a class="btn-open no-decoration" title="Open Link" href="#Form/Assessment Result/{% if(s.assessment_details) { %}{{s.name}}{% } %}">
<a class="btn-open no-decoration" title="Open Link" href="/app/Form/Assessment Result/{% if(s.assessment_details) { %}{{s.name}}{% } %}">
<i class="octicon octicon-arrow-right"></i>
</a>
</span>

View File

@ -1,11 +0,0 @@
<div class="row pos-payment-row" type="{{type}}" idx={{idx}}>
<div class="col-xs-6" style="padding:20px">{{mode_of_payment}}</div>
<div class="col-xs-6">
<div class="input-group">
<input disabled class="form-control text-right amount" idx="{{idx}}" type="text" value="{%= format_currency(amount, currency) %}">
<span class="input-group-btn">
<button type="button" class="btn btn-default clr" idx="{{idx}}" style="border:1px solid #d1d8dd">C</button>
</span>
</div>
</div>
</div>

View File

@ -1,42 +0,0 @@
<div class="pos_payment row">
<div class="row" style="padding: 0px 30px;">
<h3>{{ __("Total Amount") }}: <span class="label label-default" style="font-size:20px;padding:5px">{%= format_currency(grand_total, currency) %}</span></h3>
</div>
<div class="row amount-row">
<div class="col-xs-6 col-sm-3 text-center">
<p class="amount-label"> {{ __("Paid") }} <h3 class="paid_amount">{%= format_currency(paid_amount, currency) %}</h3></p>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<p class="amount-label"> {{ __("Outstanding") }} <h3 class="outstanding_amount">{%= format_currency(outstanding_amount, currency) %} </h3></p>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<p class="amount-label"> {{ __("Change") }} <input class="form-control text-right change_amount bold" type="text" idx="change_amount" value="{{format_number(change_amount, null, 2)}}">
</p>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<p class="amount-label"> {{ __("Write off") }} <input class="form-control text-right write_off_amount bold" type="text" idx="write_off_amount" value="{{format_number(write_off_amount, null, 2)}}">
</p>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-6 ">
<div class ="row multimode-payments" style = "margin-right:10px">
</div>
</div>
<div class="col-sm-6 payment-toolbar">
{% for(var i=0; i<3; i++) { %}
<div class="row">
{% for(var j=i*3; j<(i+1)*3; j++) { %}
<button type="button" class="btn btn-default pos-keyboard-key">{{j+1}}</button>
{% } %}
</div>
{% } %}
<div class="row">
<button type="button" class="btn btn-default delete-btn">{{ __("Del") }}</button>
<button type="button" class="btn btn-default pos-keyboard-key">0</button>
<button type="button" class="btn btn-default pos-keyboard-key">.</button>
</div>
</div>
</div>
</div>

View File

@ -1,330 +0,0 @@
/* eslint-disable */
/*! Clusterize.js - v0.17.6 - 2017-03-05
* http://NeXTs.github.com/Clusterize.js/
* Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
;(function(name, definition) {
if (typeof module != 'undefined') module.exports = definition();
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
else this[name] = definition();
}('Clusterize', function() {
"use strict"
// detect ie9 and lower
// https://gist.github.com/padolsey/527683#comment-786682
var ie = (function(){
for( var v = 3,
el = document.createElement('b'),
all = el.all || [];
el.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
all[0];
){}
return v > 4 ? v : document.documentMode;
}()),
is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1;
var Clusterize = function(data) {
if( ! (this instanceof Clusterize))
return new Clusterize(data);
var self = this;
var defaults = {
rows_in_block: 50,
blocks_in_cluster: 4,
tag: null,
show_no_data_row: true,
no_data_class: 'clusterize-no-data',
no_data_text: 'No data',
keep_parity: true,
callbacks: {}
}
// public parameters
self.options = {};
var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
for(var i = 0, option; option = options[i]; i++) {
self.options[option] = typeof data[option] != 'undefined' && data[option] != null
? data[option]
: defaults[option];
}
var elems = ['scroll', 'content'];
for(var i = 0, elem; elem = elems[i]; i++) {
self[elem + '_elem'] = data[elem + 'Id']
? document.getElementById(data[elem + 'Id'])
: data[elem + 'Elem'];
if( ! self[elem + '_elem'])
throw new Error("Error! Could not find " + elem + " element");
}
// tabindex forces the browser to keep focus on the scrolling list, fixes #11
if( ! self.content_elem.hasAttribute('tabindex'))
self.content_elem.setAttribute('tabindex', 0);
// private parameters
var rows = isArray(data.rows)
? data.rows
: self.fetchMarkup(),
cache = {},
scroll_top = self.scroll_elem.scrollTop;
// append initial data
self.insertToDOM(rows, cache);
// restore the scroll position
self.scroll_elem.scrollTop = scroll_top;
// adding scroll handler
var last_cluster = false,
scroll_debounce = 0,
pointer_events_set = false,
scrollEv = function() {
// fixes scrolling issue on Mac #3
if (is_mac) {
if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
pointer_events_set = true;
clearTimeout(scroll_debounce);
scroll_debounce = setTimeout(function () {
self.content_elem.style.pointerEvents = 'auto';
pointer_events_set = false;
}, 50);
}
if (last_cluster != (last_cluster = self.getClusterNum()))
self.insertToDOM(rows, cache);
if (self.options.callbacks.scrollingProgress)
self.options.callbacks.scrollingProgress(self.getScrollProgress());
},
resize_debounce = 0,
resizeEv = function() {
clearTimeout(resize_debounce);
resize_debounce = setTimeout(self.refresh, 100);
}
on('scroll', self.scroll_elem, scrollEv);
on('resize', window, resizeEv);
// public methods
self.destroy = function(clean) {
off('scroll', self.scroll_elem, scrollEv);
off('resize', window, resizeEv);
self.html((clean ? self.generateEmptyRow() : rows).join(''));
}
self.refresh = function(force) {
if(self.getRowsHeight(rows) || force) self.update(rows);
}
self.update = function(new_rows) {
rows = isArray(new_rows)
? new_rows
: [];
var scroll_top = self.scroll_elem.scrollTop;
// fixes #39
if(rows.length * self.options.item_height < scroll_top) {
self.scroll_elem.scrollTop = 0;
last_cluster = 0;
}
self.insertToDOM(rows, cache);
self.scroll_elem.scrollTop = scroll_top;
}
self.clear = function() {
self.update([]);
}
self.getRowsAmount = function() {
return rows.length;
}
self.getScrollProgress = function() {
return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
}
var add = function(where, _new_rows) {
var new_rows = isArray(_new_rows)
? _new_rows
: [];
if( ! new_rows.length) return;
rows = where == 'append'
? rows.concat(new_rows)
: new_rows.concat(rows);
self.insertToDOM(rows, cache);
}
self.append = function(rows) {
add('append', rows);
}
self.prepend = function(rows) {
add('prepend', rows);
}
}
Clusterize.prototype = {
constructor: Clusterize,
// fetch existing markup
fetchMarkup: function() {
var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
while (rows_nodes.length) {
rows.push(rows_nodes.shift().outerHTML);
}
return rows;
},
// get tag name, content tag name, tag height, calc cluster height
exploreEnvironment: function(rows, cache) {
var opts = this.options;
opts.content_tag = this.content_elem.tagName.toLowerCase();
if( ! rows.length) return;
if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]);
if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
this.getRowsHeight(rows);
},
getRowsHeight: function(rows) {
var opts = this.options,
prev_item_height = opts.item_height;
opts.cluster_height = 0;
if( ! rows.length) return;
var nodes = this.content_elem.children;
var node = nodes[Math.floor(nodes.length / 2)];
opts.item_height = node.offsetHeight;
// consider table's border-spacing
if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
// consider margins (and margins collapsing)
if(opts.tag != 'tr') {
var marginTop = parseInt(getStyle('marginTop', node), 10) || 0;
var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0;
opts.item_height += Math.max(marginTop, marginBottom);
}
opts.block_height = opts.item_height * opts.rows_in_block;
opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
return prev_item_height != opts.item_height;
},
// get current cluster number
getClusterNum: function () {
this.options.scroll_top = this.scroll_elem.scrollTop;
return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
},
// generate empty row if no data provided
generateEmptyRow: function() {
var opts = this.options;
if( ! opts.tag || ! opts.show_no_data_row) return [];
var empty_row = document.createElement(opts.tag),
no_data_content = document.createTextNode(opts.no_data_text), td;
empty_row.className = opts.no_data_class;
if(opts.tag == 'tr') {
td = document.createElement('td');
// fixes #53
td.colSpan = 100;
td.appendChild(no_data_content);
}
empty_row.appendChild(td || no_data_content);
return [empty_row.outerHTML];
},
// generate cluster for current scroll position
generate: function (rows, cluster_num) {
var opts = this.options,
rows_len = rows.length;
if (rows_len < opts.rows_in_block) {
return {
top_offset: 0,
bottom_offset: 0,
rows_above: 0,
rows: rows_len ? rows : this.generateEmptyRow()
}
}
var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
items_end = items_start + opts.rows_in_cluster,
top_offset = Math.max(items_start * opts.item_height, 0),
bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
this_cluster_rows = [],
rows_above = items_start;
if(top_offset < 1) {
rows_above++;
}
for (var i = items_start; i < items_end; i++) {
rows[i] && this_cluster_rows.push(rows[i]);
}
return {
top_offset: top_offset,
bottom_offset: bottom_offset,
rows_above: rows_above,
rows: this_cluster_rows
}
},
renderExtraTag: function(class_name, height) {
var tag = document.createElement(this.options.tag),
clusterize_prefix = 'clusterize-';
tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
height && (tag.style.height = height + 'px');
return tag.outerHTML;
},
// if necessary verify data changed and insert to DOM
insertToDOM: function(rows, cache) {
// explore row's height
if( ! this.options.cluster_height) {
this.exploreEnvironment(rows, cache);
}
var data = this.generate(rows, this.getClusterNum()),
this_cluster_rows = data.rows.join(''),
this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache),
top_offset_changed = this.checkChanges('top', data.top_offset, cache),
only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache),
callbacks = this.options.callbacks,
layout = [];
if(this_cluster_content_changed || top_offset_changed) {
if(data.top_offset) {
this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity'));
layout.push(this.renderExtraTag('top-space', data.top_offset));
}
layout.push(this_cluster_rows);
data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset));
callbacks.clusterWillChange && callbacks.clusterWillChange();
this.html(layout.join(''));
this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
callbacks.clusterChanged && callbacks.clusterChanged();
} else if(only_bottom_offset_changed) {
this.content_elem.lastChild.style.height = data.bottom_offset + 'px';
}
},
// unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround
html: function(data) {
var content_elem = this.content_elem;
if(ie && ie <= 9 && this.options.tag == 'tr') {
var div = document.createElement('div'), last;
div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
while((last = content_elem.lastChild)) {
content_elem.removeChild(last);
}
var rows_nodes = this.getChildNodes(div.firstChild.firstChild);
while (rows_nodes.length) {
content_elem.appendChild(rows_nodes.shift());
}
} else {
content_elem.innerHTML = data;
}
},
getChildNodes: function(tag) {
var child_nodes = tag.children, nodes = [];
for (var i = 0, ii = child_nodes.length; i < ii; i++) {
nodes.push(child_nodes[i]);
}
return nodes;
},
checkChanges: function(type, value, cache) {
var changed = value != cache[type];
cache[type] = value;
return changed;
}
}
// support functions
function on(evt, element, fnc) {
return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
}
function off(evt, element, fnc) {
return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function getStyle(prop, elem) {
return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
}
return Clusterize;
}));

View File

@ -1,16 +0,0 @@
<div class="pos-bill-toolbar col-xs-9" style="display: flex; width: 70%;">
<div class="party-area" style="flex: 1;">
<span class="edit-customer-btn text-muted" style="display: inline;">
<a class="btn-open no-decoration" title="Edit Customer">
<i class="octicon octicon-pencil"></i>
</a>
</span>
</div>
<button class="btn btn-default list-customers-btn" style="margin-left: 12px">
<i class="octicon octicon-organization"></i>
</button>
</button> {% if (allow_delete) { %}
<button class="btn btn-default btn-danger" style="margin: 0 5px 0 5px">
<i class="octicon octicon-trashcan"></i>
</button> {% } %}
</div>

View File

@ -1,136 +0,0 @@
<div class="pos">
<div class="row">
<div class="col-sm-5 pos-bill-wrapper">
<div class="col-sm-12"><h6 class="form-section-heading uppercase">{{ __("Item Cart") }}</h6></div>
<div class="pos-bill">
<div class="item-cart">
<div class="pos-list-row pos-bill-header text-muted h6">
<span class="cell subject">
<!--<input class="list-select-all" type="checkbox" title="{%= __("Select All") %}">-->
{{ __("Item Name")}}
</span>
<span class="cell text-right">{{ __("Quantity") }}</span>
<span class="cell text-right">{{ __("Discount") }}</span>
<span class="cell text-right">{{ __("Rate") }}</span>
</div>
<div class="item-cart-items">
<div class="no-items-message text-extra-muted">
<span class="text-center">
<i class="fa fa-2x fa-shopping-cart"></i>
<p>{{ __("Tap items to add them here") }}</p>
</span>
</div>
<div class="items">
</div>
</div>
</div>
</div>
<div class="totals-area">
<div class="pos-list-row net-total-area">
<div class="cell"></div>
<div class="cell text-right">{%= __("Net Total") %}</div>
<div class="cell price-cell bold net-total text-right"></div>
</div>
<div class="pos-list-row tax-area">
<div class="cell"></div>
<div class="cell text-right">{%= __("Taxes") %}</div>
<div class="cell price-cell text-right tax-table">
</div>
</div>
{% if(allow_user_to_edit_discount) { %}
<div class="pos-list-row discount-amount-area">
<div class="cell"></div>
<div class="cell text-right">{%= __("Discount") %}</div>
<div class="cell price-cell discount-field-col">
<div class="input-group input-group-sm">
<span class="input-group-addon">%</span>
<input type="text" class="form-control discount-percentage text-right">
</div>
<div class="input-group input-group-sm">
<span class="input-group-addon">{%= get_currency_symbol(currency) %}</span>
<input type="text" class="form-control discount-amount text-right" placeholder="{%= 0.00 %}">
</div>
</div>
</div>
{% } %}
<div class="pos-list-row grand-total-area collapse-btn" style="border-bottom:1px solid #d1d8dd;">
<div class="cell">
<a class="">
<i class="octicon octicon-chevron-down"></i>
</a>
</div>
<div class="cell text-right bold">{%= __("Grand Total") %}</div>
<div class="cell price-cell grand-total text-right lead"></div>
</div>
<div class="pos-list-row qty-total-area collapse-btn" style="border-bottom:1px solid #d1d8dd;">
<div class="cell">
<a class="">
<i class="octicon octicon-chevron-down"></i>
</a>
</div>
<div class="cell text-right bold">{%= __("Qty Total") %}</div>
<div class="cell price-cell qty-total text-right lead"></div>
</div>
</div>
<div class="row" style="margin-top: 30px">
<div class="col-sm-6 selected-item">
</div>
<div class="col-xs-6 numeric_keypad hidden-xs" style="display:none">
{% var chartData = ["Qty", "Disc", "Price"] %} {% for(var i=0; i
<3; i++) { %} <div class="row text-right">
{% for(var j=i*3; j
<(i+1)*3; j++) { %} <button type="button" class="btn btn-default numeric-keypad" val="{{j+1}}">{{j+1}}</button>
{% } %}
<button type="button" {% if((!allow_user_to_edit_rate && __(chartData[i]) == __("Price")) || (!allow_user_to_edit_discount && __(chartData[i]) == __("Disc"))) { %} disabled {% } %} id="pos-item-{{ chartData[i].toLowerCase() }}" class="btn text-center btn-default numeric-keypad pos-operation">{{ __(chartData[i]) }}</button>
</div>
{% } %}
<div class="row text-right">
<button type="button" class="btn btn-default numeric-keypad numeric-del">{{ __("Del") }}</button>
<button type="button" class="btn btn-default numeric-keypad" val="0">0</button>
<button type="button" class="btn btn-default numeric-keypad" val=".">.</button>
<button type="button" class="btn btn-primary numeric-keypad pos-pay">{{ __("Pay") }}</button>
</div>
</div>
</div>
</div>
<div class="col-sm-5 list-customers">
<div class="col-sm-12"><h6 class="form-section-heading uppercase">{{ __("Customers in Queue") }}</h6></div>
<div class="pos-list-row pos-bill-header">
<div class="cell subject"><input class="list-select-all" type="checkbox">{{ __("Customer") }}</div>
<div class="cell text-left">{{ __("Status") }}</div>
<div class="cell text-right">{{ __("Amount") }}</div>
<div class="cell text-right">{{ __("Grand Total") }}</div>
</div>
<div class="list-customers-table border-left border-right border-bottom">
<div class="no-items-message text-extra-muted">
<span class="text-center">
<i class="fa fa-2x fa-user"></i>
<p>{{ __("No Customers yet!") }}</p>
</span>
</div>
</div>
</div>
<div class="col-sm-7 pos-items-section">
<div class="col-sm-12"><h6 class="form-section-heading uppercase">{{ __("Stock Items") }}</h6></div>
<div class="row pos-item-area">
</div>
<span id="customer-results" style="color:#68a;"></span>
<div class="item-list-area">
<div class="pos-list-row pos-bill-header text-muted h6">
<div class="cell subject search-item-group">
<div class="dropdown">
<a class="text-muted dropdown-toggle" data-toggle="dropdown"><span class="dropdown-text">{{ __("All Item Groups") }}</span><i class="caret"></i></a>
<ul class="dropdown-menu">
</ul>
</div>
</div>
<div class="cell search-item"></div>
</div>
<div class="app-listing item-list image-view-container">
</div>
</div>
</div>
</div>

View File

@ -1,34 +0,0 @@
<div class="row pos-bill-row pos-bill-item" data-item-code="{%= item_code %}">
<div class="col-xs-4"><h6>{%= item_code || "" %}{%= __(item_name) || "" %}</h6></div>
<div class="col-xs-3">
<div class="row pos-qty-row">
<div class="col-xs-2 text-center pos-qty-btn" data-action="decrease-qty"><i class="fa fa-minus text-muted" style="font-size:12px"></i></div>
<div class="col-xs-8">
<div>
<input type="tel" value="{%= qty %}" class="form-control pos-item-qty text-right">
</div>
{% if(actual_qty != null) { %}
<div style="margin-top: 5px;" class="text-muted small text-right">
{%= __("In Stock: ") %} <span>{%= actual_qty || 0.0 %}</span>
</div>
{% } %}
</div>
<div class="col-xs-2 text-center pos-qty-btn" data-action="increase-qty"><i class="fa fa-plus text-muted" style="font-size:12px"></i></div>
</div>
</div>
<div class="col-xs-2 text-right">
<div class="row input-sm">
<input type="tel" value="{%= discount_percentage %}" class="form-control text-right pos-item-disc">
</div>
</div>
<div class="col-xs-3 text-right">
<div class="text-muted" style="margin-top: 5px;">
{% if(enabled) { %}
<input type="tel" value="{%= rate %}" class="form-control input-sm pos-item-price text-right">
{% } else { %}
<h6>{%= format_currency(rate) %}</h6>
{% } %}
</div>
<p><h6>{%= amount %}</h6></p>
</div>
</div>

View File

@ -1,9 +0,0 @@
<div class="pos-list-row pos-bill-item {{ selected_class }}" data-item-code="{{ item_code }}">
<div class="cell subject">
<!--<input class="list-row-checkbox" type="checkbox" data-name="{{item_code}}">-->
<a class="grey list-id" title="{{ item_name }}">{{ strip_html(__(item_name)) || item_code }}</a>
</div>
<div class="cell text-right">{%= qty %}</div>
<div class="cell text-right">{%= discount_percentage %}</div>
<div class="cell text-right">{%= format_currency(rate) %}</div>
</div>

View File

@ -1,9 +0,0 @@
<div class="pos-list-row" invoice-name = "{{name}}">
<div class="list-column cell subject" invoice-name = "{{name}}">
<input class="list-delete text-left" type="checkbox" style = "margin-right:5px">
<a class="grey list-id text-left customer-row" title="{{ customer }}">{%= customer %}</a>
</div>
<div class="list-column cell text-left customer-row"><span class="indicator {{data.indicator}}">{{ data.status }}</span></div>
<div class="list-column cell text-right customer-row">{%= paid_amount %}</div>
<div class="list-column cell text-right customer-row">{%= grand_total %}</div>
</div>

View File

@ -1,32 +0,0 @@
<div class="pos-item-wrapper image-view-item" data-item-code="{{item_code}}">
<div class="image-view-header doclist-row">
<div class="list-value">
<a class="grey list-id" data-name="{{item_code}}" title="{{ item_name || item_code}}">{{item_name || item_code}}<br>({{ __(item_stock) }})</a>
</div>
</div>
<div class="image-view-body">
<a data-item-code="{{ item_code }}"
title="{{ item_name || item_code }}"
>
<div class="image-field"
style="
{% if (!item_image) { %}
background-color: #fafbfc;
{% } %}
border: 0px;"
>
{% if (!item_image) { %}
<span class="placeholder-text">
{%= frappe.get_abbr(item_name || item_code) %}
</span>
{% } %}
{% if (item_image) { %}
<img src="{{ item_image }}" alt="{{item_name || item_code}}">
{% } %}
</div>
<span class="price-info">
{{item_price}} / {{item_uom}}
</span>
</a>
</div>
</div>

View File

@ -1,22 +0,0 @@
<div class="pos-selected-item-action" data-item-code="{%= item_code %}" data-idx="{%= idx %}">
<div class="pos-list-row">
<div class="cell">{{ __("Quantity") }}:</div>
<input type="tel" class="form-control cell pos-item-qty" value="{%= qty %}"/>
</div>
<div class="pos-list-row">
<div class="cell">{{ __("Price List Rate") }}:</div>
<input type="tel" class="form-control cell" disabled value="{%= price_list_rate %}"/>
</div>
<div class="pos-list-row">
<div class="cell">{{ __("Discount") }}: %</div>
<input type="tel" class="form-control cell pos-item-disc" {% if !allow_user_to_edit_discount %} disabled {% endif %} value="{%= discount_percentage %}">
</div>
<div class="pos-list-row">
<div class="cell">{{ __("Price") }}:</div>
<input type="tel" class="form-control cell pos-item-price" {% if !allow_user_to_edit_rate %} disabled {% endif %} value="{%= rate %}"/>
</div>
<div class="pos-list-row">
<div class="cell">{{ __("Amount") }}:</div>
<input type="tel" class="form-control cell pos-amount" disabled value="{%= amount %}"/>
</div>
</div>

View File

@ -1,4 +0,0 @@
<div class="pos-list-row" style="padding-right: 0;">
<div class="cell">{%= description %}</div>
<div class="cell text-right bold">{%= tax_amount %}</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -328,7 +328,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
frappe.msgprint({
message: __('Work Orders Created: {0}',
[r.message.map(function(d) {
return repl('<a href="#Form/Work Order/%(name)s">%(name)s</a>', {name:d})
return repl('<a href="/desk/Form/Work Order/%(name)s">%(name)s</a>', {name:d})
}).join(', ')]),
indicator: 'green'
})
@ -437,7 +437,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
callback: function(r) {
if(r.message) {
frappe.msgprint(__('Material Request {0} submitted.',
['<a href="#Form/Material Request/'+r.message.name+'">' + r.message.name+ '</a>']));
['<a href="/desk/Form/Material Request/'+r.message.name+'">' + r.message.name+ '</a>']));
}
d.hide();
me.frm.reload_doc();

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,4 @@
/* global Clusterize */
frappe.provide('erpnext.PointOfSale');
{% include "erpnext/selling/page/point_of_sale/pos_controller.js" %}
frappe.provide('erpnext.queries');
frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
@ -10,8 +7,10 @@ frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
single_column: true
});
wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
window.cur_pos = wrapper.pos;
frappe.require('assets/js/point-of-sale.min.js', function() {
wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
window.cur_pos = wrapper.pos;
})
};
frappe.pages['point-of-sale'].refresh = function(wrapper) {

View File

@ -1,23 +1,9 @@
{% include "erpnext/selling/page/point_of_sale/onscan.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_item_selector.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_item_cart.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_item_details.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_payment.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_past_order_list.js" %}
{% include "erpnext/selling/page/point_of_sale/pos_past_order_summary.js" %}
erpnext.PointOfSale.Controller = class {
constructor(wrapper) {
this.wrapper = $(wrapper).find('.layout-main-section');
this.page = wrapper.page;
this.load_assets();
}
load_assets() {
// after loading assets first check if opening entry has been made
frappe.require(['assets/erpnext/css/pos.css'], this.check_opening_entry.bind(this));
this.check_opening_entry();
}
fetch_opening_entry() {
@ -36,6 +22,7 @@ erpnext.PointOfSale.Controller = class {
}
create_opening_voucher() {
const me = this;
const table_fields = [
{
fieldname: "mode_of_payment", fieldtype: "Link",
@ -45,7 +32,7 @@ erpnext.PointOfSale.Controller = class {
{
fieldname: "opening_amount", fieldtype: "Currency",
in_list_view: 1, label: "Opening Amount",
options: "company:company_currency",
options: "company:company_currency",
change: function () {
dialog.fields_dict.balance_details.df.data.some(d => {
if (d.idx == this.doc.idx) {
@ -93,7 +80,7 @@ erpnext.PointOfSale.Controller = class {
fields: table_fields
}
],
primary_action: async ({ company, pos_profile, balance_details }) => {
primary_action: async function({ company, pos_profile, balance_details }) {
if (!balance_details.length) {
frappe.show_alert({
message: __("Please add Mode of payments and opening balance details."),
@ -103,7 +90,7 @@ erpnext.PointOfSale.Controller = class {
}
const method = "erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher";
const res = await frappe.call({ method, args: { pos_profile, company, balance_details }, freeze:true });
!res.exc && this.prepare_app_defaults(res.message);
!res.exc && me.prepare_app_defaults(res.message);
dialog.hide();
},
primary_action_label: __('Submit')
@ -134,7 +121,7 @@ erpnext.PointOfSale.Controller = class {
set_opening_entry_status() {
this.page.set_title_sub(
`<span class="indicator orange">
<a class="text-muted" href="#Form/POS%20Opening%20Entry/${this.pos_opening}">
<a class="text-muted" href="/desk/Form/POS%20Opening%20Entry/${this.pos_opening}">
Opened at ${moment(this.pos_opening_time).format("Do MMMM, h:mma")}
</a>
</span>`);
@ -157,10 +144,10 @@ erpnext.PointOfSale.Controller = class {
prepare_dom() {
this.wrapper.append(
`<div class="app grid grid-cols-10 pt-8 gap-6"></div>`
`<div class="point-of-sale-app"></div>`
);
this.$components_wrapper = this.wrapper.find('.app');
this.$components_wrapper = this.wrapper.find('.point-of-sale-app');
}
prepare_components() {
@ -190,7 +177,7 @@ erpnext.PointOfSale.Controller = class {
}
toggle_recent_order() {
const show = this.recent_order_list.$component.hasClass('d-none');
const show = this.recent_order_list.$component.is(':hidden');
this.toggle_recent_order_list(show);
}
@ -199,7 +186,7 @@ erpnext.PointOfSale.Controller = class {
if (this.frm.doc.items.length == 0) {
frappe.show_alert({
message:__("You must add atleast one item to save it as draft."),
message:__("You must add atleast one item to save it as draft."),
indicator:'red'
});
frappe.utils.play_sound("error");
@ -208,7 +195,7 @@ erpnext.PointOfSale.Controller = class {
this.frm.save(undefined, undefined, undefined, () => {
frappe.show_alert({
message:__("There was an error saving the document."),
message:__("There was an error saving the document."),
indicator:'red'
});
frappe.utils.play_sound("error");
@ -256,7 +243,7 @@ erpnext.PointOfSale.Controller = class {
cart_item_clicked: (item_code, batch_no, uom) => {
const item_row = this.frm.doc.items.find(
i => i.item_code === item_code
i => i.item_code === item_code
&& i.uom === uom
&& (!batch_no || (batch_no && i.batch_no === batch_no))
);
@ -356,10 +343,10 @@ erpnext.PointOfSale.Controller = class {
toggle_other_sections: (show) => {
if (show) {
this.item_details.$component.hasClass('d-none') ? '' : this.item_details.$component.addClass('d-none');
this.item_selector.$component.addClass('d-none');
this.item_details.$component.is(':visible') ? this.item_details.$component.css('display', 'none') : '';
this.item_selector.$component.css('display', 'none');
} else {
this.item_selector.$component.removeClass('d-none');
this.item_selector.$component.css('display', 'flex');
}
},
@ -388,7 +375,7 @@ erpnext.PointOfSale.Controller = class {
this.order_summary.load_summary_of(doc);
});
},
reset_summary: () => this.order_summary.show_summary_placeholder()
reset_summary: () => this.order_summary.toggle_summary_placeholder(true)
}
})
}
@ -429,7 +416,7 @@ erpnext.PointOfSale.Controller = class {
})
}
toggle_recent_order_list(show) {
this.toggle_components(!show);
@ -539,7 +526,7 @@ erpnext.PointOfSale.Controller = class {
const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value;
await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse);
}
if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) {
await frappe.model.set_value(item_row.doctype, item_row.name, field, value);
this.update_cart_html(item_row);
@ -577,7 +564,7 @@ erpnext.PointOfSale.Controller = class {
this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
this.update_cart_html(item_row);
}
}
} catch (error) {
console.log(error);
} finally {
@ -588,7 +575,7 @@ erpnext.PointOfSale.Controller = class {
get_item_from_frm(item_code, batch_no, uom) {
const has_batch_no = batch_no;
return this.frm.doc.items.find(
i => i.item_code === item_code
i => i.item_code === item_code
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
&& (i.uom === uom)
);
@ -617,7 +604,7 @@ erpnext.PointOfSale.Controller = class {
const no_serial_selected = !item_row.serial_no;
const no_batch_selected = !item_row.batch_no;
if ((serialized && no_serial_selected) || (batched && no_batch_selected) ||
if ((serialized && no_serial_selected) || (batched && no_batch_selected) ||
(serialized && batched && (no_batch_selected || no_serial_selected))) {
return true;
}

View File

@ -16,10 +16,10 @@ erpnext.PointOfSale.ItemCart = class {
prepare_dom() {
this.wrapper.append(
`<section class="col-span-4 flex flex-col shadow rounded item-cart bg-white mx-h-70 h-100"></section>`
`<section class="customer-cart-container"></section>`
)
this.$component = this.wrapper.find('.item-cart');
this.$component = this.wrapper.find('.customer-cart-container');
}
init_child_components() {
@ -29,7 +29,7 @@ erpnext.PointOfSale.ItemCart = class {
init_customer_selector() {
this.$component.append(
`<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
`<div class="customer-section"></div>`
)
this.$customer_section = this.$component.find('.customer-section');
}
@ -37,23 +37,23 @@ erpnext.PointOfSale.ItemCart = class {
reset_customer_selector() {
const frm = this.events.get_frm();
frm.set_value('customer', '');
this.$customer_section.removeClass('border pr-4 pl-4');
this.make_customer_selector();
this.customer_field.set_focus();
}
init_cart_components() {
this.$component.append(
`<div class="cart-container flex flex-col items-center rounded flex-1 relative">
<div class="absolute flex flex-col p-8 pt-0 w-full h-full">
<div class="flex text-grey cart-header pt-2 pb-2 p-4 mt-2 mb-2 w-full f-shrink-0">
<div class="flex-1">Item</div>
<div class="mr-4">Qty</div>
<div class="rate-list-header mr-1 text-right">Amount</div>
`<div class="cart-container">
<div class="abs-cart-container">
<div class="cart-label">Item Cart</div>
<div class="cart-header">
<div class="name-header">Item</div>
<div class="qty-header">Qty</div>
<div class="rate-amount-header">Amount</div>
</div>
<div class="cart-items-section flex flex-col flex-1 scroll-y rounded w-full"></div>
<div class="cart-totals-section flex flex-col w-full mt-4 f-shrink-0"></div>
<div class="numpad-section flex flex-col mt-4 d-none w-full p-8 pt-0 pb-0 f-shrink-0"></div>
<div class="cart-items-section"></div>
<div class="cart-totals-section"></div>
<div class="numpad-section"></div>
</div>
</div>`
);
@ -72,50 +72,44 @@ erpnext.PointOfSale.ItemCart = class {
}
make_no_items_placeholder() {
this.$cart_header.addClass('d-none');
this.$cart_header.css('display', 'none');
this.$cart_items_wrapper.html(
`<div class="no-item-wrapper flex items-center h-18">
<div class="flex-1 text-center text-grey">No items in cart</div>
</div>`
)
this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed');
`<div class="no-item-wrapper">No items in cart</div>`
);
}
get_discount_icon() {
return (
`<svg class="discount-icon" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`
);
}
make_cart_totals_section() {
this.$totals_section = this.$component.find('.cart-totals-section');
this.$totals_section.append(
`<div class="add-discount flex items-center pt-4 pb-4 pr-4 pl-4 text-grey pointer no-select d-none">
+ Add Discount
`<div class="add-discount-wrapper">
${this.get_discount_icon()} Add Discount
</div>
<div class="border border-grey rounded">
<div class="net-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
<div class="flex flex-col">
<div class="text-md text-dark-grey text-bold">Net Total</div>
</div>
<div class="flex flex-col text-right">
<div class="text-md text-dark-grey text-bold">0.00</div>
</div>
</div>
<div class="taxes"></div>
<div class="grand-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
<div class="flex flex-col">
<div class="text-md text-dark-grey text-bold">Grand Total</div>
</div>
<div class="flex flex-col text-right">
<div class="text-md text-dark-grey text-bold">0.00</div>
</div>
</div>
<div class="checkout-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer rounded-b text-md text-bold">
Checkout
</div>
<div class="edit-cart-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer d-none text-md text-bold">
Edit Cart
</div>
</div>`
<div class="net-total-container">
<div class="net-total-label">Net Total</div>
<div class="net-total-value">0.00</div>
</div>
<div class="taxes-container"></div>
<div class="grand-total-container">
<div>Grand Total</div>
<div>0.00</div>
</div>
<div class="checkout-btn">Checkout</div>
<div class="edit-cart-btn">Edit Cart</div>`
)
this.$add_discount_elem = this.$component.find(".add-discount");
this.$add_discount_elem = this.$component.find(".add-discount-wrapper");
}
make_cart_numpad() {
@ -137,39 +131,37 @@ erpnext.PointOfSale.ItemCart = class {
[ '', '', '', 'col-span-2' ],
[ '', '', '', 'col-span-2' ],
[ '', '', '', 'col-span-2' ],
[ '', '', '', 'col-span-2 text-bold text-danger' ]
[ '', '', '', 'col-span-2 remove-btn' ]
],
fieldnames_map: { 'Quantity': 'qty', 'Discount': 'discount_percentage' }
})
this.$numpad_section.prepend(
`<div class="flex mb-2 justify-between">
`<div class="numpad-totals">
<span class="numpad-net-total"></span>
<span class="numpad-grand-total"></span>
</div>`
)
this.$numpad_section.append(
`<div class="numpad-btn checkout-btn flex items-center justify-center h-16 pr-8 pl-8 bg-primary
text-center text-white no-select pointer rounded text-md text-bold mt-4" data-button-value="checkout">
Checkout
</div>`
`<div class="numpad-btn checkout-btn" data-button-value="checkout">Checkout</div>`
)
}
bind_events() {
const me = this;
this.$customer_section.on('click', '.add-remove-customer', function (e) {
const customer_info_is_visible = me.$cart_container.hasClass('d-none');
customer_info_is_visible ?
me.toggle_customer_info(false) : me.reset_customer_selector();
this.$customer_section.on('click', '.reset-customer-btn', function (e) {
me.reset_customer_selector();
});
this.$customer_section.on('click', '.customer-header', function(e) {
// don't triggger the event if .add-remove-customer btn is clicked which is under .customer-header
if ($(e.target).closest('.add-remove-customer').length) return;
this.$customer_section.on('click', '.close-details-btn', function (e) {
me.toggle_customer_info(false);
});
const show = !me.$cart_container.hasClass('d-none');
this.$customer_section.on('click', '.customer-display', function(e) {
if ($(e.target).closest('.reset-customer-btn').length) return;
const show = me.$cart_container.is(':visible');
me.toggle_customer_info(show);
});
@ -178,7 +170,7 @@ erpnext.PointOfSale.ItemCart = class {
me.toggle_item_highlight(this);
const payment_section_hidden = me.$totals_section.find('.edit-cart-btn').hasClass('d-none');
const payment_section_hidden = !me.$totals_section.find('.edit-cart-btn').is(':visible');
if (!payment_section_hidden) {
// payment section is visible
// edit cart first and then open item details section
@ -193,23 +185,19 @@ erpnext.PointOfSale.ItemCart = class {
});
this.$component.on('click', '.checkout-btn', function() {
if (!$(this).hasClass('bg-primary')) return;
if ($(this).attr('style').indexOf('--blue-500') == -1) return;
me.events.checkout();
me.toggle_checkout_btn(false);
me.$add_discount_elem.removeClass("d-none");
});
this.$totals_section.on('click', '.edit-cart-btn', () => {
this.events.edit_cart();
this.toggle_checkout_btn(true);
this.$add_discount_elem.addClass("d-none");
});
this.$component.on('click', '.add-discount', () => {
const can_edit_discount = this.$add_discount_elem.find('.edit-discount').length;
this.$component.on('click', '.add-discount-wrapper', () => {
const can_edit_discount = this.$add_discount_elem.find('.edit-discount-btn').length;
if(!this.discount_field || can_edit_discount) this.show_discount_control();
});
@ -251,7 +239,7 @@ erpnext.PointOfSale.ItemCart = class {
frappe.ui.keys.add_shortcut({
shortcut: "ctrl+enter",
action: () => this.$component.find(".checkout-btn").click(),
condition: () => this.$component.is(":visible") && this.$totals_section.find('.edit-cart-btn').hasClass('d-none'),
condition: () => this.$component.is(":visible") && !this.$totals_section.find('.edit-cart-btn').is(':visible'),
description: __("Checkout Order / Submit Order / New Order"),
ignore_inputs: true,
page: cur_page.page.page
@ -259,14 +247,15 @@ erpnext.PointOfSale.ItemCart = class {
this.$component.find(".edit-cart-btn").attr("title", `${ctrl_label}+E`);
frappe.ui.keys.on("ctrl+e", () => {
const item_cart_visible = this.$component.is(":visible");
if (item_cart_visible && this.$totals_section.find('.checkout-btn').hasClass('d-none')) {
this.$component.find(".edit-cart-btn").click()
const checkout_btn_invisible = !this.$totals_section.find('.checkout-btn').is('visible');
if (item_cart_visible && checkout_btn_invisible) {
this.$component.find(".edit-cart-btn").click();
}
});
this.$component.find(".add-discount").attr("title", `${ctrl_label}+D`);
this.$component.find(".add-discount-wrapper").attr("title", `${ctrl_label}+D`);
frappe.ui.keys.add_shortcut({
shortcut: "ctrl+d",
action: () => this.$component.find(".add-discount").click(),
action: () => this.$component.find(".add-discount-wrapper").click(),
condition: () => this.$add_discount_elem.is(":visible"),
description: __("Add Order Discount"),
ignore_inputs: true,
@ -282,24 +271,22 @@ erpnext.PointOfSale.ItemCart = class {
toggle_item_highlight(item) {
const $cart_item = $(item);
const item_is_highlighted = $cart_item.hasClass("shadow");
const item_is_highlighted = $cart_item.attr("style") == "background-color:var(--gray-50);";
if (!item || item_is_highlighted) {
this.item_is_selected = false;
this.$cart_container.find('.cart-item-wrapper').removeClass("shadow").css("opacity", "1");
this.$cart_container.find('.cart-item-wrapper').css("background-color", "");
} else {
$cart_item.addClass("shadow");
$cart_item.css("background-color", "var(--gray-50)");
this.item_is_selected = true;
this.$cart_container.find('.cart-item-wrapper').css("opacity", "1");
this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow").css("opacity", "0.65");
this.$cart_container.find('.cart-item-wrapper').not(item).css("background-color", "");
}
// highlight with inner shadow
// $cart_item.addClass("shadow-inner bg-selected");
// me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-inner bg-selected");
}
make_customer_selector() {
this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
this.$customer_section.html(`
<div class="customer-field"></div>
`);
const me = this;
const query = { query: 'erpnext.controllers.queries.customer_query' };
const allowed_customer_group = this.events.get_allowed_customer_group() || [];
@ -332,7 +319,7 @@ erpnext.PointOfSale.ItemCart = class {
}
},
},
parent: this.$customer_section.find('.customer-search-field'),
parent: this.$customer_section.find('.customer-field'),
render_input: true,
});
this.customer_field.toggle_label(false);
@ -371,9 +358,9 @@ erpnext.PointOfSale.ItemCart = class {
}
show_discount_control() {
this.$add_discount_elem.removeClass("pr-4 pl-4");
this.$add_discount_elem.css({ 'padding': '0px', 'border': 'none' })
this.$add_discount_elem.html(
`<div class="add-discount-field flex flex-1 items-center"></div>`
`<div class="add-discount-field"></div>`
);
const me = this;
@ -382,14 +369,19 @@ erpnext.PointOfSale.ItemCart = class {
label: __('Discount'),
fieldtype: 'Data',
placeholder: __('Enter discount percentage.'),
input_class: 'input-xs',
onchange: function() {
const frm = me.events.get_frm();
if (this.value.length || this.value === 0) {
if (flt(this.value) != 0) {
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value));
me.hide_discount_control(this.value);
} else {
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', 0);
me.$add_discount_elem.html(`+ Add Discount`);
me.$add_discount_elem.css({
'border': '1px dashed var(--gray-500)',
'padding': 'var(--padding-sm) var(--padding-md)'
});
me.$add_discount_elem.html(`${me.get_discount_icon()} Add Discount`);
me.discount_field = undefined;
}
},
@ -403,38 +395,36 @@ erpnext.PointOfSale.ItemCart = class {
hide_discount_control(discount) {
if (!discount) {
this.$add_discount_elem.removeClass("pr-4 pl-4");
this.$add_discount_elem.css({ 'padding': '0px', 'border': 'none' });
this.$add_discount_elem.html(
`<div class="add-discount-field flex flex-1 items-center"></div>`
`<div class="add-discount-field"></div>`
);
} else {
this.$add_discount_elem.addClass('pr-4 pl-4');
this.$add_discount_elem.css({
'border': '1px dashed var(--dark-green-500)',
'padding': 'var(--padding-sm) var(--padding-md)'
});
this.$add_discount_elem.html(
`<svg class="mr-2" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"
stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
</svg>
<div class="edit-discount p-1 pr-3 pl-3 text-dark-grey rounded w-fit bg-green-200 mb-2">
${String(discount).bold()}% off
</div>
`
`<div class="edit-discount-btn">
${this.get_discount_icon()} Additional&nbsp;${String(discount).bold()}% discount applied
</div>`
);
}
}
update_customer_section() {
const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
const { customer, email_id='', mobile_no='' } = this.customer_info || {};
if (customer) {
this.$customer_section.addClass('border pr-4 pl-4').html(
`<div class="customer-details flex flex-col">
<div class="customer-header flex items-center rounded h-18 pointer">
${get_customer_image()}
<div class="customer-name flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
<div class="text-md text-dark-grey text-bold">${customer}</div>
this.$customer_section.html(
`<div class="customer-details">
<div class="customer-display">
${this.get_customer_image()}
<div class="customer-name-desc">
<div class="customer-name">${customer}</div>
${get_customer_description()}
</div>
<div class="f-shrink-0 add-remove-customer flex items-center pointer" data-customer="${escape(customer)}">
<div class="reset-customer-btn" data-customer="${escape(customer)}">
<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
</svg>
@ -449,26 +439,24 @@ erpnext.PointOfSale.ItemCart = class {
function get_customer_description() {
if (!email_id && !mobile_no) {
return `<div class="text-grey-200 italic">Click to add email / phone</div>`
return `<div class="customer-desc">Click to add email / phone</div>`
} else if (email_id && !mobile_no) {
return `<div class="text-grey">${email_id}</div>`
return `<div class="customer-desc">${email_id}</div>`
} else if (mobile_no && !email_id) {
return `<div class="text-grey">${mobile_no}</div>`
return `<div class="customer-desc">${mobile_no}</div>`
} else {
return `<div class="text-grey">${email_id} | ${mobile_no}</div>`
return `<div class="customer-desc">${email_id} - ${mobile_no}</div>`
}
}
function get_customer_image() {
if (image) {
return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
</div>`
} else {
return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200 text-md">
${frappe.get_abbr(customer)}
</div>`
}
}
get_customer_image() {
const { customer, image } = this.customer_info || {};
if (image) {
return `<div class="customer-image"><img src="${image}" alt="${image}""></div>`
} else {
return `<div class="customer-image customer-abbr">${frappe.get_abbr(customer)}</div>`
}
}
@ -484,57 +472,44 @@ erpnext.PointOfSale.ItemCart = class {
render_net_total(value) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.net-total').html(
`<div class="flex flex-col">
<div class="text-md text-dark-grey text-bold">Net Total</div>
</div>
<div class="flex flex-col text-right">
<div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
</div>`
this.$totals_section.find('.net-total-container').html(
`<div>Net Total</div><div>${format_currency(value, currency)}</div>`
)
this.$numpad_section.find('.numpad-net-total').html(`Net Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
this.$numpad_section.find('.numpad-net-total').html(
`<div>Net Total: <span>${format_currency(value, currency)}</span></div>`
);
}
render_grand_total(value) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.grand-total').html(
`<div class="flex flex-col">
<div class="text-md text-dark-grey text-bold">Grand Total</div>
</div>
<div class="flex flex-col text-right">
<div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
</div>`
this.$totals_section.find('.grand-total-container').html(
`<div>Grand Total</div><div>${format_currency(value, currency)}</div>`
)
this.$numpad_section.find('.numpad-grand-total').html(`Grand Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
this.$numpad_section.find('.numpad-grand-total').html(
`<div>Grand Total: <span>${format_currency(value, currency)}</span></div>`
)
}
render_taxes(value, taxes) {
if (taxes.length) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.taxes').html(
`<div class="flex items-center justify-between h-16 pr-8 pl-8 border-b-grey">
<div class="flex overflow-hidden whitespace-nowrap">
<div class="text-md text-dark-grey text-bold w-fit">Tax Charges</div>
<div class="flex ml-4 text-dark-grey">
${
taxes.map((t, i) => {
let margin_left = '';
if (i !== 0) margin_left = 'ml-2';
const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
return `<span class="border-grey p-1 pl-2 pr-2 rounded ${margin_left}">${description}</span>`
}).join('')
}
</div>
</div>
<div class="flex flex-col text-right f-shrink-0 ml-4">
<div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
</div>
</div>`
this.$totals_section.find('.taxes-container').css('display', 'flex').html(
`${
taxes.map((t, i) => {
const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
return `<div class="tax-row">
<div class="tax-label">
${description}
</div>
<div class="tax-value">${format_currency(value, currency)}</div>
</div>`
}).join('')
}`
)
} else {
this.$totals_section.find('.taxes').html('')
this.$totals_section.find('.taxes-container').css('display', 'none').html('');
}
}
@ -553,7 +528,7 @@ erpnext.PointOfSale.ItemCart = class {
const $item = this.get_cart_item(item);
if (remove_item) {
$item && $item.remove();
$item && $item.next().remove() && $item.remove();
} else {
const { item_code, batch_no, uom } = item;
const search_field = batch_no ? 'batch_no' : 'item_code';
@ -563,9 +538,9 @@ erpnext.PointOfSale.ItemCart = class {
this.render_cart_item(item_row, $item);
}
const no_of_cart_items = this.$cart_items_wrapper.children().length;
no_of_cart_items > 0 && this.highlight_checkout_btn(no_of_cart_items > 0);
const no_of_cart_items = this.$cart_items_wrapper.find('.cart-item-wrapper').length;
this.highlight_checkout_btn(no_of_cart_items > 0);
this.update_empty_cart_section(no_of_cart_items);
}
@ -575,32 +550,33 @@ erpnext.PointOfSale.ItemCart = class {
if (!$item_to_update.length) {
this.$cart_items_wrapper.append(
`<div class="cart-item-wrapper flex items-center h-18 pr-4 pl-4 rounded border-grey pointer no-select"
`<div class="cart-item-wrapper"
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
data-batch-no="${escape(item_data.batch_no || '')}">
</div>`
</div>
<div class="seperator"></div>`
)
$item_to_update = this.get_cart_item(item_data);
}
$item_to_update.html(
`<div class="flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
<div class="text-md text-dark-grey text-bold">
`${get_item_image_html()}
<div class="item-name-desc">
<div class="item-name">
${item_data.item_name}
</div>
${get_description_html()}
</div>
${get_rate_discount_html()}
</div>`
${get_rate_discount_html()}`
)
set_dynamic_rate_header_width();
this.scroll_to_item($item_to_update);
function set_dynamic_rate_header_width() {
const rate_cols = Array.from(me.$cart_items_wrapper.find(".rate-col"));
me.$cart_header.find(".rate-list-header").css("width", "");
me.$cart_items_wrapper.find(".rate-col").css("width", "");
const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount"));
me.$cart_header.find(".rate-amount-header").css("width", "");
me.$cart_items_wrapper.find(".item-rate-amount").css("width", "");
let max_width = rate_cols.reduce((max_width, elm) => {
if ($(elm).width() > max_width)
max_width = $(elm).width();
@ -610,30 +586,26 @@ erpnext.PointOfSale.ItemCart = class {
max_width += 1;
if (max_width == 1) max_width = "";
me.$cart_header.find(".rate-list-header").css("width", max_width);
me.$cart_items_wrapper.find(".rate-col").css("width", max_width);
me.$cart_header.find(".rate-amount-header").css("width", max_width);
me.$cart_items_wrapper.find(".item-rate-amount").css("width", max_width);
}
function get_rate_discount_html() {
if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
return `
<div class="flex f-shrink-0 ml-4 items-center">
<div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
<span>${item_data.qty || 0}</span>
</div>
<div class="rate-col flex flex-col f-shrink-0 text-right">
<div class="text-md text-dark-grey text-bold">${format_currency(item_data.amount, currency)}</div>
<div class="text-md-0 text-dark-grey">${format_currency(item_data.rate, currency)}</div>
<div class="item-qty-rate">
<div class="item-qty"><span>${item_data.qty || 0}</span></div>
<div class="item-rate-amount">
<div class="item-rate">${format_currency(item_data.amount, currency)}</div>
<div class="item-amount">${format_currency(item_data.rate, currency)}</div>
</div>
</div>`
} else {
return `
<div class="flex f-shrink-0 ml-4 text-right">
<div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
<span>${item_data.qty || 0}</span>
</div>
<div class="rate-col flex flex-col f-shrink-0 text-right">
<div class="text-md text-dark-grey text-bold">${format_currency(item_data.rate, currency)}</div>
<div class="item-qty-rate">
<div class="item-qty"><span>${item_data.qty || 0}</span></div>
<div class="item-rate-amount">
<div class="item-rate">${format_currency(item_data.rate, currency)}</div>
</div>
</div>`
}
@ -649,10 +621,19 @@ erpnext.PointOfSale.ItemCart = class {
}
}
item_data.description = frappe.ellipsis(item_data.description, 45);
return `<div class="text-grey">${item_data.description}</div>`
return `<div class="item-desc">${item_data.description}</div>`
}
return ``;
}
function get_item_image_html() {
const { image, item_name } = item_data;
if (image) {
return `<div class="item-image"><img src="${image}" alt="${image}""></div>`
} else {
return `<div class="item-image item-abbr">${frappe.get_abbr(item_name)}</div>`
}
}
}
scroll_to_item($item) {
@ -668,20 +649,25 @@ erpnext.PointOfSale.ItemCart = class {
toggle_checkout_btn(show_checkout) {
if (show_checkout) {
this.$totals_section.find('.checkout-btn').removeClass('d-none');
this.$totals_section.find('.edit-cart-btn').addClass('d-none');
this.$totals_section.find('.checkout-btn').css('display', 'flex');
this.$totals_section.find('.edit-cart-btn').css('display', 'none');
} else {
this.$totals_section.find('.checkout-btn').addClass('d-none');
this.$totals_section.find('.edit-cart-btn').removeClass('d-none');
this.$totals_section.find('.checkout-btn').css('display', 'none');
this.$totals_section.find('.edit-cart-btn').css('display', 'flex');
}
}
highlight_checkout_btn(toggle) {
const has_primary_class = this.$totals_section.find('.checkout-btn').hasClass('bg-primary');
if (toggle && !has_primary_class) {
this.$totals_section.find('.checkout-btn').addClass('bg-primary text-white text-lg');
} else if (!toggle && has_primary_class) {
this.$totals_section.find('.checkout-btn').removeClass('bg-primary text-white text-lg');
if (toggle) {
this.$add_discount_elem.css('display', 'flex');
this.$cart_container.find('.checkout-btn').css({
'background-color': 'var(--blue-500)'
});
} else {
this.$add_discount_elem.css('display', 'none');
this.$cart_container.find('.checkout-btn').css({
'background-color': 'var(--blue-200)'
});
}
}
@ -689,8 +675,7 @@ erpnext.PointOfSale.ItemCart = class {
const $no_item_element = this.$cart_items_wrapper.find('.no-item-wrapper');
// if cart has items and no item is present
no_of_cart_items > 0 && $no_item_element && $no_item_element.remove()
&& this.$cart_items_wrapper.removeClass('mt-4 border-grey border-dashed') && this.$cart_header.removeClass('d-none');
no_of_cart_items > 0 && $no_item_element && $no_item_element.remove() && this.$cart_header.css('display', 'flex');
no_of_cart_items === 0 && !$no_item_element.length && this.make_no_items_placeholder();
}
@ -753,36 +738,36 @@ erpnext.PointOfSale.ItemCart = class {
}
highlight_numpad_btn($btn, curr_action) {
const curr_action_is_highlighted = $btn.hasClass('shadow-inner');
const curr_action_is_highlighted = $btn.hasClass('highlighted-numpad-btn');
const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action);
if (!curr_action_is_highlighted) {
$btn.addClass('shadow-inner bg-selected');
$btn.addClass('highlighted-numpad-btn');
}
if (this.prev_action === curr_action && curr_action_is_highlighted) {
// if Qty is pressed twice
$btn.removeClass('shadow-inner bg-selected');
$btn.removeClass('highlighted-numpad-btn');
}
if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) {
// Order: Qty -> Rate then remove Qty highlight
const prev_btn = $(`[data-button-value='${this.prev_action}']`);
prev_btn.removeClass('shadow-inner bg-selected');
prev_btn.removeClass('highlighted-numpad-btn');
}
if (!curr_action_is_action || curr_action === 'done') {
// if numbers are clicked
setTimeout(() => {
$btn.removeClass('shadow-inner bg-selected');
}, 100);
$btn.removeClass('highlighted-numpad-btn');
}, 200);
}
}
toggle_numpad(show) {
if (show) {
this.$totals_section.addClass('d-none');
this.$numpad_section.removeClass('d-none');
this.$totals_section.css('display', 'none');
this.$numpad_section.css('display', 'flex');
} else {
this.$totals_section.removeClass('d-none');
this.$numpad_section.addClass('d-none');
this.$totals_section.css('display', 'flex');
this.$numpad_section.css('display', 'none');
}
this.reset_numpad();
}
@ -790,7 +775,7 @@ erpnext.PointOfSale.ItemCart = class {
reset_numpad() {
this.numpad_value = '';
this.prev_action = undefined;
this.$numpad_section.find('.shadow-inner').removeClass('shadow-inner bg-selected');
this.$numpad_section.find('.highlighted-numpad-btn').removeClass('highlighted-numpad-btn');
}
toggle_numpad_field_edit(fieldname) {
@ -801,48 +786,60 @@ erpnext.PointOfSale.ItemCart = class {
toggle_customer_info(show) {
if (show) {
this.$cart_container.addClass('d-none')
this.$customer_section.addClass('flex-1 scroll-y').removeClass('mb-0 border pr-4 pl-4')
this.$customer_section.find('.icon').addClass('w-24 h-24 text-2xl').removeClass('w-12 h-12 text-md')
this.$customer_section.find('.customer-header').removeClass('h-18');
this.$customer_section.find('.customer-details').addClass('sticky z-100 bg-white');
const { customer } = this.customer_info || {};
this.$customer_section.find('.customer-name').html(
`<div class="text-md text-dark-grey text-bold">${this.customer_info.customer}</div>
<div class="last-transacted-on text-grey-200"></div>`
)
this.$customer_section.find('.customer-details').append(
`<div class="customer-form">
<div class="text-grey mt-4 mb-6">CONTACT DETAILS</div>
<div class="grid grid-cols-2 gap-4">
<div class="email_id-field"></div>
<div class="mobile_no-field"></div>
<div class="loyalty_program-field"></div>
<div class="loyalty_points-field"></div>
this.$cart_container.css('display', 'none');
this.$customer_section.css({
'height': '100%',
'padding-top': '0px',
'overflow-x': 'hidden',
'overflow-y': 'scroll'
});
this.$customer_section.find('.customer-details').html(
`<div class="header">
<div class="label">Contact Details</div>
<div class="close-details-btn">
<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
</svg>
</div>
<div class="text-grey mt-4 mb-6">RECENT TRANSACTIONS</div>
</div>`
)
</div>
<div class="customer-display">
${this.get_customer_image()}
<div class="customer-name-desc">
<div class="customer-name">${customer}</div>
<div class="customer-desc"></div>
</div>
</div>
<div class="customer-fields-container">
<div class="email_id-field"></div>
<div class="mobile_no-field"></div>
<div class="loyalty_program-field"></div>
<div class="loyalty_points-field"></div>
</div>
<div class="transactions-label">Recent Transactions</div>`
);
// transactions need to be in diff div from sticky elem for scrolling
this.$customer_section.append(`<div class="customer-transactions flex-1 rounded"></div>`)
this.$customer_section.append(`<div class="customer-transactions"></div>`)
this.render_customer_info_form();
this.render_customer_fields();
this.fetch_customer_transactions();
} else {
this.$cart_container.removeClass('d-none');
this.$customer_section.removeClass('flex-1 scroll-y').addClass('mb-0 border pr-4 pl-4');
this.$customer_section.find('.icon').addClass('w-12 h-12 text-md').removeClass('w-24 h-24 text-2xl');
this.$customer_section.find('.customer-header').addClass('h-18')
this.$customer_section.find('.customer-details').removeClass('sticky z-100 bg-white');
this.$cart_container.css('display', 'flex');
this.$customer_section.css({
'height': '',
'padding-top': '',
'overflow-x': '',
'overflow-y': ''
});
this.update_customer_section();
}
}
render_customer_info_form() {
const $customer_form = this.$customer_section.find('.customer-form');
render_customer_fields() {
const $customer_form = this.$customer_section.find('.customer-fields-container');
const dfs = [{
fieldname: 'email_id',
@ -864,7 +861,7 @@ erpnext.PointOfSale.ItemCart = class {
},{
fieldname: 'loyalty_points',
label: __('Loyalty Points'),
fieldtype: 'Int',
fieldtype: 'Data',
read_only: 1
}];
@ -916,41 +913,45 @@ erpnext.PointOfSale.ItemCart = class {
const transaction_container = this.$customer_section.find('.customer-transactions');
if (!res.length) {
transaction_container.removeClass('flex-1 border rounded').html(
`<div class="text-grey text-center">No recent transactions found</div>`
transaction_container.html(
`<div class="no-transactions-placeholder">No recent transactions found</div>`
)
return;
};
const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow();
this.$customer_section.find('.last-transacted-on').html(`Last transacted ${elapsed_time}`);
this.$customer_section.find('.customer-desc').html(`Last transacted ${elapsed_time}`);
res.forEach(invoice => {
const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
let indicator_color = '';
if (in_list(['Paid', 'Consolidated'], invoice.status)) (indicator_color = 'green');
if (invoice.status === 'Draft') (indicator_color = 'red');
if (invoice.status === 'Return') (indicator_color = 'grey');
let indicator_color = {
'Paid': 'green',
'Draft': 'red',
'Return': 'gray',
'Consolidated': 'blue'
};
transaction_container.append(
`<div class="invoice-wrapper flex p-3 justify-between border-grey rounded pointer no-select" data-invoice-name="${escape(invoice.name)}">
<div class="flex flex-col justify-end">
<div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
<div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
${posting_datetime}
</div>
`<div class="invoice-wrapper" data-invoice-name="${escape(invoice.name)}">
<div class="invoice-name-date">
<div class="invoice-name">${invoice.name}</div>
<div class="invoice-date">${posting_datetime}</div>
</div>
<div class="flex flex-col text-right">
<div class="f-shrink-0 text-md text-dark-grey text-bold ml-4">
<div class="invoice-total-status">
<div class="invoice-total">
${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
</div>
<div class="f-shrink-0 text-grey ml-4 text-bold indicator ${indicator_color}">${invoice.status}</div>
<div class="invoice-status">
<span class="indicator-pill whitespace-nowrap ${indicator_color[invoice.status]}">
<span>${invoice.status}</span>
</span>
</div>
</div>
</div>`
</div>
<div class="seperator"></div>`
)
});
})
});
}
load_invoice() {
@ -973,20 +974,18 @@ erpnext.PointOfSale.ItemCart = class {
this.update_totals_section(frm);
if(frm.doc.docstatus === 1) {
this.$totals_section.find('.checkout-btn').addClass('d-none');
this.$totals_section.find('.edit-cart-btn').addClass('d-none');
this.$totals_section.find('.grand-total').removeClass('border-b-grey');
this.$totals_section.find('.checkout-btn').css('display', 'none');
this.$totals_section.find('.edit-cart-btn').css('display', 'none');
} else {
this.$totals_section.find('.checkout-btn').removeClass('d-none');
this.$totals_section.find('.edit-cart-btn').addClass('d-none');
this.$totals_section.find('.grand-total').addClass('border-b-grey');
this.$totals_section.find('.checkout-btn').css('display', 'flex');
this.$totals_section.find('.edit-cart-btn').css('display', 'none');
}
this.toggle_component(true);
}
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
}

View File

@ -16,35 +16,36 @@ erpnext.PointOfSale.ItemDetails = class {
prepare_dom() {
this.wrapper.append(
`<section class="col-span-4 flex shadow rounded item-details bg-white mx-h-70 h-100 d-none"></section>`
`<section class="item-details-container"></section>`
)
this.$component = this.wrapper.find('.item-details');
this.$component = this.wrapper.find('.item-details-container');
}
init_child_components() {
this.$component.html(
`<div class="details-container flex flex-col p-8 rounded w-full">
<div class="flex justify-between mb-2">
<div class="text-grey">ITEM DETAILS</div>
<div class="close-btn text-grey hover-underline pointer no-select">Close</div>
`<div class="item-details-header">
<div class="label">Item Details</div>
<div class="close-btn">
<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
</svg>
</div>
<div class="item-defaults flex">
<div class="flex-1 flex flex-col justify-end mr-4 mb-2">
<div class="item-name text-xl font-weight-450"></div>
<div class="item-description text-md-0 text-grey-200"></div>
<div class="item-price text-xl font-bold"></div>
</div>
<div class="item-image flex items-center justify-center w-46 h-46 bg-light-grey rounded ml-4 text-6xl text-grey-100"></div>
</div>
<div class="item-display">
<div class="item-name-desc-price">
<div class="item-name"></div>
<div class="item-desc"></div>
<div class="item-price"></div>
</div>
<div class="discount-section flex items-center"></div>
<div class="text-grey mt-4 mb-6">STOCK DETAILS</div>
<div class="form-container grid grid-cols-2 row-gap-2 col-gap-4 grid-auto-row"></div>
</div>`
<div class="item-image"></div>
</div>
<div class="discount-section"></div>
<div class="form-container"></div>`
)
this.$item_name = this.$component.find('.item-name');
this.$item_description = this.$component.find('.item-description');
this.$item_description = this.$component.find('.item-desc');
this.$item_price = this.$component.find('.item-price');
this.$item_image = this.$component.find('.item-image');
this.$form_container = this.$component.find('.form-container');
@ -52,7 +53,7 @@ erpnext.PointOfSale.ItemDetails = class {
}
toggle_item_details_section(item) {
const { item_code, batch_no, uom } = this.current_item;
const { item_code, batch_no, uom } = this.current_item;
const item_code_is_same = item && item_code === item.item_code;
const batch_is_same = item && batch_no == item.batch_no;
const uom_is_same = item && uom === item.uom;
@ -104,11 +105,11 @@ erpnext.PointOfSale.ItemDetails = class {
}
render_dom(item) {
let { item_code ,item_name, description, image, price_list_rate } = item;
let { item_name, description, image, price_list_rate } = item;
function get_description_html() {
if (description) {
description = description.indexOf('...') === -1 && description.length > 75 ? description.substr(0, 73) + '...' : description;
description = description.indexOf('...') === -1 && description.length > 140 ? description.substr(0, 139) + '...' : description;
return description;
}
return ``;
@ -118,11 +119,9 @@ erpnext.PointOfSale.ItemDetails = class {
this.$item_description.html(get_description_html());
this.$item_price.html(format_currency(price_list_rate, this.currency));
if (image) {
this.$item_image.html(
`<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">`
);
this.$item_image.html(`<img src="${image}" alt="${image}">`);
} else {
this.$item_image.html(frappe.get_abbr(item_code));
this.$item_image.html(`<div class="item-abbr">${frappe.get_abbr(item_name)}</div>`);
}
}
@ -130,12 +129,8 @@ erpnext.PointOfSale.ItemDetails = class {
render_discount_dom(item) {
if (item.discount_percentage) {
this.$dicount_section.html(
`<div class="text-grey line-through mr-4 text-md mb-2">
${format_currency(item.price_list_rate, this.currency)}
</div>
<div class="p-1 pr-3 pl-3 rounded w-fit text-bold bg-green-200 mb-2">
${item.discount_percentage}% off
</div>`
`<div class="item-rate">${format_currency(item.price_list_rate, this.currency)}</div>
<div class="item-discount">${item.discount_percentage}% off</div>`
)
this.$item_price.html(format_currency(item.rate, this.currency));
} else {
@ -149,9 +144,7 @@ erpnext.PointOfSale.ItemDetails = class {
fields_to_display.forEach((fieldname, idx) => {
this.$form_container.append(
`<div class="">
<div class="item_detail_field ${fieldname}-control" data-fieldname="${fieldname}"></div>
</div>`
`<div class="${fieldname}-control" data-fieldname="${fieldname}"></div>`
)
const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname);
@ -185,22 +178,15 @@ erpnext.PointOfSale.ItemDetails = class {
make_auto_serial_selection_btn(item) {
if (item.has_serial_no) {
this.$form_container.append(
`<div class="grid-filler no-select"></div>`
)
if (!item.has_batch_no) {
this.$form_container.append(
`<div class="grid-filler no-select"></div>`
)
}
this.$form_container.append(
`<div class="auto-fetch-btn bg-grey-100 border border-grey text-bold rounded pt-3 pb-3 pl-6 pr-8 text-grey pointer no-select mt-2"
style="height: 3.3rem">
Auto Fetch Serial Numbers
</div>`
`<div class="btn btn-sm btn-secondary auto-fetch-btn">Auto Fetch Serial Numbers</div>`
)
this.$form_container.find('.serial_no-control').find('textarea').css('height', '9rem');
this.$form_container.find('.serial_no-control').parent().addClass('row-span-2');
this.$form_container.find('.serial_no-control').find('textarea').css('height', '6rem');
}
}
@ -294,8 +280,13 @@ erpnext.PointOfSale.ItemDetails = class {
}
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
const field_control = me[`${fieldname}_control`];
if (field_control) {
const field_control = this[`${fieldname}_control`];
const { item_code, batch_no, uom } = this.current_item;
const item_code_is_same = item_code === item_row.item_code;
const batch_is_same = batch_no == item_row.batch_no;
const uom_is_same = uom === item_row.uom;
if (field_control && item_code_is_same && batch_is_same && uom_is_same) {
field_control.set_value(value);
cur_pos.update_cart_html(item_row);
}
@ -409,6 +400,6 @@ erpnext.PointOfSale.ItemDetails = class {
}
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
}

View File

@ -1,3 +1,5 @@
import onScan from 'onscan.js';
erpnext.PointOfSale.ItemSelector = class {
constructor({ frm, wrapper, events, pos_profile }) {
this.wrapper = wrapper;
@ -17,18 +19,13 @@ erpnext.PointOfSale.ItemSelector = class {
prepare_dom() {
this.wrapper.append(
`<section class="col-span-6 flex shadow rounded items-selector bg-white mx-h-70 h-100">
<div class="flex flex-col rounded w-full scroll-y">
<div class="filter-section flex p-8 pb-2 bg-white sticky z-100">
<div class="search-field flex f-grow-3 mr-8 items-center text-grey"></div>
<div class="item-group-field flex f-grow-1 items-center text-grey text-bold"></div>
</div>
<div class="flex flex-1 flex-col p-8 pt-2">
<div class="text-grey mb-6">ALL ITEMS</div>
<div class="items-container grid grid-cols-4 gap-8">
</div>
</div>
`<section class="items-selector">
<div class="filter-section">
<div class="label">All Items</div>
<div class="search-field"></div>
<div class="item-group-field"></div>
</div>
<div class="items-container"></div>
</section>`
);
@ -51,7 +48,8 @@ erpnext.PointOfSale.ItemSelector = class {
}
get_items({start = 0, page_length = 40, search_value=''}) {
const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list;
const doc = this.events.get_frm().doc;
const price_list = (doc && doc.selling_price_list) || this.price_list;
let { item_group, pos_profile } = this;
!item_group && (item_group = this.parent_item_group);
@ -80,27 +78,28 @@ erpnext.PointOfSale.ItemSelector = class {
function get_item_image_html() {
if (item_image) {
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>`
return `<div class="item-display">
<img src="${item_image}" alt="${frappe.get_abbr(item.item_name)}">
</div>`;
} else {
return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">
${frappe.get_abbr(item.item_name)}
</div>`
return `<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
}
}
return (
`<div class="item-wrapper rounded shadow pointer no-select" data-item-code="${escape(item.item_code)}"
data-serial-no="${escape(serial_no)}" data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
`<div class="item-wrapper"
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
title="Avaiable Qty: ${actual_qty}">
${get_item_image_html()}
<div class="flex items-center pr-4 pl-4 h-10 justify-between">
<div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
<div class="item-detail">
<div class="item-name">
<span class="indicator ${indicator_color}"></span>
${frappe.ellipsis(item.item_name, 18)}
</div>
<div class="f-shrink-0 text-dark-grey text-bold ml-4">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
<div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
</div>
</div>`
)
@ -108,6 +107,7 @@ erpnext.PointOfSale.ItemSelector = class {
make_search_bar() {
const me = this;
const doc = me.events.get_frm().doc;
this.$component.find('.search-field').html('');
this.$component.find('.item-group-field').html('');
@ -115,7 +115,7 @@ erpnext.PointOfSale.ItemSelector = class {
df: {
label: __('Search'),
fieldtype: 'Data',
placeholder: __('Search by item code, serial number, batch no or barcode')
placeholder: __('Search by item code, serial number or barcode')
},
parent: this.$component.find('.search-field'),
render_input: true,
@ -135,7 +135,7 @@ erpnext.PointOfSale.ItemSelector = class {
return {
query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
filters: {
pos_profile: me.events.get_frm().doc?.pos_profile
pos_profile: doc ? doc.pos_profile : ''
}
}
},
@ -149,6 +149,7 @@ erpnext.PointOfSale.ItemSelector = class {
bind_events() {
const me = this;
window.onScan = onScan;
onScan.attachTo(document, {
onScan: (sScancode) => {
if (this.search_field && this.$component.is(':visible')) {
@ -252,23 +253,23 @@ erpnext.PointOfSale.ItemSelector = class {
resize_selector(minimize) {
minimize ?
this.$component.find('.search-field').removeClass('mr-8') :
this.$component.find('.search-field').addClass('mr-8');
this.$component.find('.filter-section').css('grid-template-columns', 'repeat(1, minmax(0, 1fr))') :
this.$component.find('.filter-section').css('grid-template-columns', 'repeat(12, minmax(0, 1fr))');
minimize ?
this.$component.find('.filter-section').addClass('flex-col') :
this.$component.find('.filter-section').removeClass('flex-col');
this.$component.find('.search-field').css('margin', 'var(--margin-sm) 0px') :
this.$component.find('.search-field').css('margin', '0px var(--margin-sm)');
minimize ?
this.$component.removeClass('col-span-6').addClass('col-span-2') :
this.$component.removeClass('col-span-2').addClass('col-span-6')
this.$component.css('grid-column', 'span 2 / span 2') :
this.$component.css('grid-column', 'span 6 / span 6')
minimize ?
this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') :
this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4')
this.$items_container.css('grid-template-columns', 'repeat(1, minmax(0, 1fr))') :
this.$items_container.css('grid-template-columns', 'repeat(4, minmax(0, 1fr))')
}
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
}

View File

@ -25,14 +25,13 @@ erpnext.PointOfSale.NumberPad = class {
const fieldname = fieldnames && fieldnames[number] ?
fieldnames[number] : typeof number === 'string' ? frappe.scrub(number) : number;
return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
return a2 + `<div class="numpad-btn ${class_to_append}" data-button-value="${fieldname}">${number}</div>`
}, '')
}, '');
}
this.wrapper.html(
`<div class="grid grid-cols-${cols} gap-4">
`<div class="numpad-container">
${get_keys()}
</div>`
)

View File

@ -14,17 +14,13 @@ erpnext.PointOfSale.PastOrderList = class {
prepare_dom() {
this.wrapper.append(
`<section class="col-span-4 flex flex-col shadow rounded past-order-list bg-white mx-h-70 h-100 d-none">
<div class="flex flex-col rounded w-full scroll-y">
<div class="filter-section flex flex-col p-8 pb-2 bg-white sticky z-100">
<div class="search-field flex items-center text-grey"></div>
<div class="status-field flex items-center text-grey text-bold"></div>
</div>
<div class="flex flex-1 flex-col p-8 pt-2">
<div class="text-grey mb-6">RECENT ORDERS</div>
<div class="invoices-container rounded border grid grid-cols-1"></div>
</div>
`<section class="past-order-list">
<div class="filter-section">
<div class="label">Recent Orders</div>
<div class="search-field"></div>
<div class="status-field"></div>
</div>
<div class="invoices-container"></div>
</section>`
);
@ -66,7 +62,7 @@ erpnext.PointOfSale.PastOrderList = class {
options: `Draft\nPaid\nConsolidated\nReturn`,
placeholder: __('Filter by invoice status'),
onchange: function() {
me.refresh_list(me.search_field.get_value(), this.value);
if (me.$component.is(':visible')) me.refresh_list();
}
},
parent: this.$component.find('.status-field'),
@ -77,10 +73,6 @@ erpnext.PointOfSale.PastOrderList = class {
this.status_field.set_value('Draft');
}
toggle_component(show) {
show ? this.$component.removeClass('d-none') && this.refresh_list() : this.$component.addClass('d-none');
}
refresh_list() {
frappe.dom.freeze();
this.events.reset_summary();
@ -106,23 +98,26 @@ erpnext.PointOfSale.PastOrderList = class {
get_invoice_html(invoice) {
const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
return (
`<div class="invoice-wrapper flex p-4 justify-between border-b-grey pointer no-select" data-invoice-name="${escape(invoice.name)}">
<div class="flex flex-col justify-end">
<div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
<div class="flex items-center">
<div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
<svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
${invoice.customer}
</div>
`<div class="invoice-wrapper" data-invoice-name="${escape(invoice.name)}">
<div class="invoice-name-date">
<div class="invoice-name">${invoice.name}</div>
<div class="invoice-date">
<svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
${invoice.customer}
</div>
</div>
<div class="flex flex-col text-right">
<div class="f-shrink-0 text-lg text-dark-grey text-bold ml-4">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
<div class="f-shrink-0 text-grey ml-4">${posting_datetime}</div>
<div class="invoice-total-status">
<div class="invoice-total">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
<div class="invoice-date">${posting_datetime}</div>
</div>
</div>`
</div>
<div class="seperator"></div>`
);
}
toggle_component(show) {
show ? this.$component.css('display', 'flex') && this.refresh_list() : this.$component.css('display', 'none');
}
};

View File

@ -8,85 +8,39 @@ erpnext.PointOfSale.PastOrderSummary = class {
init_component() {
this.prepare_dom();
this.init_child_components();
this.init_email_print_dialog();
this.bind_events();
this.attach_shortcuts();
}
prepare_dom() {
this.wrapper.append(
`<section class="col-span-6 flex flex-col items-center shadow rounded past-order-summary bg-white mx-h-70 h-100 d-none">
<div class="no-summary-placeholder flex flex-1 items-center justify-center p-16">
<div class="no-item-wrapper flex items-center h-18 pr-4 pl-4">
<div class="flex-1 text-center text-grey">Select an invoice to load summary data</div>
</div>
`<section class="past-order-summary">
<div class="no-summary-placeholder">
Select an invoice to load summary data
</div>
<div class="summary-wrapper d-none flex-1 w-66 text-dark-grey relative">
<div class="summary-container absolute flex flex-col pt-16 pb-16 pr-8 pl-8 w-full h-full"></div>
<div class="invoice-summary-wrapper">
<div class="abs-container">
<div class="upper-section"></div>
<div class="label">Items</div>
<div class="items-container summary-container"></div>
<div class="label">Totals</div>
<div class="totals-container summary-container"></div>
<div class="label">Payments</div>
<div class="payments-container summary-container"></div>
<div class="summary-btns"></div>
</div>
</div>
</section>`
);
this.$component = this.wrapper.find('.past-order-summary');
this.$summary_wrapper = this.$component.find('.summary-wrapper');
this.$summary_container = this.$component.find('.summary-container');
}
init_child_components() {
this.init_upper_section();
this.init_items_summary();
this.init_totals_summary();
this.init_payments_summary();
this.init_summary_buttons();
this.init_email_print_dialog();
}
init_upper_section() {
this.$summary_container.append(
`<div class="flex upper-section justify-between w-full h-24"></div>`
);
this.$summary_wrapper = this.$component.find('.invoice-summary-wrapper');
this.$summary_container = this.$component.find('.abs-container');
this.$upper_section = this.$summary_container.find('.upper-section');
}
init_items_summary() {
this.$summary_container.append(
`<div class="flex flex-col flex-1 mt-6 w-full scroll-y">
<div class="text-grey mb-4 sticky bg-white">ITEMS</div>
<div class="items-summary-container border rounded flex flex-col w-full"></div>
</div>`
);
this.$items_summary_container = this.$summary_container.find('.items-summary-container');
}
init_totals_summary() {
this.$summary_container.append(
`<div class="flex flex-col mt-6 w-full f-shrink-0">
<div class="text-grey mb-4">TOTALS</div>
<div class="summary-totals-container border rounded flex flex-col w-full"></div>
</div>`
);
this.$totals_summary_container = this.$summary_container.find('.summary-totals-container');
}
init_payments_summary() {
this.$summary_container.append(
`<div class="flex flex-col mt-6 w-full f-shrink-0">
<div class="text-grey mb-4">PAYMENTS</div>
<div class="payments-summary-container border rounded flex flex-col w-full mb-4"></div>
</div>`
);
this.$payment_summary_container = this.$summary_container.find('.payments-summary-container');
}
init_summary_buttons() {
this.$summary_container.append(
`<div class="summary-btns flex summary-btns justify-between w-full f-shrink-0"></div>`
);
this.$items_container = this.$summary_container.find('.items-container');
this.$totals_container = this.$summary_container.find('.totals-container');
this.$payment_container = this.$summary_container.find('.payments-container');
this.$summary_btns = this.$summary_container.find('.summary-btns');
}
@ -121,132 +75,88 @@ erpnext.PointOfSale.PastOrderSummary = class {
}
get_upper_section_html(doc) {
const { status } = doc; let indicator_color = '';
const { status } = doc;
let indicator_color = '';
in_list(['Paid', 'Consolidated'], status) && (indicator_color = 'green');
status === 'Draft' && (indicator_color = 'red');
status === 'Return' && (indicator_color = 'grey');
return `<div class="flex flex-col items-start justify-end pr-4">
<div class="text-lg text-bold pt-2">${doc.customer}</div>
<div class="text-grey">${this.customer_email}</div>
<div class="text-grey mt-auto">Sold by: ${doc.owner}</div>
return `<div class="left-section">
<div class="customer-name">${doc.customer}</div>
<div class="customer-email">${this.customer_email}</div>
<div class="cashier">Sold by: ${doc.owner}</div>
</div>
<div class="flex flex-col flex-1 items-end justify-between">
<div class="text-2-5xl text-bold">${format_currency(doc.paid_amount, doc.currency)}</div>
<div class="flex justify-between">
<div class="text-grey mr-4">${doc.name}</div>
<div class="text-grey text-bold indicator ${indicator_color}">${doc.status}</div>
</div>
<div class="right-section">
<div class="paid-amount">${format_currency(doc.paid_amount, doc.currency)}</div>
<div class="invoice-name">${doc.name}</div>
<span class="indicator-pill whitespace-nowrap ${indicator_color}"><span>${doc.status}</span></span>
</div>`;
}
get_item_html(doc, item_data) {
return `<div class="item-row-wrapper">
<div class="item-name">${item_data.item_name}</div>
<div class="item-qty">${item_data.qty || 0}</div>
<div class="item-rate-disc">${get_rate_discount_html()}</div>
</div>`;
function get_rate_discount_html() {
if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
return `<span class="item-disc">(${item_data.discount_percentage}% off)</span>
<div class="item-rate">${format_currency(item_data.rate, doc.currency)}</div>`;
} else {
return `<div class="item-rate">${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}</div>`;
}
}
}
get_discount_html(doc) {
if (doc.discount_amount) {
return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
<div class="flex f-shrink-1 items-center">
<div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap mr-2">
Discount
</div>
<span class="text-grey">(${doc.additional_discount_percentage} %)</span>
</div>
<div class="flex flex-col f-shrink-0 ml-auto text-right">
<div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.discount_amount, doc.currency)}</div>
</div>
</div>`;
return `<div class="summary-row-wrapper">
<div>Discount (${doc.additional_discount_percentage} %)</div>
<div>${format_currency(doc.discount_amount, doc.currency)}</div>
</div>`;
} else {
return ``;
}
}
get_net_total_html(doc) {
return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
<div class="flex f-shrink-1 items-center">
<div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
Net Total
</div>
</div>
<div class="flex flex-col f-shrink-0 ml-auto text-right">
<div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.net_total, doc.currency)}</div>
</div>
return `<div class="summary-row-wrapper">
<div>Net Total</div>
<div>${format_currency(doc.net_total, doc.currency)}</div>
</div>`;
}
get_taxes_html(doc) {
const taxes = doc.taxes.map((t, i) => {
let margin_left = '';
if (i !== 0) margin_left = 'ml-2';
return `<span class="pl-2 pr-2 ${margin_left}">${t.description} @${t.rate}%</span>`;
}).join('');
if (!doc.taxes.length) return '';
return `
<div class="total-summary-wrapper flex items-center justify-between h-12 pr-4 pl-4 border-b-grey">
<div class="flex">
<div class="text-md-0 text-dark-grey text-bold w-fit">Tax Charges</div>
<div class="flex ml-6 text-dark-grey">${taxes}</div>
</div>
<div class="flex flex-col text-right">
<div class="text-md-0 text-dark-grey text-bold">
${format_currency(doc.base_total_taxes_and_charges, doc.currency)}
</div>
</div>
<div class="taxes-wrapper">
${
doc.taxes.map((t, i) => {
const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
return `<div class="tax-row">
<div class="tax-label">${description}</div>
<div class="tax-value">${format_currency(t.tax_amount_after_discount_amount, doc.currency)}</div>
</div>`
}).join('')
}
</div>`;
}
get_grand_total_html(doc) {
return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
<div class="flex f-shrink-1 items-center">
<div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
Grand Total
</div>
</div>
<div class="flex flex-col f-shrink-0 ml-auto text-right">
<div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.grand_total, doc.currency)}</div>
</div>
return `<div class="summary-row-wrapper grand-total">
<div>Grand Total</div>
<div>${format_currency(doc.grand_total, doc.currency)}</div>
</div>`;
}
get_item_html(doc, item_data) {
return `<div class="item-summary-wrapper flex items-center h-12 pr-4 pl-4 border-b-grey pointer no-select">
<div class="flex w-6 h-6 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
<span>${item_data.qty || 0}</span>
</div>
<div class="flex flex-col f-shrink-1">
<div class="text-md text-dark-grey text-bold overflow-hidden whitespace-nowrap">
${item_data.item_name}
</div>
</div>
<div class="flex f-shrink-0 ml-auto text-right">
${get_rate_discount_html()}
</div>
</div>`;
function get_rate_discount_html() {
if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
return `<span class="text-grey mr-2">
(${item_data.discount_percentage}% off)
</span>
<div class="text-md-0 text-dark-grey text-bold">
${format_currency(item_data.rate, doc.currency)}
</div>`;
} else {
return `<div class="text-md-0 text-dark-grey text-bold">
${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}
</div>`;
}
}
}
get_payment_html(doc, payment) {
return `<div class="payment-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
<div class="flex f-shrink-1 items-center">
<div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
${payment.mode_of_payment}
</div>
</div>
<div class="flex flex-col f-shrink-0 ml-auto text-right">
<div class="text-md-0 text-dark-grey text-bold">${format_currency(payment.amount, doc.currency)}</div>
</div>
return `<div class="summary-row-wrapper payments">
<div>${payment.mode_of_payment}</div>
<div>${format_currency(payment.amount, doc.currency)}</div>
</div>`;
}
@ -254,22 +164,22 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.$summary_container.on('click', '.return-btn', () => {
this.events.process_return(this.doc.name);
this.toggle_component(false);
this.$component.find('.no-summary-placeholder').removeClass('d-none');
this.$summary_wrapper.addClass('d-none');
this.$component.find('.no-summary-placeholder').css('display', 'flex');
this.$summary_wrapper.css('display', 'none');
});
this.$summary_container.on('click', '.edit-btn', () => {
this.events.edit_order(this.doc.name);
this.toggle_component(false);
this.$component.find('.no-summary-placeholder').removeClass('d-none');
this.$summary_wrapper.addClass('d-none');
this.$component.find('.no-summary-placeholder').css('display', 'flex');
this.$summary_wrapper.css('display', 'none');
});
this.$summary_container.on('click', '.new-btn', () => {
this.events.new_order();
this.toggle_component(false);
this.$component.find('.no-summary-placeholder').removeClass('d-none');
this.$summary_wrapper.addClass('d-none');
this.$component.find('.no-summary-placeholder').css('display', 'flex');
this.$summary_wrapper.css('display', 'none');
});
this.$summary_container.on('click', '.email-btn', () => {
@ -312,10 +222,6 @@ erpnext.PointOfSale.PastOrderSummary = class {
});
}
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
}
send_email() {
const frm = this.events.get_frm();
const recipients = this.email_dialog.get_values().recipients;
@ -338,8 +244,10 @@ erpnext.PointOfSale.PastOrderSummary = class {
if(!r.exc) {
frappe.utils.play_sound("email");
if(r.message["emails_not_sent_to"]) {
frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)",
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) );
frappe.msgprint(__(
"Email not sent to {0} (unsubscribed / disabled)",
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]
));
} else {
frappe.show_alert({
message: __('Email sent successfully.'),
@ -361,9 +269,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
m.visible_btns.forEach(b => {
const class_name = b.split(' ')[0].toLowerCase();
this.$summary_btns.append(
`<div class="${class_name}-btn border rounded h-14 flex flex-1 items-center mr-4 justify-center text-md text-bold no-select pointer">
${b}
</div>`
`<div class="summary-btn btn btn-default ${class_name}-btn">${b}</div>`
);
});
}
@ -371,29 +277,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.$summary_btns.children().last().removeClass('mr-4');
}
show_summary_placeholder() {
this.$summary_wrapper.addClass("d-none");
this.$component.find('.no-summary-placeholder').removeClass('d-none');
}
switch_to_post_submit_summary() {
// switch to full width view
this.$component.removeClass('col-span-6').addClass('col-span-10');
this.$summary_wrapper.removeClass('w-66').addClass('w-40');
// switch place holder with summary container
this.$component.find('.no-summary-placeholder').addClass('d-none');
this.$summary_wrapper.removeClass('d-none');
}
switch_to_recent_invoice_summary() {
// switch full width view with 60% view
this.$component.removeClass('col-span-10').addClass('col-span-6');
this.$summary_wrapper.removeClass('w-40').addClass('w-66');
// switch place holder with summary container
this.$component.find('.no-summary-placeholder').addClass('d-none');
this.$summary_wrapper.removeClass('d-none');
toggle_summary_placeholder(show) {
if (show) {
this.$summary_wrapper.css('display', 'none');
this.$component.find('.no-summary-placeholder').css('display', 'flex');
} else {
this.$summary_wrapper.css('display', 'flex');
this.$component.find('.no-summary-placeholder').css('display', 'none');
}
}
get_condition_btn_map(after_submission) {
@ -408,14 +299,15 @@ erpnext.PointOfSale.PastOrderSummary = class {
}
load_summary_of(doc, after_submission=false) {
this.$summary_wrapper.removeClass("d-none");
this.toggle_summary_placeholder(false)
after_submission ?
this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary();
this.$summary_wrapper.css('grid-column', 'span 10 / span 10') :
this.$summary_wrapper.css('grid-column', 'span 6 / span 6')
this.doc = doc;
this.attach_basic_info(doc);
this.attach_document_info(doc);
this.attach_items_info(doc);
@ -428,7 +320,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.add_summary_btns(condition_btns_map);
}
attach_basic_info(doc) {
attach_document_info(doc) {
frappe.db.get_value('Customer', this.doc.customer, 'email_id').then(({ message }) => {
this.customer_email = message.email_id || '';
const upper_section_dom = this.get_upper_section_html(doc);
@ -437,19 +329,19 @@ erpnext.PointOfSale.PastOrderSummary = class {
}
attach_items_info(doc) {
this.$items_summary_container.html('');
doc.items.forEach(item => {
this.$items_container.html('');
doc.items.forEach((item, i) => {
const item_dom = this.get_item_html(doc, item);
this.$items_summary_container.append(item_dom);
this.$items_container.append(item_dom);
});
}
attach_payments_info(doc) {
this.$payment_summary_container.html('');
this.$payment_container.html('');
doc.payments.forEach(p => {
if (p.amount) {
const payment_dom = this.get_payment_html(doc, p);
this.$payment_summary_container.append(payment_dom);
this.$payment_container.append(payment_dom);
}
});
if (doc.redeem_loyalty_points && doc.loyalty_amount) {
@ -457,20 +349,24 @@ erpnext.PointOfSale.PastOrderSummary = class {
mode_of_payment: 'Loyalty Points',
amount: doc.loyalty_amount,
});
this.$payment_summary_container.append(payment_dom);
this.$payment_container.append(payment_dom);
}
}
attach_totals_info(doc) {
this.$totals_summary_container.html('');
this.$totals_container.html('');
const discount_dom = this.get_discount_html(doc);
const net_total_dom = this.get_net_total_html(doc);
const taxes_dom = this.get_taxes_html(doc);
const discount_dom = this.get_discount_html(doc);
const grand_total_dom = this.get_grand_total_html(doc);
this.$totals_summary_container.append(discount_dom);
this.$totals_summary_container.append(net_total_dom);
this.$totals_summary_container.append(taxes_dom);
this.$totals_summary_container.append(grand_total_dom);
this.$totals_container.append(net_total_dom);
this.$totals_container.append(taxes_dom);
this.$totals_container.append(discount_dom);
this.$totals_container.append(grand_total_dom);
}
toggle_component(show) {
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
};

View File

@ -1,5 +1,3 @@
{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
erpnext.PointOfSale.Payment = class {
constructor({ events, wrapper }) {
this.wrapper = wrapper;
@ -18,52 +16,37 @@ erpnext.PointOfSale.Payment = class {
prepare_dom() {
this.wrapper.append(
`<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
<div class="flex flex-col p-16 pt-8 pb-8 w-full">
<div class="text-grey mb-6 payment-section no-select pointer">
PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
</div>
<div class="payment-modes flex flex-wrap"></div>
<div class="invoice-details-section"></div>
<div class="flex mt-auto justify-center w-full">
<div class="flex flex-col justify-center flex-1 ml-4">
<div class="flex w-full">
<div class="totals-remarks items-end justify-end flex flex-1">
<div class="remarks text-md-0 text-grey mr-auto"></div>
<div class="totals flex justify-end pt-4"></div>
</div>
<div class="number-pad w-40 mb-4 ml-8 d-none"></div>
</div>
<div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
Complete Order
</div>
<div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
</div>
`<section class="payment-container">
<div class="section-label payment-section">Payment Method</div>
<div class="payment-modes"></div>
<div class="fields-numpad-container">
<div class="fields-section">
<div class="section-label">Additional Information</div>
<div class="invoice-fields"></div>
</div>
<div class="number-pad"></div>
</div>
<div class="totals-section">
<div class="totals"></div>
</div>
<div class="submit-order-btn">Complete Order</div>
</section>`
)
this.$component = this.wrapper.find('.payment-section');
this.$component = this.wrapper.find('.payment-container');
this.$payment_modes = this.$component.find('.payment-modes');
this.$totals_remarks = this.$component.find('.totals-remarks');
this.$totals_section = this.$component.find('.totals-section');
this.$totals = this.$component.find('.totals');
this.$remarks = this.$component.find('.remarks');
this.$numpad = this.$component.find('.number-pad');
this.$invoice_details_section = this.$component.find('.invoice-details-section');
this.$invoice_fields_section = this.$component.find('.fields-section');
}
make_invoice_fields_control() {
frappe.db.get_doc("POS Settings", undefined).then((doc) => {
const fields = doc.invoice_fields;
if (!fields.length) return;
this.$invoice_details_section.html(
`<div class="text-grey pb-6 mt-2 pointer no-select">
ADDITIONAL INFORMATION<span class="octicon octicon-chevron-down collapse-indicator"></span>
</div>
<div class="invoice-fields grid grid-cols-2 gap-4 mb-6 d-none"></div>`
);
this.$invoice_fields = this.$invoice_details_section.find('.invoice-fields');
this.$invoice_fields = this.$invoice_fields_section.find('.invoice-fields');
this.$invoice_fields.html('');
const frm = this.events.get_frm();
fields.forEach(df => {
@ -127,9 +110,9 @@ erpnext.PointOfSale.Payment = class {
this.selected_mode.set_value(this.numpad_value);
function highlight_numpad_btn($btn) {
$btn.addClass('shadow-inner bg-selected');
$btn.addClass('shadow-base-inner bg-selected');
setTimeout(() => {
$btn.removeClass('shadow-inner bg-selected');
$btn.removeClass('shadow-base-inner bg-selected');
}, 100);
}
}
@ -142,13 +125,16 @@ erpnext.PointOfSale.Payment = class {
// if clicked element doesn't have .mode-of-payment class then return
if (!$(e.target).is(mode_clicked)) return;
const scrollLeft = mode_clicked.offset().left - me.$payment_modes.offset().left + me.$payment_modes.scrollLeft();
me.$payment_modes.animate({ scrollLeft });
const mode = mode_clicked.attr('data-mode');
// hide all control fields and shortcuts
$(`.mode-of-payment-control`).addClass('d-none');
$(`.cash-shortcuts`).addClass('d-none');
me.$payment_modes.find(`.pay-amount`).removeClass('d-none');
me.$payment_modes.find(`.loyalty-amount-name`).addClass('d-none');
$(`.mode-of-payment-control`).css('display', 'none');
$(`.cash-shortcuts`).css('display', 'none');
me.$payment_modes.find(`.pay-amount`).css('display', 'inline');
me.$payment_modes.find(`.loyalty-amount-name`).css('display', 'none');
// remove highlight from all mode-of-payments
$('.mode-of-payment').removeClass('border-primary');
@ -157,21 +143,20 @@ erpnext.PointOfSale.Payment = class {
// clicked one is selected then unselect it
mode_clicked.removeClass('border-primary');
me.selected_mode = '';
me.toggle_numpad(false);
} else {
// clicked one is not selected then select it
mode_clicked.addClass('border-primary');
mode_clicked.find('.mode-of-payment-control').removeClass('d-none');
mode_clicked.find('.cash-shortcuts').removeClass('d-none');
me.$payment_modes.find(`.${mode}-amount`).addClass('d-none');
me.$payment_modes.find(`.${mode}-name`).removeClass('d-none');
me.toggle_numpad(true);
mode_clicked.find('.mode-of-payment-control').css('display', 'flex');
mode_clicked.find('.cash-shortcuts').css('display', 'grid');
me.$payment_modes.find(`.${mode}-amount`).css('display', 'none');
me.$payment_modes.find(`.${mode}-name`).css('display', 'inline');
me.selected_mode = me[`${mode}_control`];
const doc = me.events.get_frm().doc;
me.selected_mode?.$input?.get(0).focus();
const current_value = me.selected_mode?.get_value()
!current_value && doc.grand_total > doc.paid_amount ? me.selected_mode?.set_value(doc.grand_total - doc.paid_amount) : '';
me.selected_mode = me[`${mode}_control`];
me.selected_mode && me.selected_mode.$input.get(0).focus();
const current_value = me.selected_mode ? me.selected_mode.get_value() : undefined;
!current_value && doc.grand_total > doc.paid_amount && me.selected_mode ?
me.selected_mode.set_value(doc.grand_total - doc.paid_amount) : '';
}
})
@ -198,7 +183,7 @@ erpnext.PointOfSale.Payment = class {
me.selected_mode.set_value(value);
})
this.$component.on('click', '.submit-order', () => {
this.$component.on('click', '.submit-order-btn', () => {
const doc = this.events.get_frm().doc;
const paid_amount = doc.paid_amount;
const items = doc.items;
@ -217,9 +202,9 @@ erpnext.PointOfSale.Payment = class {
this.update_totals_section(frm.doc);
// need to re calculate cash shortcuts after discount is applied
const is_cash_shortcuts_invisible = this.$payment_modes.find('.cash-shortcuts').hasClass('d-none');
const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible');
this.attach_cash_shortcuts(frm.doc);
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').removeClass('d-none');
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid');
})
frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
@ -235,29 +220,16 @@ erpnext.PointOfSale.Payment = class {
this[`${mode}_control`].set_value(default_mop.amount);
}
});
this.$component.on('click', '.invoice-details-section', function(e) {
if ($(e.target).closest('.invoice-fields').length) return;
me.$payment_modes.addClass('d-none');
me.$invoice_fields.toggleClass("d-none");
me.toggle_numpad(false);
});
this.$component.on('click', '.payment-section', () => {
this.$invoice_fields.addClass("d-none");
this.$payment_modes.toggleClass('d-none');
this.toggle_numpad(true);
})
}
attach_shortcuts() {
const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
this.$component.find('.submit-order').attr("title", `${ctrl_label}+Enter`);
this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`);
frappe.ui.keys.on("ctrl+enter", () => {
const payment_is_visible = this.$component.is(":visible");
const active_mode = this.$payment_modes.find(".border-primary");
if (payment_is_visible && active_mode.length) {
this.$component.find('.submit-order').click();
this.$component.find('.submit-order-btn').click();
}
});
@ -287,15 +259,13 @@ erpnext.PointOfSale.Payment = class {
}
toggle_numpad(show) {
if (show) {
this.$numpad.removeClass('d-none');
this.$remarks.addClass('d-none');
this.$totals_remarks.addClass('w-60 justify-center').removeClass('justify-end w-full');
} else {
this.$numpad.addClass('d-none');
this.$remarks.removeClass('d-none');
this.$totals_remarks.removeClass('w-60 justify-center').addClass('justify-end w-full');
}
// if (show) {
// this.$numpad.css('display', 'flex');
// this.$totals_section.addClass('w-60 justify-center').removeClass('justify-end w-full');
// } else {
// this.$numpad.css('display', 'none');
// this.$totals_section.removeClass('w-60 justify-center').addClass('justify-end w-full');
// }
}
render_payment_section() {
@ -327,7 +297,7 @@ erpnext.PointOfSale.Payment = class {
fieldtype: 'Data',
onchange: function() {}
},
parent: this.$totals_remarks.find(`.remarks`),
parent: this.$totals_section.find(`.remarks`),
render_input: true,
});
this[`remark_control`].set_value('');
@ -348,12 +318,11 @@ erpnext.PointOfSale.Payment = class {
const amount = p.amount > 0 ? format_currency(p.amount, currency) : '';
return (
`<div class="w-half ${margin} bg-white">
<div class="mode-of-payment rounded border border-grey text-grey text-md
mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="${mode}" data-payment-type="${payment_type}">
`<div class="payment-mode-wrapper">
<div class="mode-of-payment" data-mode="${mode}" data-payment-type="${payment_type}">
${p.mode_of_payment}
<div class="${mode}-amount pay-amount inline float-right text-bold">${amount}</div>
<div class="${mode} mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
<div class="${mode}-amount pay-amount">${amount}</div>
<div class="${mode} mode-of-payment-control"></div>
</div>
</div>`
)
@ -405,12 +374,10 @@ erpnext.PointOfSale.Payment = class {
this.$payment_modes.find('.cash-shortcuts').remove();
this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after(
`<div class="cash-shortcuts grid grid-cols-3 gap-2 flex-1 text-center text-md-0 mb-2 d-none">
`<div class="cash-shortcuts">
${
shortcuts.map(s => {
return `<div class="shortcut rounded bg-light-grey text-dark-grey pt-2 pb-2 no-select pointer" data-value="${s}">
${format_currency(s, currency, 0)}
</div>`
return `<div class="shortcut" data-value="${s}">${format_currency(s, currency, 0)}</div>`
}).join('')
}
</div>`
@ -457,13 +424,12 @@ erpnext.PointOfSale.Payment = class {
const margin = this.$payment_modes.children().length % 2 === 0 ? 'pr-2' : 'pl-2';
const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : '';
this.$payment_modes.append(
`<div class="w-half ${margin} bg-white">
<div class="mode-of-payment rounded border border-grey text-grey text-md
mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
`<div class="payment-mode-wrapper">
<div class="mode-of-payment" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
Redeem Loyalty Points
<div class="loyalty-amount-amount pay-amount inline float-right text-bold">${amount}</div>
<div class="loyalty-amount-name inline float-right text-bold text-md-0 d-none">${loyalty_program}</div>
<div class="loyalty-amount mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
<div class="loyalty-amount-amount pay-amount">${amount}</div>
<div class="loyalty-amount-name">${loyalty_program}</div>
<div class="loyalty-amount mode-of-payment-control"></div>
</div>
</div>`
)
@ -520,18 +486,24 @@ erpnext.PointOfSale.Payment = class {
const label = change ? __('Change') : __('To Be Paid');
this.$totals.html(
`<div>
<div class="pr-8 border-r-grey">Paid Amount</div>
<div class="pr-8 border-r-grey text-bold text-2xl">${format_currency(paid_amount, currency)}</div>
`<div class="col">
<div class="total-label">Grand Total</div>
<div class="value">${format_currency(doc.grand_total, currency)}</div>
</div>
<div>
<div class="pl-8">${label}</div>
<div class="pl-8 text-green-400 text-bold text-2xl">${format_currency(change || remaining, currency)}</div>
<div class="seperator-y"></div>
<div class="col">
<div class="total-label">Paid Amount</div>
<div class="value">${format_currency(paid_amount, currency)}</div>
</div>
<div class="seperator-y"></div>
<div class="col">
<div class="total-label">${label}</div>
<div class="value">${format_currency(change || remaining, currency)}</div>
</div>`
)
}
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
}

View File

@ -21,7 +21,6 @@
<h3>{%= __("Next Steps") %}</h3>
<ul class="list-unstyled">
<li><a class="text-muted" href="#">{%= __("Go to the Desktop and start using ERPNext") %}</a></li>
<li><a class="text-muted" href="#modules/Learn">{%= __("View a list of all the help videos") %}</a></li>
<li><a class="text-muted" href="https://erpnext.com/docs/user" target="_blank">{%= __("Read the ERPNext Manual") %}</a></li>
<li><a class="text-muted" href="https://discuss.erpnext.com" target="_blank">{%= __("Community Forum") %}</a></li>
</ul>

View File

@ -198,7 +198,7 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
freeze: true,
callback: function(r) {
frappe.show_alert(__('Stock Entry {0} created',
['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
['<a href="/desk/Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
dialog.hide();
callback(r);
},

View File

@ -102,7 +102,7 @@ frappe.ui.form.on('Batch', {
},
callback: (r) => {
frappe.show_alert(__('Stock Entry {0} created',
['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
['<a href="/desk/Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
frm.refresh();
},
});

View File

@ -85,7 +85,7 @@ frappe.ui.form.on("Item", {
}
if (frm.doc.variant_of) {
frm.set_intro(__('This Item is a Variant of {0} (Template).',
[`<a href="#Form/Item/${frm.doc.variant_of}">${frm.doc.variant_of}</a>`]), true);
[`<a href="/desk/Form/Item/${frm.doc.variant_of}">${frm.doc.variant_of}</a>`]), true);
}
if (frappe.defaults.get_default("item_naming_by")!="Naming Series" || frm.doc.variant_of) {
@ -649,7 +649,7 @@ $.extend(erpnext.item, {
if (r.message) {
var variant = r.message;
frappe.msgprint_dialog = frappe.msgprint(__("Item Variant {0} already exists with same attributes",
[repl('<a href="#Form/Item/%(item_encoded)s" class="strong variant-click">%(item)s</a>', {
[repl('<a href="/desk/Form/Item/%(item_encoded)s" class="strong variant-click">%(item)s</a>', {
item_encoded: encodeURIComponent(variant),
item: variant
})]

View File

@ -860,7 +860,7 @@ class Item(WebsiteGenerator):
rows = ''
for docname, attr_list in not_included.items():
link = "<a href='#Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
rows += table_row(link, body(attr_list))
error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.')

View File

@ -14,6 +14,6 @@ frappe.ui.form.on("Item Price", {
frm.add_fetch("item_code", "stock_uom", "uom");
frm.set_df_property("bulk_import_help", "options",
'<a href="#data-import-tool/Item Price">' + __("Import in Bulk") + '</a>');
'<a href="/desk/data-import-tool/Item Price">' + __("Import in Bulk") + '</a>');
}
});

View File

@ -184,7 +184,7 @@ frappe.ui.form.on("Issue", {
let url = window.location.href
let arr = url.split("/");
let result = arr[0] + "//" + arr[2]
frappe.msgprint(`New issue created: <a href="${result}/desk#Form/Issue/${r.message}">${r.message}</a>`)
frappe.msgprint(`New issue created: <a href="${result}//desk/Form/Issue/${r.message}">${r.message}</a>`)
frm.reload_doc();
dialog.hide();
});

View File

@ -207,7 +207,7 @@ class Issue(Document):
"comment_type": "Info",
"reference_doctype": "Issue",
"reference_name": replicated_issue.name,
"content": " - Split the Issue from <a href='#Form/Issue/{0}'>{1}</a>".format(self.name, frappe.bold(self.name)),
"content": " - Split the Issue from <a href='/app/Form/Issue/{0}'>{1}</a>".format(self.name, frappe.bold(self.name)),
}).insert(ignore_permissions=True)
return replicated_issue.name

View File

@ -26,12 +26,12 @@ class FindItemBot(BotParser):
for warehouse in warehouses:
qty = frappe.db.get_value("Bin", {'item_code': item[0], 'warehouse': warehouse.name}, 'actual_qty')
if qty:
out.append(_('{0} units of [{1}](#Form/Item/{1}) found in [{2}](#Form/Warehouse/{2})').format(qty,
out.append(_('{0} units of [{1}](/app/Form/Item/{1}) found in [{2}](/app/Form/Warehouse/{2})').format(qty,
item[0], warehouse.name))
found = True
if not found:
out.append(_('[{0}](#Form/Item/{0}) is out of stock').format(item[0]))
out.append(_('[{0}](/app/Form/Item/{0}) is out of stock').format(item[0]))
return "\n\n".join(out)

View File

@ -15,6 +15,7 @@
"snyk": "^1.290.1"
},
"dependencies": {
"onscan.js": "^1.5.2"
},
"scripts": {
"snyk-protect": "snyk protect",

View File

@ -1217,6 +1217,11 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
onscan.js@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341"
integrity sha512-9oGYy2gXYRjvXO9GYqqVca0VuCTAmWhbmX3egBSBP13rXiMNb+dKPJzKFEeECGqPBpf0m40Zoo+GUQ7eCackdw==
opn@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"