mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-13 14:41:53 +00:00
fix: resolve backport merge conflicts for #57615
This commit is contained in:
@@ -96,8 +96,6 @@ frappe.ui.form.on("Subscription", {
|
||||
});
|
||||
},
|
||||
});
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
frappe.ui.form.on("Subscription Plan Detail", {
|
||||
plan: function (frm, cdt, cdn) {
|
||||
@@ -124,81 +122,3 @@ frappe.ui.form.on("Subscription Plan Detail", {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Status -> colour and label for the calendar heatmap. Keys are Title-case to
|
||||
// match the value frappe-charts shows in its hover tooltip.
|
||||
const HEATMAP_COLORS = {
|
||||
Paid: "#39d353",
|
||||
Unpaid: "#388bfd",
|
||||
Overdue: "#f0883e",
|
||||
Cancelled: "#f85149",
|
||||
Refunded: "#a371f7",
|
||||
Planned: "#87ceeb",
|
||||
};
|
||||
|
||||
// Days inside the window but outside the subscription's active span stay faded.
|
||||
const EMPTY_COLOR = "#ebedf0";
|
||||
|
||||
function title_case(status) {
|
||||
return status.charAt(0).toUpperCase() + status.slice(1);
|
||||
}
|
||||
|
||||
function render_heatmap($wrapper, days, doc) {
|
||||
const data_points = {};
|
||||
days.forEach((day) => {
|
||||
data_points[day.date] = title_case(day.status);
|
||||
});
|
||||
|
||||
$wrapper.empty();
|
||||
const chart_el = $('<div class="subscription-billing-heatmap"></div>').appendTo($wrapper)[0];
|
||||
|
||||
new frappe.Chart(chart_el, {
|
||||
type: "heatmap",
|
||||
data: {
|
||||
dataPoints: data_points,
|
||||
start: new Date(days[0].date),
|
||||
end: new Date(days[days.length - 1].date),
|
||||
},
|
||||
discreteDomains: 1,
|
||||
showLegend: 0,
|
||||
// frappe-charts only does an intensity scale; we recolour each square by
|
||||
// its own status below, so the scale colours are placeholders.
|
||||
colors: ["#ebedf0", "#ebedf0", "#ebedf0", "#ebedf0", "#ebedf0"],
|
||||
});
|
||||
|
||||
// Paint every day square with its status colour (data-value holds the status).
|
||||
// The chart re-renders once for its entry animation, so repaint on each redraw.
|
||||
const within_subscription = (date) =>
|
||||
(!doc.start_date || date >= doc.start_date) && (!doc.end_date || date <= doc.end_date);
|
||||
|
||||
const paint = () =>
|
||||
chart_el.querySelectorAll("[data-date]").forEach((square) => {
|
||||
const status = square.getAttribute("data-value");
|
||||
if (status === "Planned" && !within_subscription(square.getAttribute("data-date"))) {
|
||||
// Outside the subscription's span: render blank and drop the status so the
|
||||
// hover tooltip shows only the date, not "Planned".
|
||||
square.setAttribute("fill", EMPTY_COLOR);
|
||||
square.setAttribute("data-value", "");
|
||||
return;
|
||||
}
|
||||
square.setAttribute("fill", HEATMAP_COLORS[status] || EMPTY_COLOR);
|
||||
});
|
||||
|
||||
paint();
|
||||
new MutationObserver(paint).observe(chart_el, { childList: true, subtree: true });
|
||||
|
||||
const legend = Object.keys(HEATMAP_COLORS)
|
||||
.map(
|
||||
(status) =>
|
||||
`<span style="display:inline-flex;align-items:center;gap:4px;margin-right:12px;">
|
||||
<span style="width:11px;height:11px;border-radius:2px;background:${HEATMAP_COLORS[status]};"></span>
|
||||
${__(status)}
|
||||
</span>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
$(`<div style="margin-top:8px;font-size:11px;color:var(--text-muted);">${legend}</div>`).appendTo(
|
||||
$wrapper
|
||||
);
|
||||
}
|
||||
>>>>>>> 7febc28ed6 (feat: auto-fill subscription accounting dimensions from plan with item fallback (#57615))
|
||||
|
||||
@@ -830,7 +830,7 @@ def get_item_dimension(
|
||||
|
||||
selling = item_defaults.get("selling_cost_center")
|
||||
buying = item_defaults.get("buying_cost_center")
|
||||
if party_type == PARTY_SUPPLIER:
|
||||
if party_type == "Supplier":
|
||||
return buying or selling
|
||||
return selling or buying
|
||||
|
||||
|
||||
@@ -809,133 +809,6 @@ class TestSubscription(ERPNextTestSuite):
|
||||
)
|
||||
self.assertEqual(len(subscription.invoices), 0)
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
def test_generate_invoice_at_migration_patch(self):
|
||||
from erpnext.patches.v16_0.migrate_subscription_generate_invoice_at import VALUE_MAP, execute
|
||||
|
||||
subscription = create_subscription(start_date=add_days(nowdate(), 10))
|
||||
for old_value, new_value in VALUE_MAP.items():
|
||||
frappe.db.set_value("Subscription", subscription.name, "generate_invoice_at", old_value)
|
||||
execute()
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Subscription", subscription.name, "generate_invoice_at"), new_value
|
||||
)
|
||||
|
||||
def test_next_billing_period_populated_for_prepaid(self):
|
||||
subscription = create_subscription(
|
||||
start_date=add_days(nowdate(), 10),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
)
|
||||
self.assertEqual(getdate(subscription.next_billing_period_start), getdate(add_days(nowdate(), 10)))
|
||||
self.assertGreater(
|
||||
getdate(subscription.next_billing_period_end), getdate(subscription.next_billing_period_start)
|
||||
)
|
||||
|
||||
def test_status_becomes_refunded_when_only_invoice_credited(self):
|
||||
subscription = create_subscription(
|
||||
start_date=nowdate(),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
submit_invoice=1,
|
||||
)
|
||||
subscription.process(posting_date=nowdate())
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
|
||||
make_full_credit_note(subscription.get_current_invoice().name)
|
||||
|
||||
subscription.reload()
|
||||
self.assertEqual(subscription.status, "Refunded")
|
||||
|
||||
def test_status_stays_unpaid_when_one_of_two_invoices_credited(self):
|
||||
subscription = create_subscription(
|
||||
start_date=add_months(nowdate(), -2),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
submit_invoice=1,
|
||||
generate_new_invoices_past_due_date=1,
|
||||
)
|
||||
invoices = frappe.get_all(
|
||||
"Sales Invoice",
|
||||
filters={"subscription": subscription.name, "docstatus": 1, "is_return": 0},
|
||||
pluck="name",
|
||||
order_by="from_date asc",
|
||||
)
|
||||
self.assertGreaterEqual(len(invoices), 2)
|
||||
|
||||
make_full_credit_note(invoices[0])
|
||||
|
||||
subscription.reload()
|
||||
self.assertNotEqual(subscription.status, "Refunded")
|
||||
|
||||
def test_refunded_reverts_to_active_after_full_settlement(self):
|
||||
subscription = create_subscription(
|
||||
start_date=nowdate(),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
submit_invoice=1,
|
||||
)
|
||||
subscription.process(posting_date=nowdate())
|
||||
invoice = subscription.get_current_invoice()
|
||||
make_full_credit_note(invoice.name)
|
||||
|
||||
subscription.reload()
|
||||
self.assertEqual(subscription.status, "Refunded")
|
||||
|
||||
invoice.db_set("status", "Paid")
|
||||
invoice.db_set("outstanding_amount", 0)
|
||||
subscription.process()
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
def test_heatmap_spans_twelve_months_from_start_month(self):
|
||||
start_date = getdate("2024-03-14")
|
||||
subscription = create_subscription(start_date=start_date)
|
||||
heatmap = subscription.get_billing_heatmap()
|
||||
self.assertEqual(getdate(heatmap[0]["date"]), get_first_day(start_date))
|
||||
self.assertEqual(
|
||||
getdate(heatmap[-1]["date"]), get_last_day(add_months(get_first_day(start_date), 11))
|
||||
)
|
||||
self.assertIn("status", heatmap[0])
|
||||
|
||||
def test_heatmap_marks_paid_days_green(self):
|
||||
subscription = create_subscription(
|
||||
start_date=nowdate(),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
submit_invoice=1,
|
||||
)
|
||||
subscription.process(posting_date=nowdate())
|
||||
invoice = subscription.get_current_invoice()
|
||||
invoice.db_set("status", "Paid")
|
||||
invoice.db_set("outstanding_amount", 0)
|
||||
|
||||
subscription.reload()
|
||||
cells = {cell["date"]: cell for cell in subscription.get_billing_heatmap()}
|
||||
self.assertEqual(cells[str(getdate(invoice.from_date))]["status"], "paid")
|
||||
|
||||
def test_heatmap_marks_future_planned_days(self):
|
||||
subscription = create_subscription(
|
||||
start_date=nowdate(),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
)
|
||||
today = getdate(nowdate())
|
||||
planned = [
|
||||
cell
|
||||
for cell in subscription.get_billing_heatmap()
|
||||
if cell["status"] == "planned" and getdate(cell["date"]) > today
|
||||
]
|
||||
self.assertTrue(planned)
|
||||
|
||||
def test_heatmap_marks_refunded_days_for_credited_periods(self):
|
||||
subscription = create_subscription(
|
||||
start_date=nowdate(),
|
||||
generate_invoice_at="Prepaid (bill at period start)",
|
||||
submit_invoice=1,
|
||||
)
|
||||
subscription.process(posting_date=nowdate())
|
||||
invoice = subscription.get_current_invoice()
|
||||
make_full_credit_note(invoice.name)
|
||||
|
||||
subscription.reload()
|
||||
cells = {cell["date"]: cell for cell in subscription.get_billing_heatmap()}
|
||||
self.assertEqual(cells[str(getdate(invoice.from_date))]["status"], "refunded")
|
||||
|
||||
def test_plan_dimensions_resolve_from_plan_then_item(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
@@ -957,6 +830,7 @@ class TestSubscription(ERPNextTestSuite):
|
||||
"item_defaults": [
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"default_warehouse": "_Test Warehouse - _TC",
|
||||
"selling_cost_center": "_Test Cost Center - _TC",
|
||||
"buying_cost_center": "_Test Cost Center 2 - _TC",
|
||||
}
|
||||
@@ -978,16 +852,6 @@ class TestSubscription(ERPNextTestSuite):
|
||||
self.assertNotIn("cost_center", get_plan_dimensions("_Test Sub Plan No CC"))
|
||||
|
||||
|
||||
def make_full_credit_note(invoice_name):
|
||||
from erpnext.accounts.doctype.sales_invoice.mapper import make_sales_return
|
||||
|
||||
credit_note = make_sales_return(invoice_name)
|
||||
credit_note.insert()
|
||||
credit_note.submit()
|
||||
return credit_note
|
||||
|
||||
>>>>>>> 7febc28ed6 (feat: auto-fill subscription accounting dimensions from plan with item fallback (#57615))
|
||||
|
||||
def make_plans():
|
||||
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
|
||||
create_plan(plan_name="_Test Plan Name 2", cost=1999, currency="INR")
|
||||
|
||||
Reference in New Issue
Block a user