Merge branch 'develop' of https://github.com/frappe/erpnext into revert_dynamic_splitting
This commit is contained in:
commit
96dcfba65a
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
- name: Setup dependencies
|
- name: Setup dependencies
|
||||||
run: |
|
run: |
|
||||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||||
|
|||||||
@ -76,6 +76,7 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
|
|||||||
"deposit": 100,
|
"deposit": 100,
|
||||||
"bank_account": self.bank_account,
|
"bank_account": self.bank_account,
|
||||||
"reference_number": "123",
|
"reference_number": "123",
|
||||||
|
"currency": "INR",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.save()
|
.save()
|
||||||
|
|||||||
@ -49,6 +49,24 @@ class BankTransaction(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_duplicate_references()
|
self.validate_duplicate_references()
|
||||||
|
self.validate_currency()
|
||||||
|
|
||||||
|
def validate_currency(self):
|
||||||
|
"""
|
||||||
|
Bank Transaction should be on the same currency as the Bank Account.
|
||||||
|
"""
|
||||||
|
if self.currency and self.bank_account:
|
||||||
|
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
|
||||||
|
account_currency = frappe.get_cached_value("Account", account, "account_currency")
|
||||||
|
|
||||||
|
if self.currency != account_currency:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
|
||||||
|
).format(
|
||||||
|
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def set_status(self):
|
def set_status(self):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
|
|||||||
@ -126,7 +126,7 @@
|
|||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"label": "Tax Rate",
|
||||||
"oldfieldname": "rate",
|
"oldfieldname": "rate",
|
||||||
"oldfieldtype": "Currency"
|
"oldfieldtype": "Currency"
|
||||||
},
|
},
|
||||||
@ -230,7 +230,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-05 20:04:36.618240",
|
"modified": "2024-01-14 10:04:36.618240",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges",
|
"name": "Purchase Taxes and Charges",
|
||||||
|
|||||||
@ -16,6 +16,7 @@ from frappe.utils.data import (
|
|||||||
date_diff,
|
date_diff,
|
||||||
flt,
|
flt,
|
||||||
get_last_day,
|
get_last_day,
|
||||||
|
get_link_to_form,
|
||||||
getdate,
|
getdate,
|
||||||
nowdate,
|
nowdate,
|
||||||
)
|
)
|
||||||
@ -317,6 +318,37 @@ class Subscription(Document):
|
|||||||
if self.is_new():
|
if self.is_new():
|
||||||
self.set_subscription_status()
|
self.set_subscription_status()
|
||||||
|
|
||||||
|
self.validate_party_billing_currency()
|
||||||
|
|
||||||
|
def validate_party_billing_currency(self):
|
||||||
|
"""
|
||||||
|
Subscription should be of the same currency as the Party's default billing currency or company default.
|
||||||
|
"""
|
||||||
|
if self.party:
|
||||||
|
party_billing_currency = frappe.get_cached_value(
|
||||||
|
self.party_type, self.party, "default_currency"
|
||||||
|
) or frappe.get_cached_value("Company", self.company, "default_currency")
|
||||||
|
|
||||||
|
plans = [x.plan for x in self.plans]
|
||||||
|
subscription_plan_currencies = frappe.db.get_all(
|
||||||
|
"Subscription Plan", filters={"name": ("in", plans)}, fields=["name", "currency"]
|
||||||
|
)
|
||||||
|
unsupported_plans = []
|
||||||
|
for x in subscription_plan_currencies:
|
||||||
|
if x.currency != party_billing_currency:
|
||||||
|
unsupported_plans.append("{0}".format(get_link_to_form("Subscription Plan", x.name)))
|
||||||
|
|
||||||
|
if unsupported_plans:
|
||||||
|
unsupported_plans = [
|
||||||
|
_(
|
||||||
|
"Below Subscription Plans are of different currency to the party default billing currency/Company currency: {0}"
|
||||||
|
).format(frappe.bold(party_billing_currency))
|
||||||
|
] + unsupported_plans
|
||||||
|
|
||||||
|
frappe.throw(
|
||||||
|
unsupported_plans, frappe.ValidationError, "Unsupported Subscription Plans", as_list=True
|
||||||
|
)
|
||||||
|
|
||||||
def validate_trial_period(self) -> None:
|
def validate_trial_period(self) -> None:
|
||||||
"""
|
"""
|
||||||
Runs sanity checks on trial period dates for the `Subscription`
|
Runs sanity checks on trial period dates for the `Subscription`
|
||||||
|
|||||||
@ -460,11 +460,13 @@ class TestSubscription(FrappeTestCase):
|
|||||||
self.assertEqual(len(subscription.invoices), 1)
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
|
||||||
def test_multi_currency_subscription(self):
|
def test_multi_currency_subscription(self):
|
||||||
|
party = "_Test Subscription Customer"
|
||||||
|
frappe.db.set_value("Customer", party, "default_currency", "USD")
|
||||||
subscription = create_subscription(
|
subscription = create_subscription(
|
||||||
start_date="2018-01-01",
|
start_date="2018-01-01",
|
||||||
generate_invoice_at="Beginning of the current subscription period",
|
generate_invoice_at="Beginning of the current subscription period",
|
||||||
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1}],
|
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1, "currency": "USD"}],
|
||||||
party="_Test Subscription Customer",
|
party=party,
|
||||||
)
|
)
|
||||||
|
|
||||||
subscription.process()
|
subscription.process()
|
||||||
@ -528,13 +530,21 @@ class TestSubscription(FrappeTestCase):
|
|||||||
|
|
||||||
|
|
||||||
def make_plans():
|
def make_plans():
|
||||||
create_plan(plan_name="_Test Plan Name", cost=900)
|
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
|
||||||
create_plan(plan_name="_Test Plan Name 2", cost=1999)
|
create_plan(plan_name="_Test Plan Name 2", cost=1999, currency="INR")
|
||||||
create_plan(
|
create_plan(
|
||||||
plan_name="_Test Plan Name 3", cost=1999, billing_interval="Day", billing_interval_count=14
|
plan_name="_Test Plan Name 3",
|
||||||
|
cost=1999,
|
||||||
|
billing_interval="Day",
|
||||||
|
billing_interval_count=14,
|
||||||
|
currency="INR",
|
||||||
)
|
)
|
||||||
create_plan(
|
create_plan(
|
||||||
plan_name="_Test Plan Name 4", cost=20000, billing_interval="Month", billing_interval_count=3
|
plan_name="_Test Plan Name 4",
|
||||||
|
cost=20000,
|
||||||
|
billing_interval="Month",
|
||||||
|
billing_interval_count=3,
|
||||||
|
currency="INR",
|
||||||
)
|
)
|
||||||
create_plan(
|
create_plan(
|
||||||
plan_name="_Test Plan Multicurrency", cost=50, billing_interval="Month", currency="USD"
|
plan_name="_Test Plan Multicurrency", cost=50, billing_interval="Month", currency="USD"
|
||||||
|
|||||||
@ -41,7 +41,8 @@
|
|||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
"options": "Currency"
|
"options": "Currency",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
@ -148,10 +149,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-10 15:24:15.794477",
|
"modified": "2024-01-14 17:59:34.687977",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription Plan",
|
"name": "Subscription Plan",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -193,5 +195,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ class SubscriptionPlan(Document):
|
|||||||
billing_interval_count: DF.Int
|
billing_interval_count: DF.Int
|
||||||
cost: DF.Currency
|
cost: DF.Currency
|
||||||
cost_center: DF.Link | None
|
cost_center: DF.Link | None
|
||||||
currency: DF.Link | None
|
currency: DF.Link
|
||||||
item: DF.Link
|
item: DF.Link
|
||||||
payment_gateway: DF.Link | None
|
payment_gateway: DF.Link | None
|
||||||
plan_name: DF.Data
|
plan_name: DF.Data
|
||||||
|
|||||||
@ -84,10 +84,6 @@ function get_filters() {
|
|||||||
options: budget_against_options,
|
options: budget_against_options,
|
||||||
default: "Cost Center",
|
default: "Cost Center",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
get_data: function() {
|
|
||||||
console.log(this.options);
|
|
||||||
return ["Emacs", "Rocks"];
|
|
||||||
},
|
|
||||||
on_change: function() {
|
on_change: function() {
|
||||||
frappe.query_report.set_filter_value("budget_against_filter", []);
|
frappe.query_report.set_filter_value("budget_against_filter", []);
|
||||||
frappe.query_report.refresh();
|
frappe.query_report.refresh();
|
||||||
|
|||||||
@ -376,6 +376,10 @@ class PartyLedgerSummaryReport(object):
|
|||||||
if not income_or_expense_accounts:
|
if not income_or_expense_accounts:
|
||||||
# prevent empty 'in' condition
|
# prevent empty 'in' condition
|
||||||
income_or_expense_accounts.append("")
|
income_or_expense_accounts.append("")
|
||||||
|
else:
|
||||||
|
# escape '%' in account name
|
||||||
|
# ignoring frappe.db.escape as it replaces single quotes with double quotes
|
||||||
|
income_or_expense_accounts = [x.replace("%", "%%") for x in income_or_expense_accounts]
|
||||||
|
|
||||||
accounts_query = (
|
accounts_query = (
|
||||||
qb.from_(gl)
|
qb.from_(gl)
|
||||||
|
|||||||
@ -448,6 +448,10 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
|||||||
for gle in gl_entries:
|
for gle in gl_entries:
|
||||||
group_by_value = gle.get(group_by)
|
group_by_value = gle.get(group_by)
|
||||||
gle.voucher_type = _(gle.voucher_type)
|
gle.voucher_type = _(gle.voucher_type)
|
||||||
|
gle.voucher_subtype = _(gle.voucher_subtype)
|
||||||
|
gle.against_voucher_type = _(gle.against_voucher_type)
|
||||||
|
gle.remarks = _(gle.remarks)
|
||||||
|
gle.party_type = _(gle.party_type)
|
||||||
|
|
||||||
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
|
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
|
||||||
if not group_by_voucher_consolidated:
|
if not group_by_voucher_consolidated:
|
||||||
|
|||||||
@ -59,7 +59,21 @@ frappe.query_reports["Item-wise Sales Register"] = {
|
|||||||
"fieldname": "group_by",
|
"fieldname": "group_by",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"options": ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"]
|
"options": ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "income_account",
|
||||||
|
"label": __("Income Account"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
get_query: () => {
|
||||||
|
let company = frappe.query_report.get_filter_value('company');
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'company': company,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
|
|||||||
@ -83,9 +83,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
|
|||||||
"company": d.company,
|
"company": d.company,
|
||||||
"sales_order": d.sales_order,
|
"sales_order": d.sales_order,
|
||||||
"delivery_note": d.delivery_note,
|
"delivery_note": d.delivery_note,
|
||||||
"income_account": d.unrealized_profit_loss_account
|
"income_account": get_income_account(d),
|
||||||
if d.is_internal_customer == 1
|
|
||||||
else d.income_account,
|
|
||||||
"cost_center": d.cost_center,
|
"cost_center": d.cost_center,
|
||||||
"stock_qty": d.stock_qty,
|
"stock_qty": d.stock_qty,
|
||||||
"stock_uom": d.stock_uom,
|
"stock_uom": d.stock_uom,
|
||||||
@ -150,6 +148,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
|
|||||||
return columns, data, None, None, None, skip_total_row
|
return columns, data, None, None, None, skip_total_row
|
||||||
|
|
||||||
|
|
||||||
|
def get_income_account(row):
|
||||||
|
if row.enable_deferred_revenue:
|
||||||
|
return row.deferred_revenue_account
|
||||||
|
elif row.is_internal_customer == 1:
|
||||||
|
return row.unrealized_profit_loss_account
|
||||||
|
else:
|
||||||
|
return row.income_account
|
||||||
|
|
||||||
|
|
||||||
def get_columns(additional_table_columns, filters):
|
def get_columns(additional_table_columns, filters):
|
||||||
columns = []
|
columns = []
|
||||||
|
|
||||||
@ -358,6 +365,13 @@ def get_conditions(filters, additional_conditions=None):
|
|||||||
if filters.get("item_group"):
|
if filters.get("item_group"):
|
||||||
conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
|
conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
|
||||||
|
|
||||||
|
if filters.get("income_account"):
|
||||||
|
conditions += """
|
||||||
|
and (ifnull(`tabSales Invoice Item`.income_account, '') = %(income_account)s
|
||||||
|
or ifnull(`tabSales Invoice Item`.deferred_revenue_account, '') = %(income_account)s
|
||||||
|
or ifnull(`tabSales Invoice`.unrealized_profit_loss_account, '') = %(income_account)s)
|
||||||
|
"""
|
||||||
|
|
||||||
if not filters.get("group_by"):
|
if not filters.get("group_by"):
|
||||||
conditions += (
|
conditions += (
|
||||||
"ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
|
"ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
|
||||||
@ -399,6 +413,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
|||||||
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
|
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
|
||||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||||
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
|
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
|
||||||
|
`tabSales Invoice Item`.enable_deferred_revenue, `tabSales Invoice Item`.deferred_revenue_account,
|
||||||
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
|
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
|
||||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||||
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
|
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
|
||||||
|
|||||||
@ -421,23 +421,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
meta = frappe.get_meta(doctype, cached=True)
|
meta = frappe.get_meta(doctype, cached=True)
|
||||||
searchfields = meta.get_search_fields()
|
searchfields = meta.get_search_fields()
|
||||||
|
|
||||||
query = get_batches_from_stock_ledger_entries(searchfields, txt, filters)
|
batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len)
|
||||||
bundle_query = get_batches_from_serial_and_batch_bundle(searchfields, txt, filters)
|
batches.extend(
|
||||||
|
get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)
|
||||||
data = (
|
|
||||||
frappe.qb.from_((query) + (bundle_query))
|
|
||||||
.select("batch_no", "qty", "manufacturing_date", "expiry_date")
|
|
||||||
.offset(start)
|
|
||||||
.limit(page_len)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for field in searchfields:
|
filtered_batches = get_filterd_batches(batches)
|
||||||
data = data.select(field)
|
|
||||||
|
|
||||||
data = data.run()
|
return filtered_batches
|
||||||
data = get_filterd_batches(data)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def get_filterd_batches(data):
|
def get_filterd_batches(data):
|
||||||
@ -457,7 +448,7 @@ def get_filterd_batches(data):
|
|||||||
return filterd_batch
|
return filterd_batch
|
||||||
|
|
||||||
|
|
||||||
def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
|
def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, page_len=100):
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
batch_table = frappe.qb.DocType("Batch")
|
batch_table = frappe.qb.DocType("Batch")
|
||||||
|
|
||||||
@ -479,6 +470,8 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
|
|||||||
& (stock_ledger_entry.batch_no.isnotnull())
|
& (stock_ledger_entry.batch_no.isnotnull())
|
||||||
)
|
)
|
||||||
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
|
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
|
||||||
|
.offset(start)
|
||||||
|
.limit(page_len)
|
||||||
)
|
)
|
||||||
|
|
||||||
query = query.select(
|
query = query.select(
|
||||||
@ -493,16 +486,16 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
|
|||||||
query = query.select(batch_table[field])
|
query = query.select(batch_table[field])
|
||||||
|
|
||||||
if txt:
|
if txt:
|
||||||
txt_condition = batch_table.name.like(txt)
|
txt_condition = batch_table.name.like("%{0}%".format(txt))
|
||||||
for field in searchfields + ["name"]:
|
for field in searchfields + ["name"]:
|
||||||
txt_condition |= batch_table[field].like(txt)
|
txt_condition |= batch_table[field].like("%{0}%".format(txt))
|
||||||
|
|
||||||
query = query.where(txt_condition)
|
query = query.where(txt_condition)
|
||||||
|
|
||||||
return query
|
return query.run(as_list=1) or []
|
||||||
|
|
||||||
|
|
||||||
def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
|
def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0, page_len=100):
|
||||||
bundle = frappe.qb.DocType("Serial and Batch Entry")
|
bundle = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
batch_table = frappe.qb.DocType("Batch")
|
batch_table = frappe.qb.DocType("Batch")
|
||||||
@ -527,6 +520,8 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
|
|||||||
& (stock_ledger_entry.serial_and_batch_bundle.isnotnull())
|
& (stock_ledger_entry.serial_and_batch_bundle.isnotnull())
|
||||||
)
|
)
|
||||||
.groupby(bundle.batch_no, bundle.warehouse)
|
.groupby(bundle.batch_no, bundle.warehouse)
|
||||||
|
.offset(start)
|
||||||
|
.limit(page_len)
|
||||||
)
|
)
|
||||||
|
|
||||||
bundle_query = bundle_query.select(
|
bundle_query = bundle_query.select(
|
||||||
@ -541,13 +536,13 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
|
|||||||
bundle_query = bundle_query.select(batch_table[field])
|
bundle_query = bundle_query.select(batch_table[field])
|
||||||
|
|
||||||
if txt:
|
if txt:
|
||||||
txt_condition = batch_table.name.like(txt)
|
txt_condition = batch_table.name.like("%{0}%".format(txt))
|
||||||
for field in searchfields + ["name"]:
|
for field in searchfields + ["name"]:
|
||||||
txt_condition |= batch_table[field].like(txt)
|
txt_condition |= batch_table[field].like("%{0}%".format(txt))
|
||||||
|
|
||||||
bundle_query = bundle_query.where(txt_condition)
|
bundle_query = bundle_query.where(txt_condition)
|
||||||
|
|
||||||
return bundle_query
|
return bundle_query.run(as_list=1)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@ -21,7 +21,7 @@ $.extend(erpnext, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
toggle_naming_series: function() {
|
toggle_naming_series: function() {
|
||||||
if(cur_frm.fields_dict.naming_series) {
|
if(cur_frm && cur_frm.fields_dict.naming_series) {
|
||||||
cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal?true:false);
|
cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal?true:false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -126,9 +126,9 @@ erpnext.SalesFunnel = class SalesFunnel {
|
|||||||
if (me.options.chart == 'sales_funnel'){
|
if (me.options.chart == 'sales_funnel'){
|
||||||
me.render_funnel();
|
me.render_funnel();
|
||||||
} else if (me.options.chart == 'opp_by_lead_source'){
|
} else if (me.options.chart == 'opp_by_lead_source'){
|
||||||
me.render_chart("Sales Opportunities by Source");
|
me.render_chart(__("Sales Opportunities by Source"));
|
||||||
} else if (me.options.chart == 'sales_pipeline'){
|
} else if (me.options.chart == 'sales_pipeline'){
|
||||||
me.render_chart("Sales Pipeline by Stage");
|
me.render_chart(__("Sales Pipeline by Stage"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Make",
|
"label": "Manufacturer",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) {
|
frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) {
|
||||||
var page = frappe.ui.make_app_page({
|
var page = frappe.ui.make_app_page({
|
||||||
parent: wrapper,
|
parent: wrapper,
|
||||||
title: 'Warehouse Capacity Summary',
|
title: __('Warehouse Capacity Summary'),
|
||||||
single_column: true
|
single_column: true
|
||||||
});
|
});
|
||||||
page.set_secondary_action('Refresh', () => page.capacity_dashboard.refresh(), 'refresh');
|
page.set_secondary_action('Refresh', () => page.capacity_dashboard.refresh(), 'refresh');
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
<div class="dashboard-list-item" style="padding: 12px 15px;">
|
<div class="dashboard-list-item" style="padding: 12px 15px;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
||||||
Warehouse
|
{{ __("Warehouse") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
||||||
Item
|
{{ __("Item") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1 text-muted" style="margin-top: 8px;">
|
<div class="col-sm-1 text-muted" style="margin-top: 8px;">
|
||||||
Stock Capacity
|
{{ __("Stock Capacity") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
||||||
Balance Stock Qty
|
{{ __("Balance Stock Qty") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
<div class="col-sm-2 text-muted" style="margin-top: 8px;">
|
||||||
% Occupied
|
{{ __("% Occupied") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Dict, List, Tuple, Union
|
from typing import Dict, Iterator, List, Tuple, Union
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@ -231,25 +231,32 @@ class FIFOSlots:
|
|||||||
consumed/updated and maintained via FIFO. **
|
consumed/updated and maintained via FIFO. **
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
if self.sle is None:
|
|
||||||
self.sle = self.__get_stock_ledger_entries()
|
|
||||||
|
|
||||||
for d in self.sle:
|
stock_ledger_entries = self.sle
|
||||||
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
|
||||||
|
|
||||||
if d.voucher_type == "Stock Reconciliation":
|
with frappe.db.unbuffered_cursor():
|
||||||
# get difference in qty shift as actual qty
|
if stock_ledger_entries is None:
|
||||||
prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
|
stock_ledger_entries = self.__get_stock_ledger_entries()
|
||||||
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
|
|
||||||
|
|
||||||
serial_nos = get_serial_nos(d.serial_no) if d.serial_no else []
|
for d in stock_ledger_entries:
|
||||||
|
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
||||||
|
|
||||||
if d.actual_qty > 0:
|
if d.voucher_type == "Stock Reconciliation":
|
||||||
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
# get difference in qty shift as actual qty
|
||||||
else:
|
prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
|
||||||
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
|
||||||
|
|
||||||
self.__update_balances(d, key)
|
serial_nos = get_serial_nos(d.serial_no) if d.serial_no else []
|
||||||
|
|
||||||
|
if d.actual_qty > 0:
|
||||||
|
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||||
|
else:
|
||||||
|
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||||
|
|
||||||
|
self.__update_balances(d, key)
|
||||||
|
|
||||||
|
# Note that stock_ledger_entries is an iterator, you can not reuse it like a list
|
||||||
|
del stock_ledger_entries
|
||||||
|
|
||||||
if not self.filters.get("show_warehouse_wise_stock"):
|
if not self.filters.get("show_warehouse_wise_stock"):
|
||||||
# (Item 1, WH 1), (Item 1, WH 2) => (Item 1)
|
# (Item 1, WH 1), (Item 1, WH 2) => (Item 1)
|
||||||
@ -381,7 +388,7 @@ class FIFOSlots:
|
|||||||
|
|
||||||
return item_aggregated_data
|
return item_aggregated_data
|
||||||
|
|
||||||
def __get_stock_ledger_entries(self) -> List[Dict]:
|
def __get_stock_ledger_entries(self) -> Iterator[Dict]:
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
item = self.__get_item_query() # used as derived table in sle query
|
item = self.__get_item_query() # used as derived table in sle query
|
||||||
|
|
||||||
@ -418,7 +425,7 @@ class FIFOSlots:
|
|||||||
|
|
||||||
sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)
|
sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)
|
||||||
|
|
||||||
return sle_query.run(as_dict=True)
|
return sle_query.run(as_dict=True, as_iterator=True)
|
||||||
|
|
||||||
def __get_item_query(self) -> str:
|
def __get_item_query(self) -> str:
|
||||||
item_table = frappe.qb.DocType("Item")
|
item_table = frappe.qb.DocType("Item")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user