mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 08:54:45 +00:00
Merge branch 'rebrand-ui' of https://github.com/frappe/erpnext into rebrand-ui
This commit is contained in:
@@ -150,6 +150,7 @@
|
|||||||
"it": true,
|
"it": true,
|
||||||
"context": true,
|
"context": true,
|
||||||
"before": true,
|
"before": true,
|
||||||
"beforeEach": true
|
"beforeEach": true,
|
||||||
|
"onScan": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,4 @@ frappe.pages['point-of-sale'].refresh = function(wrapper) {
|
|||||||
wrapper.pos.wrapper.html("");
|
wrapper.pos.wrapper.html("");
|
||||||
wrapper.pos.check_opening_entry();
|
wrapper.pos.check_opening_entry();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
@@ -191,8 +191,8 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
|
|
||||||
this.frm.save(undefined, undefined, undefined, () => {
|
this.frm.save(undefined, undefined, undefined, () => {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message:__("There was an error saving the document."),
|
message: __("There was an error saving the document."),
|
||||||
indicator:'red'
|
indicator: 'red'
|
||||||
});
|
});
|
||||||
frappe.utils.play_sound("error");
|
frappe.utils.play_sound("error");
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -201,7 +201,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
() => this.make_new_invoice(),
|
() => this.make_new_invoice(),
|
||||||
() => frappe.dom.unfreeze(),
|
() => frappe.dom.unfreeze(),
|
||||||
]);
|
]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
close_pos() {
|
close_pos() {
|
||||||
@@ -682,5 +682,5 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
})
|
})
|
||||||
.catch(e => console.log(e));
|
.catch(e => console.log(e));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
erpnext.PointOfSale.Payment = class {
|
erpnext.PointOfSale.Payment = class {
|
||||||
constructor({ events, wrapper }) {
|
constructor({ events, wrapper }) {
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
@@ -11,7 +12,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
this.initialize_numpad();
|
this.initialize_numpad();
|
||||||
this.bind_events();
|
this.bind_events();
|
||||||
this.attach_shortcuts();
|
this.attach_shortcuts();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_dom() {
|
prepare_dom() {
|
||||||
@@ -31,7 +32,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
</div>
|
</div>
|
||||||
<div class="submit-order-btn">Complete Order</div>
|
<div class="submit-order-btn">Complete Order</div>
|
||||||
</section>`
|
</section>`
|
||||||
)
|
);
|
||||||
this.$component = this.wrapper.find('.payment-container');
|
this.$component = this.wrapper.find('.payment-container');
|
||||||
this.$payment_modes = this.$component.find('.payment-modes');
|
this.$payment_modes = this.$component.find('.payment-modes');
|
||||||
this.$totals_section = this.$component.find('.totals-section');
|
this.$totals_section = this.$component.find('.totals-section');
|
||||||
@@ -44,7 +45,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
frappe.db.get_doc("POS Settings", undefined).then((doc) => {
|
frappe.db.get_doc("POS Settings", undefined).then((doc) => {
|
||||||
const fields = doc.invoice_fields;
|
const fields = doc.invoice_fields;
|
||||||
if (!fields.length) return;
|
if (!fields.length) return;
|
||||||
|
|
||||||
this.$invoice_fields = this.$invoice_fields_section.find('.invoice-fields');
|
this.$invoice_fields = this.$invoice_fields_section.find('.invoice-fields');
|
||||||
this.$invoice_fields.html('');
|
this.$invoice_fields.html('');
|
||||||
const frm = this.events.get_frm();
|
const frm = this.events.get_frm();
|
||||||
@@ -54,8 +55,10 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
`<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>`
|
`<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>`
|
||||||
);
|
);
|
||||||
let df_events = {
|
let df_events = {
|
||||||
onchange: function() { frm.set_value(this.df.fieldname, this.value); }
|
onchange: function() {
|
||||||
}
|
frm.set_value(this.df.fieldname, this.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
if (df.fieldtype == "Button") {
|
if (df.fieldtype == "Button") {
|
||||||
df_events = {
|
df_events = {
|
||||||
click: function() {
|
click: function() {
|
||||||
@@ -63,11 +66,11 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
frm.script_manager.trigger(df.fieldname, frm.doc.doctype, frm.doc.docname);
|
frm.script_manager.trigger(df.fieldname, frm.doc.doctype, frm.doc.docname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
|
this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
|
||||||
df: {
|
df: {
|
||||||
...df,
|
...df,
|
||||||
...df_events
|
...df_events
|
||||||
},
|
},
|
||||||
@@ -75,7 +78,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
render_input: true,
|
render_input: true,
|
||||||
});
|
});
|
||||||
this[`${df.fieldname}_field`].set_value(frm.doc[df.fieldname]);
|
this[`${df.fieldname}_field`].set_value(frm.doc[df.fieldname]);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,13 +98,12 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
[ 7, 8, 9 ],
|
[ 7, 8, 9 ],
|
||||||
[ '.', 0, 'Delete' ]
|
[ '.', 0, 'Delete' ]
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
this.numpad_value = '';
|
this.numpad_value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
on_numpad_clicked($btn) {
|
on_numpad_clicked($btn) {
|
||||||
const me = this;
|
|
||||||
const button_value = $btn.attr('data-button-value');
|
const button_value = $btn.attr('data-button-value');
|
||||||
|
|
||||||
highlight_numpad_btn($btn);
|
highlight_numpad_btn($btn);
|
||||||
@@ -155,10 +157,10 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
me.selected_mode = me[`${mode}_control`];
|
me.selected_mode = me[`${mode}_control`];
|
||||||
me.selected_mode && me.selected_mode.$input.get(0).focus();
|
me.selected_mode && me.selected_mode.$input.get(0).focus();
|
||||||
const current_value = me.selected_mode ? me.selected_mode.get_value() : undefined;
|
const current_value = me.selected_mode ? me.selected_mode.get_value() : undefined;
|
||||||
!current_value && doc.grand_total > doc.paid_amount && me.selected_mode ?
|
!current_value && doc.grand_total > doc.paid_amount && me.selected_mode ?
|
||||||
me.selected_mode.set_value(doc.grand_total - doc.paid_amount) : '';
|
me.selected_mode.set_value(doc.grand_total - doc.paid_amount) : '';
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
frappe.realtime.on("process_phone_payment", function(data) {
|
frappe.realtime.on("process_phone_payment", function(data) {
|
||||||
frappe.dom.unfreeze();
|
frappe.dom.unfreeze();
|
||||||
@@ -168,7 +170,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
|
|
||||||
if (data["ResultCode"] == 0) {
|
if (data["ResultCode"] == 0) {
|
||||||
title = __("Payment Received");
|
title = __("Payment Received");
|
||||||
$('.btn.btn-xs.btn-default[data-fieldname=request_for_payment]').html(`Payment Received`)
|
$('.btn.btn-xs.btn-default[data-fieldname=request_for_payment]').html(`Payment Received`);
|
||||||
me.events.submit_invoice();
|
me.events.submit_invoice();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,10 +180,10 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$payment_modes.on('click', '.shortcut', function(e) {
|
this.$payment_modes.on('click', '.shortcut', () => {
|
||||||
const value = $(this).attr('data-value');
|
const value = $(this).attr('data-value');
|
||||||
me.selected_mode.set_value(value);
|
me.selected_mode.set_value(value);
|
||||||
})
|
});
|
||||||
|
|
||||||
this.$component.on('click', '.submit-order-btn', () => {
|
this.$component.on('click', '.submit-order-btn', () => {
|
||||||
const doc = this.events.get_frm().doc;
|
const doc = this.events.get_frm().doc;
|
||||||
@@ -189,14 +191,14 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const items = doc.items;
|
const items = doc.items;
|
||||||
|
|
||||||
if (paid_amount == 0 || !items.length) {
|
if (paid_amount == 0 || !items.length) {
|
||||||
const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order.")
|
const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order.");
|
||||||
frappe.show_alert({ message, indicator: "orange" });
|
frappe.show_alert({ message, indicator: "orange" });
|
||||||
frappe.utils.play_sound("error");
|
frappe.utils.play_sound("error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.events.submit_invoice();
|
this.events.submit_invoice();
|
||||||
})
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('POS Invoice', 'paid_amount', (frm) => {
|
frappe.ui.form.on('POS Invoice', 'paid_amount', (frm) => {
|
||||||
this.update_totals_section(frm.doc);
|
this.update_totals_section(frm.doc);
|
||||||
@@ -205,7 +207,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible');
|
const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible');
|
||||||
this.attach_cash_shortcuts(frm.doc);
|
this.attach_cash_shortcuts(frm.doc);
|
||||||
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid');
|
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid');
|
||||||
})
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
|
frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
|
||||||
const formatted_currency = format_currency(frm.doc.loyalty_amount, frm.doc.currency);
|
const formatted_currency = format_currency(frm.doc.loyalty_amount, frm.doc.currency);
|
||||||
@@ -239,14 +241,14 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const payment_is_visible = this.$component.is(":visible");
|
const payment_is_visible = this.$component.is(":visible");
|
||||||
let active_mode = this.$payment_modes.find(".border-primary");
|
let active_mode = this.$payment_modes.find(".border-primary");
|
||||||
active_mode = active_mode.length ? active_mode.attr("data-mode") : undefined;
|
active_mode = active_mode.length ? active_mode.attr("data-mode") : undefined;
|
||||||
|
|
||||||
if (!active_mode) return;
|
if (!active_mode) return;
|
||||||
|
|
||||||
const mode_of_payments = Array.from(this.$payment_modes.find(".mode-of-payment")).map(m => $(m).attr("data-mode"));
|
const mode_of_payments = Array.from(this.$payment_modes.find(".mode-of-payment")).map(m => $(m).attr("data-mode"));
|
||||||
const mode_index = mode_of_payments.indexOf(active_mode);
|
const mode_index = mode_of_payments.indexOf(active_mode);
|
||||||
const next_mode_index = (mode_index + 1) % mode_of_payments.length;
|
const next_mode_index = (mode_index + 1) % mode_of_payments.length;
|
||||||
const next_mode_to_be_clicked = this.$payment_modes.find(`.mode-of-payment[data-mode="${mode_of_payments[next_mode_index]}"]`);
|
const next_mode_to_be_clicked = this.$payment_modes.find(`.mode-of-payment[data-mode="${mode_of_payments[next_mode_index]}"]`);
|
||||||
|
|
||||||
if (payment_is_visible && mode_index != next_mode_index) {
|
if (payment_is_visible && mode_index != next_mode_index) {
|
||||||
next_mode_to_be_clicked.click();
|
next_mode_to_be_clicked.click();
|
||||||
}
|
}
|
||||||
@@ -258,14 +260,8 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_numpad(show) {
|
toggle_numpad() {
|
||||||
// if (show) {
|
// pass
|
||||||
// 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() {
|
render_payment_section() {
|
||||||
@@ -309,9 +305,8 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const payments = doc.payments;
|
const payments = doc.payments;
|
||||||
const currency = doc.currency;
|
const currency = doc.currency;
|
||||||
|
|
||||||
this.$payment_modes.html(
|
this.$payment_modes.html(`${
|
||||||
`${
|
payments.map((p, i) => {
|
||||||
payments.map((p, i) => {
|
|
||||||
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
|
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
|
||||||
const payment_type = p.type;
|
const payment_type = p.type;
|
||||||
const margin = i % 2 === 0 ? 'pr-2' : 'pl-2';
|
const margin = i % 2 === 0 ? 'pr-2' : 'pl-2';
|
||||||
@@ -319,16 +314,15 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
`<div class="payment-mode-wrapper">
|
`<div class="payment-mode-wrapper">
|
||||||
<div class="mode-of-payment" data-mode="${mode}" data-payment-type="${payment_type}">
|
<div class="mode-of-payment" data-mode="${mode}" data-payment-type="${payment_type}">
|
||||||
${p.mode_of_payment}
|
${p.mode_of_payment}
|
||||||
<div class="${mode}-amount pay-amount">${amount}</div>
|
<div class="${mode}-amount pay-amount">${amount}</div>
|
||||||
<div class="${mode} mode-of-payment-control"></div>
|
<div class="${mode} mode-of-payment-control"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
)
|
);
|
||||||
}).join('')
|
}).join('')
|
||||||
}`
|
}`);
|
||||||
)
|
|
||||||
|
|
||||||
payments.forEach(p => {
|
payments.forEach(p => {
|
||||||
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
|
const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
|
||||||
@@ -359,10 +353,10 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
|
this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
this.render_loyalty_points_payment_mode();
|
this.render_loyalty_points_payment_mode();
|
||||||
|
|
||||||
this.attach_cash_shortcuts(doc);
|
this.attach_cash_shortcuts(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,12 +370,12 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after(
|
this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after(
|
||||||
`<div class="cash-shortcuts">
|
`<div class="cash-shortcuts">
|
||||||
${
|
${
|
||||||
shortcuts.map(s => {
|
shortcuts.map(s => {
|
||||||
return `<div class="shortcut" data-value="${s}">${format_currency(s, currency, 0)}</div>`
|
return `<div class="shortcut" data-value="${s}">${format_currency(s, currency, 0)}</div>`;
|
||||||
}).join('')
|
}).join('')
|
||||||
}
|
}
|
||||||
</div>`
|
</div>`
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get_cash_shortcuts(grand_total) {
|
get_cash_shortcuts(grand_total) {
|
||||||
@@ -393,13 +387,13 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const get_nearest = (amount, x) => {
|
const get_nearest = (amount, x) => {
|
||||||
let nearest_x = Math.ceil((amount / x)) * x;
|
let nearest_x = Math.ceil((amount / x)) * x;
|
||||||
return nearest_x === amount ? nearest_x + x : nearest_x;
|
return nearest_x === amount ? nearest_x + x : nearest_x;
|
||||||
}
|
};
|
||||||
|
|
||||||
return steps.reduce((finalArr, x) => {
|
return steps.reduce((finalArr, x) => {
|
||||||
let nearest_x = get_nearest(grand_total, x);
|
let nearest_x = get_nearest(grand_total, x);
|
||||||
nearest_x = finalArr.indexOf(nearest_x) != -1 ? nearest_x + x : nearest_x;
|
nearest_x = finalArr.indexOf(nearest_x) != -1 ? nearest_x + x : nearest_x;
|
||||||
return [...finalArr, nearest_x];
|
return [...finalArr, nearest_x];
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_loyalty_points_payment_mode() {
|
render_loyalty_points_payment_mode() {
|
||||||
@@ -408,7 +402,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const { loyalty_program, loyalty_points, conversion_factor } = this.events.get_customer_details();
|
const { loyalty_program, loyalty_points, conversion_factor } = this.events.get_customer_details();
|
||||||
|
|
||||||
this.$payment_modes.find(`.mode-of-payment[data-mode="loyalty-amount"]`).parent().remove();
|
this.$payment_modes.find(`.mode-of-payment[data-mode="loyalty-amount"]`).parent().remove();
|
||||||
|
|
||||||
if (!loyalty_program) return;
|
if (!loyalty_program) return;
|
||||||
|
|
||||||
let description, read_only, max_redeemable_amount;
|
let description, read_only, max_redeemable_amount;
|
||||||
@@ -416,7 +410,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
description = __("You don't have enough points to redeem.");
|
description = __("You don't have enough points to redeem.");
|
||||||
read_only = true;
|
read_only = true;
|
||||||
} else {
|
} else {
|
||||||
max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc))
|
max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc));
|
||||||
description = __("You can redeem upto {0}.", [format_currency(max_redeemable_amount)]);
|
description = __("You can redeem upto {0}.", [format_currency(max_redeemable_amount)]);
|
||||||
read_only = false;
|
read_only = false;
|
||||||
}
|
}
|
||||||
@@ -432,7 +426,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
<div class="loyalty-amount mode-of-payment-control"></div>
|
<div class="loyalty-amount mode-of-payment-control"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
)
|
);
|
||||||
|
|
||||||
this['loyalty-amount_control'] = frappe.ui.form.make_control({
|
this['loyalty-amount_control'] = frappe.ui.form.make_control({
|
||||||
df: {
|
df: {
|
||||||
@@ -474,7 +468,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
`<div class="w-full pr-2">
|
`<div class="w-full pr-2">
|
||||||
<div class="add-mode-of-payment w-half text-grey mb-4 no-select pointer">+ Add Payment Method</div>
|
<div class="add-mode-of-payment w-half text-grey mb-4 no-select pointer">+ Add Payment Method</div>
|
||||||
</div>`
|
</div>`
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_totals_section(doc) {
|
update_totals_section(doc) {
|
||||||
@@ -482,7 +476,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const paid_amount = doc.paid_amount;
|
const paid_amount = doc.paid_amount;
|
||||||
const remaining = doc.grand_total - doc.paid_amount;
|
const remaining = doc.grand_total - doc.paid_amount;
|
||||||
const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined;
|
const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined;
|
||||||
const currency = doc.currency
|
const currency = doc.currency;
|
||||||
const label = change ? __('Change') : __('To Be Paid');
|
const label = change ? __('Change') : __('To Be Paid');
|
||||||
|
|
||||||
this.$totals.html(
|
this.$totals.html(
|
||||||
@@ -500,10 +494,10 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
<div class="total-label">${label}</div>
|
<div class="total-label">${label}</div>
|
||||||
<div class="value">${format_currency(change || remaining, currency)}</div>
|
<div class="value">${format_currency(change || remaining, currency)}</div>
|
||||||
</div>`
|
</div>`
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_component(show) {
|
toggle_component(show) {
|
||||||
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
|
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
Reference in New Issue
Block a user