Merge branch 'develop' into multiple-shifts
This commit is contained in:
commit
7ba66b0320
@ -224,10 +224,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
(frm.doc.total_allocated_amount > party_amount)));
|
||||
|
||||
frm.toggle_display("set_exchange_gain_loss",
|
||||
(frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount &&
|
||||
((frm.doc.paid_from_account_currency != company_currency ||
|
||||
frm.doc.paid_to_account_currency != company_currency) &&
|
||||
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)));
|
||||
frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount);
|
||||
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
@ -35,10 +35,11 @@ class PricingRule(Document):
|
||||
self.margin_rate_or_amount = 0.0
|
||||
|
||||
def validate_duplicate_apply_on(self):
|
||||
field = apply_on_dict.get(self.apply_on)
|
||||
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
|
||||
if len(values) != len(set(values)):
|
||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||
if self.apply_on != "Transaction":
|
||||
field = apply_on_dict.get(self.apply_on)
|
||||
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
|
||||
if len(values) != len(set(values)):
|
||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||
|
||||
def validate_mandatory(self):
|
||||
for apply_on, field in apply_on_dict.items():
|
||||
|
@ -124,11 +124,10 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
expense_accounts = (
|
||||
tax_accounts
|
||||
) = (
|
||||
expense_columns
|
||||
) = tax_columns = unrealized_profit_loss_accounts = unrealized_profit_loss_account_columns = []
|
||||
|
||||
expense_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list(
|
||||
@ -163,10 +162,11 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
unrealized_profit_loss_account_columns = [
|
||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
||||
]
|
||||
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(account + ":Currency/currency:120")
|
||||
tax_columns = [
|
||||
(account + ":Currency/currency:120")
|
||||
for account in tax_accounts
|
||||
if account not in expense_accounts
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
|
@ -126,7 +126,8 @@ class Opportunity(TransactionBase):
|
||||
def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None):
|
||||
if not self.has_active_quotation():
|
||||
self.status = "Lost"
|
||||
self.lost_reasons = self.competitors = []
|
||||
self.lost_reasons = []
|
||||
self.competitors = []
|
||||
|
||||
if detailed_reason:
|
||||
self.order_lost_reason = detailed_reason
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
import json
|
||||
from itertools import groupby
|
||||
|
||||
import frappe
|
||||
import pandas
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
@ -101,18 +101,19 @@ class OpportunitySummaryBySalesStage(object):
|
||||
|
||||
self.convert_to_base_currency()
|
||||
|
||||
dataframe = pandas.DataFrame.from_records(self.query_result)
|
||||
dataframe.replace(to_replace=[None], value="Not Assigned", inplace=True)
|
||||
result = dataframe.groupby(["sales_stage", based_on], as_index=False)["amount"].sum()
|
||||
for row in self.query_result:
|
||||
if not row.get(based_on):
|
||||
row[based_on] = "Not Assigned"
|
||||
|
||||
self.grouped_data = []
|
||||
|
||||
for i in range(len(result["amount"])):
|
||||
grouping_key = lambda o: (o["sales_stage"], o[based_on]) # noqa
|
||||
for (sales_stage, _based_on), rows in groupby(self.query_result, grouping_key):
|
||||
self.grouped_data.append(
|
||||
{
|
||||
"sales_stage": result["sales_stage"][i],
|
||||
based_on: result[based_on][i],
|
||||
"amount": result["amount"][i],
|
||||
"sales_stage": sales_stage,
|
||||
based_on: _based_on,
|
||||
"amount": sum(flt(r["amount"]) for r in rows),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
from itertools import groupby
|
||||
|
||||
import frappe
|
||||
import pandas
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
@ -109,18 +109,15 @@ class SalesPipelineAnalytics(object):
|
||||
|
||||
self.convert_to_base_currency()
|
||||
|
||||
dataframe = pandas.DataFrame.from_records(self.query_result)
|
||||
dataframe.replace(to_replace=[None], value="Not Assigned", inplace=True)
|
||||
result = dataframe.groupby([self.pipeline_by, self.period_by], as_index=False)["amount"].sum()
|
||||
|
||||
self.grouped_data = []
|
||||
|
||||
for i in range(len(result["amount"])):
|
||||
grouping_key = lambda o: (o.get(self.pipeline_by) or "Not Assigned", o[self.period_by]) # noqa
|
||||
for (pipeline_by, period_by), rows in groupby(self.query_result, grouping_key):
|
||||
self.grouped_data.append(
|
||||
{
|
||||
self.pipeline_by: result[self.pipeline_by][i],
|
||||
self.period_by: result[self.period_by][i],
|
||||
"amount": result["amount"][i],
|
||||
self.pipeline_by: pipeline_by,
|
||||
self.period_by: period_by,
|
||||
"amount": sum(flt(r["amount"]) for r in rows),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -34,6 +34,15 @@ frappe.ui.form.on("Leave Allocation", {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// make new leaves allocated field read only if allocation is created via leave policy assignment
|
||||
// and leave type is earned leave, since these leaves would be allocated via the scheduler
|
||||
if (frm.doc.leave_policy_assignment) {
|
||||
frappe.db.get_value("Leave Type", frm.doc.leave_type, "is_earned_leave", (r) => {
|
||||
if (r && cint(r.is_earned_leave))
|
||||
frm.set_df_property("new_leaves_allocated", "read_only", 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
expire_allocation: function(frm) {
|
||||
|
@ -237,7 +237,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-18 19:15:53.262536",
|
||||
"modified": "2022-04-07 09:50:33.145825",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Allocation",
|
||||
@ -281,5 +281,6 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"timeline_field": "employee",
|
||||
"title_field": "employee_name"
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1
|
||||
}
|
@ -353,6 +353,17 @@ def update_previous_leave_allocation(
|
||||
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
|
||||
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
|
||||
|
||||
if e_leave_type.based_on_date_of_joining:
|
||||
text = _("allocated {0} leave(s) via scheduler on {1} based on the date of joining").format(
|
||||
frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
|
||||
)
|
||||
else:
|
||||
text = _("allocated {0} leave(s) via scheduler on {1}").format(
|
||||
frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
|
||||
)
|
||||
|
||||
allocation.add_comment(comment_type="Info", text=text)
|
||||
|
||||
|
||||
def get_monthly_earned_leave(annual_leaves, frequency, rounding):
|
||||
earned_leaves = 0.0
|
||||
|
@ -584,9 +584,10 @@ def regenerate_repayment_schedule(loan, cancel=0):
|
||||
balance_amount / len(loan_doc.get("repayment_schedule")) - accrued_entries
|
||||
)
|
||||
else:
|
||||
if not cancel:
|
||||
repayment_period = loan_doc.repayment_periods - accrued_entries
|
||||
if not cancel and repayment_period > 0:
|
||||
monthly_repayment_amount = get_monthly_repayment_amount(
|
||||
balance_amount, loan_doc.rate_of_interest, loan_doc.repayment_periods - accrued_entries
|
||||
balance_amount, loan_doc.rate_of_interest, repayment_period
|
||||
)
|
||||
else:
|
||||
monthly_repayment_amount = last_repayment_amount
|
||||
|
@ -28,12 +28,12 @@ frappe.ui.form.on('Job Card', {
|
||||
frappe.flags.resume_job = 0;
|
||||
let has_items = frm.doc.items && frm.doc.items.length;
|
||||
|
||||
if (frm.doc.__onload.work_order_closed) {
|
||||
if (!frm.is_new() && frm.doc.__onload.work_order_closed) {
|
||||
frm.disable_save();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
|
||||
if (!frm.is_new() && has_items && frm.doc.docstatus < 2) {
|
||||
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
|
||||
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
|
||||
|
||||
|
@ -20,7 +20,7 @@ def execute():
|
||||
"""
|
||||
UPDATE `tab{doctype}`
|
||||
SET is_cancelled = 0
|
||||
where is_cancelled in ('', NULL, 'No')""".format(
|
||||
where is_cancelled in ('', 'No') or is_cancelled is NULL""".format(
|
||||
doctype=doctype
|
||||
)
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ def execute():
|
||||
"""
|
||||
UPDATE `tab{doctype}`
|
||||
SET is_subcontracted = 0
|
||||
where is_subcontracted in ('', NULL, 'No')""".format(
|
||||
where is_subcontracted in ('', 'No') or is_subcontracted is null""".format(
|
||||
doctype=doctype
|
||||
)
|
||||
)
|
||||
|
@ -609,8 +609,8 @@ function check_can_calculate_pending_qty(me) {
|
||||
&& erpnext.stock.bom
|
||||
&& erpnext.stock.bom.name === doc.bom_no;
|
||||
const itemChecks = !!item
|
||||
&& !item.allow_alternative_item
|
||||
&& erpnext.stock.bom && erpnext.stock.items
|
||||
&& !item.original_item
|
||||
&& erpnext.stock.bom && erpnext.stock.bom.items
|
||||
&& (item.item_code in erpnext.stock.bom.items);
|
||||
return docChecks && itemChecks;
|
||||
}
|
||||
|
@ -100,7 +100,8 @@ class Customer(TransactionBase):
|
||||
@frappe.whitelist()
|
||||
def get_customer_group_details(self):
|
||||
doc = frappe.get_doc("Customer Group", self.customer_group)
|
||||
self.accounts = self.credit_limits = []
|
||||
self.accounts = []
|
||||
self.credit_limits = []
|
||||
self.payment_terms = self.default_price_list = ""
|
||||
|
||||
tables = [["accounts", "account"], ["credit_limits", "credit_limit"]]
|
||||
|
@ -45,7 +45,8 @@ class TestCustomer(FrappeTestCase):
|
||||
c_doc.customer_name = "Testing Customer"
|
||||
c_doc.customer_group = "_Testing Customer Group"
|
||||
c_doc.payment_terms = c_doc.default_price_list = ""
|
||||
c_doc.accounts = c_doc.credit_limits = []
|
||||
c_doc.accounts = []
|
||||
c_doc.credit_limits = []
|
||||
c_doc.insert()
|
||||
c_doc.get_customer_group_details()
|
||||
self.assertEqual(c_doc.payment_terms, "_Test Payment Term Template 3")
|
||||
|
@ -1,10 +1,11 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from itertools import groupby
|
||||
|
||||
import frappe
|
||||
import pandas as pd
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.accounts.report.utils import convert
|
||||
|
||||
@ -89,28 +90,21 @@ def get_opp_by_lead_source(from_date, to_date, company):
|
||||
for x in opportunities
|
||||
]
|
||||
|
||||
df = (
|
||||
pd.DataFrame(cp_opportunities)
|
||||
.groupby(["source", "sales_stage"], as_index=False)
|
||||
.agg({"compound_amount": "sum"})
|
||||
)
|
||||
summary = {}
|
||||
sales_stages = set()
|
||||
group_key = lambda o: (o["source"], o["sales_stage"]) # noqa
|
||||
for (source, sales_stage), rows in groupby(cp_opportunities, group_key):
|
||||
summary.setdefault(source, {})[sales_stage] = sum(r["compound_amount"] for r in rows)
|
||||
sales_stages.add(sales_stage)
|
||||
|
||||
result = {}
|
||||
result["labels"] = list(set(df.source.values))
|
||||
result["datasets"] = []
|
||||
|
||||
for s in set(df.sales_stage.values):
|
||||
result["datasets"].append(
|
||||
{"name": s, "values": [0] * len(result["labels"]), "chartType": "bar"}
|
||||
)
|
||||
|
||||
for row in df.itertuples():
|
||||
source_index = result["labels"].index(row.source)
|
||||
|
||||
for dataset in result["datasets"]:
|
||||
if dataset["name"] == row.sales_stage:
|
||||
dataset["values"][source_index] = row.compound_amount
|
||||
pivot_table = []
|
||||
for sales_stage in sales_stages:
|
||||
row = []
|
||||
for source, sales_stage_values in summary.items():
|
||||
row.append(flt(sales_stage_values.get(sales_stage)))
|
||||
pivot_table.append({"chartType": "bar", "name": sales_stage, "values": row})
|
||||
|
||||
result = {"datasets": pivot_table, "labels": list(summary.keys())}
|
||||
return result
|
||||
|
||||
else:
|
||||
@ -148,20 +142,14 @@ def get_pipeline_data(from_date, to_date, company):
|
||||
for x in opportunities
|
||||
]
|
||||
|
||||
df = (
|
||||
pd.DataFrame(cp_opportunities)
|
||||
.groupby(["sales_stage"], as_index=True)
|
||||
.agg({"compound_amount": "sum"})
|
||||
.to_dict()
|
||||
)
|
||||
|
||||
result = {}
|
||||
result["labels"] = df["compound_amount"].keys()
|
||||
result["datasets"] = []
|
||||
result["datasets"].append(
|
||||
{"name": _("Total Amount"), "values": df["compound_amount"].values(), "chartType": "bar"}
|
||||
)
|
||||
summary = {}
|
||||
for sales_stage, rows in groupby(cp_opportunities, lambda o: o["sales_stage"]):
|
||||
summary[sales_stage] = sum(flt(r["compound_amount"]) for r in rows)
|
||||
|
||||
result = {
|
||||
"labels": list(summary.keys()),
|
||||
"datasets": [{"name": _("Total Amount"), "values": list(summary.values()), "chartType": "bar"}],
|
||||
}
|
||||
return result
|
||||
|
||||
else:
|
||||
|
@ -27,28 +27,55 @@ function get_filters() {
|
||||
"default": frappe.datetime.get_today()
|
||||
},
|
||||
{
|
||||
"fieldname":"sales_order",
|
||||
"label": __("Sales Order"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
"fieldname":"customer_group",
|
||||
"label": __("Customer Group"),
|
||||
"fieldtype": "Link",
|
||||
"width": 100,
|
||||
"options": "Sales Order",
|
||||
"get_data": function(txt) {
|
||||
return frappe.db.get_link_options("Sales Order", txt, this.filters());
|
||||
},
|
||||
"filters": () => {
|
||||
return {
|
||||
docstatus: 1,
|
||||
payment_terms_template: ['not in', ['']],
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
transaction_date: ['between', [frappe.query_report.get_filter_value("period_start_date"), frappe.query_report.get_filter_value("period_end_date")]]
|
||||
"options": "Customer Group",
|
||||
},
|
||||
{
|
||||
"fieldname":"customer",
|
||||
"label": __("Customer"),
|
||||
"fieldtype": "Link",
|
||||
"width": 100,
|
||||
"options": "Customer",
|
||||
"get_query": () => {
|
||||
var customer_group = frappe.query_report.get_filter_value('customer_group');
|
||||
return{
|
||||
"query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
|
||||
"filters": [
|
||||
['Customer', 'disabled', '=', '0'],
|
||||
['Customer Group','name', '=', customer_group]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"item_group",
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"width": 100,
|
||||
"options": "Item Group",
|
||||
|
||||
},
|
||||
{
|
||||
"fieldname":"item",
|
||||
"label": __("Item"),
|
||||
"fieldtype": "Link",
|
||||
"width": 100,
|
||||
"options": "Item",
|
||||
"get_query": () => {
|
||||
var item_group = frappe.query_report.get_filter_value('item_group');
|
||||
return{
|
||||
"query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
|
||||
"filters": [
|
||||
['Item', 'disabled', '=', '0'],
|
||||
['Item Group','name', '=', item_group]
|
||||
]
|
||||
}
|
||||
},
|
||||
on_change: function(){
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb, query_builder
|
||||
from frappe.query_builder import functions
|
||||
from frappe.query_builder import Criterion, functions
|
||||
|
||||
|
||||
def get_columns():
|
||||
@ -14,6 +14,12 @@ def get_columns():
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
},
|
||||
{
|
||||
"label": _("Customer"),
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
},
|
||||
{
|
||||
"label": _("Posting Date"),
|
||||
"fieldname": "submitted",
|
||||
@ -67,6 +73,55 @@ def get_columns():
|
||||
return columns
|
||||
|
||||
|
||||
def get_descendants_of(doctype, group_name):
|
||||
group_doc = qb.DocType(doctype)
|
||||
# get lft and rgt of group node
|
||||
lft, rgt = (
|
||||
qb.from_(group_doc).select(group_doc.lft, group_doc.rgt).where(group_doc.name == group_name)
|
||||
).run()[0]
|
||||
|
||||
# get all children of group node
|
||||
query = (
|
||||
qb.from_(group_doc).select(group_doc.name).where((group_doc.lft >= lft) & (group_doc.rgt <= rgt))
|
||||
)
|
||||
|
||||
child_nodes = []
|
||||
for x in query.run():
|
||||
child_nodes.append(x[0])
|
||||
|
||||
return child_nodes
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_customers_or_items(doctype, txt, searchfield, start, page_len, filters):
|
||||
filter_list = []
|
||||
if isinstance(filters, list):
|
||||
for item in filters:
|
||||
if item[0] == doctype:
|
||||
filter_list.append(item)
|
||||
elif item[0] == "Customer Group":
|
||||
if item[3] != "":
|
||||
filter_list.append(
|
||||
[doctype, "customer_group", "in", get_descendants_of("Customer Group", item[3])]
|
||||
)
|
||||
elif item[0] == "Item Group":
|
||||
if item[3] != "":
|
||||
filter_list.append([doctype, "item_group", "in", get_descendants_of("Item Group", item[3])])
|
||||
|
||||
if searchfield and txt:
|
||||
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
|
||||
|
||||
return frappe.desk.reportview.execute(
|
||||
doctype,
|
||||
filters=filter_list,
|
||||
fields=["name", "customer_group"] if doctype == "Customer" else ["name", "item_group"],
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
"""
|
||||
Convert filter options to conditions used in query
|
||||
@ -79,11 +134,37 @@ def get_conditions(filters):
|
||||
conditions.start_date = filters.period_start_date or frappe.utils.add_months(
|
||||
conditions.end_date, -1
|
||||
)
|
||||
conditions.sales_order = filters.sales_order or []
|
||||
|
||||
return conditions
|
||||
|
||||
|
||||
def build_filter_criterions(filters):
|
||||
filters = frappe._dict(filters) if filters else frappe._dict({})
|
||||
qb_criterions = []
|
||||
|
||||
if filters.customer_group:
|
||||
qb_criterions.append(
|
||||
qb.DocType("Sales Order").customer_group.isin(
|
||||
get_descendants_of("Customer Group", filters.customer_group)
|
||||
)
|
||||
)
|
||||
|
||||
if filters.customer:
|
||||
qb_criterions.append(qb.DocType("Sales Order").customer == filters.customer)
|
||||
|
||||
if filters.item_group:
|
||||
qb_criterions.append(
|
||||
qb.DocType("Sales Order Item").item_group.isin(
|
||||
get_descendants_of("Item Group", filters.item_group)
|
||||
)
|
||||
)
|
||||
|
||||
if filters.item:
|
||||
qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item)
|
||||
|
||||
return qb_criterions
|
||||
|
||||
|
||||
def get_so_with_invoices(filters):
|
||||
"""
|
||||
Get Sales Order with payment terms template with their associated Invoices
|
||||
@ -92,16 +173,23 @@ def get_so_with_invoices(filters):
|
||||
|
||||
so = qb.DocType("Sales Order")
|
||||
ps = qb.DocType("Payment Schedule")
|
||||
soi = qb.DocType("Sales Order Item")
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
filter_criterions = build_filter_criterions(filters)
|
||||
|
||||
datediff = query_builder.CustomFunction("DATEDIFF", ["cur_date", "due_date"])
|
||||
ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"])
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
query_so = (
|
||||
qb.from_(so)
|
||||
.join(soi)
|
||||
.on(soi.parent == so.name)
|
||||
.join(ps)
|
||||
.on(ps.parent == so.name)
|
||||
.select(
|
||||
so.name,
|
||||
so.customer,
|
||||
so.transaction_date.as_("submitted"),
|
||||
ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
|
||||
ps.payment_term,
|
||||
@ -117,12 +205,10 @@ def get_so_with_invoices(filters):
|
||||
& (so.company == conditions.company)
|
||||
& (so.transaction_date[conditions.start_date : conditions.end_date])
|
||||
)
|
||||
.where(Criterion.all(filter_criterions))
|
||||
.orderby(so.name, so.transaction_date, ps.due_date)
|
||||
)
|
||||
|
||||
if conditions.sales_order != []:
|
||||
query_so = query_so.where(so.name.isin(conditions.sales_order))
|
||||
|
||||
sorders = query_so.run(as_dict=True)
|
||||
|
||||
invoices = []
|
||||
|
@ -11,10 +11,13 @@ from erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_s
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template"]
|
||||
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template", "Customer"]
|
||||
|
||||
|
||||
class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def create_payment_terms_template(self):
|
||||
# create template for 50-50 payments
|
||||
template = None
|
||||
@ -48,9 +51,9 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
template.insert()
|
||||
self.template = template
|
||||
|
||||
def test_payment_terms_status(self):
|
||||
def test_01_payment_terms_status(self):
|
||||
self.create_payment_terms_template()
|
||||
item = create_item(item_code="_Test Excavator", is_stock_item=0)
|
||||
item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
|
||||
so = make_sales_order(
|
||||
transaction_date="2021-06-15",
|
||||
delivery_date=add_days("2021-06-15", -30),
|
||||
@ -78,13 +81,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
"sales_order": [so.name],
|
||||
"item": item.item_code,
|
||||
}
|
||||
)
|
||||
|
||||
expected_value = [
|
||||
{
|
||||
"name": so.name,
|
||||
"customer": so.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Completed",
|
||||
"payment_term": None,
|
||||
@ -98,6 +102,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
},
|
||||
{
|
||||
"name": so.name,
|
||||
"customer": so.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Partly Paid",
|
||||
"payment_term": None,
|
||||
@ -132,11 +137,11 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
)
|
||||
doc.insert()
|
||||
|
||||
def test_alternate_currency(self):
|
||||
def test_02_alternate_currency(self):
|
||||
transaction_date = "2021-06-15"
|
||||
self.create_payment_terms_template()
|
||||
self.create_exchange_rate(transaction_date)
|
||||
item = create_item(item_code="_Test Excavator", is_stock_item=0)
|
||||
item = create_item(item_code="_Test Excavator 2", is_stock_item=0)
|
||||
so = make_sales_order(
|
||||
transaction_date=transaction_date,
|
||||
currency="USD",
|
||||
@ -166,7 +171,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
"sales_order": [so.name],
|
||||
"item": item.item_code,
|
||||
}
|
||||
)
|
||||
|
||||
@ -174,6 +179,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
expected_value = [
|
||||
{
|
||||
"name": so.name,
|
||||
"customer": so.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Completed",
|
||||
"payment_term": None,
|
||||
@ -187,6 +193,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
},
|
||||
{
|
||||
"name": so.name,
|
||||
"customer": so.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Partly Paid",
|
||||
"payment_term": None,
|
||||
@ -200,3 +207,134 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
||||
},
|
||||
]
|
||||
self.assertEqual(data, expected_value)
|
||||
|
||||
def test_03_group_filters(self):
|
||||
transaction_date = "2021-06-15"
|
||||
self.create_payment_terms_template()
|
||||
item1 = create_item(item_code="_Test Excavator 1", is_stock_item=0)
|
||||
item1.item_group = "Products"
|
||||
item1.save()
|
||||
|
||||
so1 = make_sales_order(
|
||||
transaction_date=transaction_date,
|
||||
delivery_date=add_days(transaction_date, -30),
|
||||
item=item1.item_code,
|
||||
qty=1,
|
||||
rate=1000000,
|
||||
do_not_save=True,
|
||||
)
|
||||
so1.po_no = ""
|
||||
so1.taxes_and_charges = ""
|
||||
so1.taxes = ""
|
||||
so1.payment_terms_template = self.template.name
|
||||
so1.save()
|
||||
so1.submit()
|
||||
|
||||
item2 = create_item(item_code="_Test Steel", is_stock_item=0)
|
||||
item2.item_group = "Raw Material"
|
||||
item2.save()
|
||||
|
||||
so2 = make_sales_order(
|
||||
customer="_Test Customer 1",
|
||||
transaction_date=transaction_date,
|
||||
delivery_date=add_days(transaction_date, -30),
|
||||
item=item2.item_code,
|
||||
qty=100,
|
||||
rate=1000,
|
||||
do_not_save=True,
|
||||
)
|
||||
so2.po_no = ""
|
||||
so2.taxes_and_charges = ""
|
||||
so2.taxes = ""
|
||||
so2.payment_terms_template = self.template.name
|
||||
so2.save()
|
||||
so2.submit()
|
||||
|
||||
base_filters = {
|
||||
"company": "_Test Company",
|
||||
"period_start_date": "2021-06-01",
|
||||
"period_end_date": "2021-06-30",
|
||||
}
|
||||
|
||||
expected_value_so1 = [
|
||||
{
|
||||
"name": so1.name,
|
||||
"customer": so1.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Overdue",
|
||||
"payment_term": None,
|
||||
"description": "_Test 50-50",
|
||||
"due_date": datetime.date(2021, 6, 30),
|
||||
"invoice_portion": 50.0,
|
||||
"currency": "INR",
|
||||
"base_payment_amount": 500000.0,
|
||||
"paid_amount": 0.0,
|
||||
"invoices": "",
|
||||
},
|
||||
{
|
||||
"name": so1.name,
|
||||
"customer": so1.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Overdue",
|
||||
"payment_term": None,
|
||||
"description": "_Test 50-50",
|
||||
"due_date": datetime.date(2021, 7, 15),
|
||||
"invoice_portion": 50.0,
|
||||
"currency": "INR",
|
||||
"base_payment_amount": 500000.0,
|
||||
"paid_amount": 0.0,
|
||||
"invoices": "",
|
||||
},
|
||||
]
|
||||
|
||||
expected_value_so2 = [
|
||||
{
|
||||
"name": so2.name,
|
||||
"customer": so2.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Overdue",
|
||||
"payment_term": None,
|
||||
"description": "_Test 50-50",
|
||||
"due_date": datetime.date(2021, 6, 30),
|
||||
"invoice_portion": 50.0,
|
||||
"currency": "INR",
|
||||
"base_payment_amount": 50000.0,
|
||||
"paid_amount": 0.0,
|
||||
"invoices": "",
|
||||
},
|
||||
{
|
||||
"name": so2.name,
|
||||
"customer": so2.customer,
|
||||
"submitted": datetime.date(2021, 6, 15),
|
||||
"status": "Overdue",
|
||||
"payment_term": None,
|
||||
"description": "_Test 50-50",
|
||||
"due_date": datetime.date(2021, 7, 15),
|
||||
"invoice_portion": 50.0,
|
||||
"currency": "INR",
|
||||
"base_payment_amount": 50000.0,
|
||||
"paid_amount": 0.0,
|
||||
"invoices": "",
|
||||
},
|
||||
]
|
||||
|
||||
group_filters = [
|
||||
{"customer_group": "All Customer Groups"},
|
||||
{"item_group": "All Item Groups"},
|
||||
{"item_group": "Products"},
|
||||
{"item_group": "Raw Material"},
|
||||
]
|
||||
|
||||
expected_values_for_group_filters = [
|
||||
expected_value_so1 + expected_value_so2,
|
||||
expected_value_so1 + expected_value_so2,
|
||||
expected_value_so1,
|
||||
expected_value_so2,
|
||||
]
|
||||
|
||||
for idx, g in enumerate(group_filters, 0):
|
||||
# build filter
|
||||
filters = frappe._dict({}).update(base_filters).update(g)
|
||||
with self.subTest(filters=filters):
|
||||
columns, data, message, chart = execute(filters)
|
||||
self.assertEqual(data, expected_values_for_group_filters[idx])
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
from typing import List
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@ -18,6 +18,7 @@ from frappe.utils import (
|
||||
now_datetime,
|
||||
nowtime,
|
||||
strip,
|
||||
strip_html,
|
||||
)
|
||||
from frappe.utils.html_utils import clean_html
|
||||
|
||||
@ -69,10 +70,6 @@ class Item(Document):
|
||||
self.item_code = strip(self.item_code)
|
||||
self.name = self.item_code
|
||||
|
||||
def before_insert(self):
|
||||
if not self.description:
|
||||
self.description = self.item_name
|
||||
|
||||
def after_insert(self):
|
||||
"""set opening stock and item price"""
|
||||
if self.standard_rate:
|
||||
@ -86,7 +83,7 @@ class Item(Document):
|
||||
if not self.item_name:
|
||||
self.item_name = self.item_code
|
||||
|
||||
if not self.description:
|
||||
if not strip_html(cstr(self.description)).strip():
|
||||
self.description = self.item_name
|
||||
|
||||
self.validate_uom()
|
||||
@ -890,25 +887,38 @@ class Item(Document):
|
||||
if self.is_new():
|
||||
return
|
||||
|
||||
fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no")
|
||||
restricted_fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no")
|
||||
|
||||
values = frappe.db.get_value("Item", self.name, restricted_fields, as_dict=True)
|
||||
if not values:
|
||||
return
|
||||
|
||||
values = frappe.db.get_value("Item", self.name, fields, as_dict=True)
|
||||
if not values.get("valuation_method") and self.get("valuation_method"):
|
||||
values["valuation_method"] = (
|
||||
frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO"
|
||||
)
|
||||
|
||||
if values:
|
||||
for field in fields:
|
||||
if cstr(self.get(field)) != cstr(values.get(field)):
|
||||
if self.check_if_linked_document_exists(field):
|
||||
frappe.throw(
|
||||
_(
|
||||
"As there are existing transactions against item {0}, you can not change the value of {1}"
|
||||
).format(self.name, frappe.bold(self.meta.get_label(field)))
|
||||
)
|
||||
changed_fields = [
|
||||
field for field in restricted_fields if cstr(self.get(field)) != cstr(values.get(field))
|
||||
]
|
||||
if not changed_fields:
|
||||
return
|
||||
|
||||
def check_if_linked_document_exists(self, field):
|
||||
if linked_doc := self._get_linked_submitted_documents(changed_fields):
|
||||
changed_field_labels = [frappe.bold(self.meta.get_label(f)) for f in changed_fields]
|
||||
msg = _(
|
||||
"As there are existing submitted transactions against item {0}, you can not change the value of {1}."
|
||||
).format(self.name, ", ".join(changed_field_labels))
|
||||
|
||||
if linked_doc and isinstance(linked_doc, dict):
|
||||
msg += "<br>"
|
||||
msg += _("Example of a linked document: {0}").format(
|
||||
frappe.get_desk_link(linked_doc.doctype, linked_doc.docname)
|
||||
)
|
||||
|
||||
frappe.throw(msg, title=_("Linked with submitted documents"))
|
||||
|
||||
def _get_linked_submitted_documents(self, changed_fields: List[str]) -> Optional[Dict[str, str]]:
|
||||
linked_doctypes = [
|
||||
"Delivery Note Item",
|
||||
"Sales Invoice Item",
|
||||
@ -921,7 +931,7 @@ class Item(Document):
|
||||
|
||||
# For "Is Stock Item", following doctypes is important
|
||||
# because reserved_qty, ordered_qty and requested_qty updated from these doctypes
|
||||
if field == "is_stock_item":
|
||||
if "is_stock_item" in changed_fields:
|
||||
linked_doctypes += [
|
||||
"Sales Order Item",
|
||||
"Purchase Order Item",
|
||||
@ -940,11 +950,21 @@ class Item(Document):
|
||||
"Sales Invoice Item",
|
||||
):
|
||||
# If Invoice has Stock impact, only then consider it.
|
||||
if self.stock_ledger_created():
|
||||
return True
|
||||
if linked_doc := frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"item_code": self.name, "is_cancelled": 0},
|
||||
["voucher_no as docname", "voucher_type as doctype"],
|
||||
as_dict=True,
|
||||
):
|
||||
return linked_doc
|
||||
|
||||
elif frappe.db.get_value(doctype, filters):
|
||||
return True
|
||||
elif linked_doc := frappe.db.get_value(
|
||||
doctype,
|
||||
filters,
|
||||
["parent as docname", "parenttype as doctype"],
|
||||
as_dict=True,
|
||||
):
|
||||
return linked_doc
|
||||
|
||||
def validate_auto_reorder_enabled_in_stock_settings(self):
|
||||
if self.reorder_levels:
|
||||
|
@ -31,7 +31,7 @@ def get_data():
|
||||
},
|
||||
{"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]},
|
||||
{"label": _("Traceability"), "items": ["Serial No", "Batch"]},
|
||||
{"label": _("Move"), "items": ["Stock Entry"]},
|
||||
{"label": _("Stock Movement"), "items": ["Stock Entry", "Stock Reconciliation"]},
|
||||
{"label": _("E-commerce"), "items": ["Website Item"]},
|
||||
],
|
||||
}
|
||||
|
@ -744,6 +744,40 @@ class TestItem(FrappeTestCase):
|
||||
self.assertTrue(get_data(warehouse="_Test Warehouse - _TC"))
|
||||
self.assertTrue(get_data(item_group="All Item Groups"))
|
||||
|
||||
def test_empty_description(self):
|
||||
item = make_item(properties={"description": "<p></p>"})
|
||||
self.assertEqual(item.description, item.item_name)
|
||||
item.description = ""
|
||||
item.save()
|
||||
self.assertEqual(item.description, item.item_name)
|
||||
|
||||
def test_item_type_field_change(self):
|
||||
"""Check if critical fields like `is_stock_item`, `has_batch_no` are not changed if transactions exist."""
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
transaction_creators = [
|
||||
lambda i: make_purchase_receipt(item_code=i),
|
||||
lambda i: make_purchase_invoice(item_code=i, update_stock=1),
|
||||
lambda i: make_stock_entry(item_code=i, qty=1, target="_Test Warehouse - _TC"),
|
||||
lambda i: create_delivery_note(item_code=i),
|
||||
]
|
||||
|
||||
properties = {"has_batch_no": 0, "allow_negative_stock": 1, "valuation_rate": 10}
|
||||
for transaction_creator in transaction_creators:
|
||||
item = make_item(properties=properties)
|
||||
transaction = transaction_creator(item.name)
|
||||
item.has_batch_no = 1
|
||||
self.assertRaises(frappe.ValidationError, item.save)
|
||||
|
||||
transaction.cancel()
|
||||
# should be allowed now
|
||||
item.reload()
|
||||
item.has_batch_no = 1
|
||||
item.save()
|
||||
|
||||
|
||||
def set_item_variant_settings(fields):
|
||||
doc = frappe.get_doc("Item Variant Settings")
|
||||
|
@ -38,6 +38,16 @@ class TestWarehouse(FrappeTestCase):
|
||||
self.assertEqual(p_warehouse.name, child_warehouse.parent_warehouse)
|
||||
self.assertEqual(child_warehouse.is_group, 0)
|
||||
|
||||
def test_naming(self):
|
||||
company = "Wind Power LLC"
|
||||
warehouse_name = "Named Warehouse - WP"
|
||||
wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
|
||||
self.assertEqual(wh.name, warehouse_name)
|
||||
|
||||
warehouse_name = "Unnamed Warehouse"
|
||||
wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
|
||||
self.assertIn(warehouse_name, wh.name)
|
||||
|
||||
def test_unlinking_warehouse_from_item_defaults(self):
|
||||
company = "_Test Company"
|
||||
|
||||
|
@ -21,8 +21,9 @@ class Warehouse(NestedSet):
|
||||
suffix = " - " + frappe.get_cached_value("Company", self.company, "abbr")
|
||||
if not self.warehouse_name.endswith(suffix):
|
||||
self.name = self.warehouse_name + suffix
|
||||
else:
|
||||
self.name = self.warehouse_name
|
||||
return
|
||||
|
||||
self.name = self.warehouse_name
|
||||
|
||||
def onload(self):
|
||||
"""load account name for General Ledger Report"""
|
||||
|
@ -285,7 +285,7 @@ Asset scrapped via Journal Entry {0},Actif mis au rebut via Écriture de Journal
|
||||
"Asset {0} cannot be scrapped, as it is already {1}","L'actif {0} ne peut pas être mis au rebut, car il est déjà {1}",
|
||||
Asset {0} does not belong to company {1},L'actif {0} ne fait pas partie à la société {1},
|
||||
Asset {0} must be submitted,L'actif {0} doit être soumis,
|
||||
Assets,Les atouts,
|
||||
Assets,Actifs - Immo.,
|
||||
Assign,Assigner,
|
||||
Assign Salary Structure,Affecter la structure salariale,
|
||||
Assign To,Attribuer À,
|
||||
@ -1211,7 +1211,7 @@ Hello,Bonjour,
|
||||
Help Results for,Aide Résultats pour,
|
||||
High,Haut,
|
||||
High Sensitivity,Haute sensibilité,
|
||||
Hold,Tenir,
|
||||
Hold,Mettre en attente,
|
||||
Hold Invoice,Facture en attente,
|
||||
Holiday,Vacances,
|
||||
Holiday List,Liste de vacances,
|
||||
@ -4240,7 +4240,7 @@ For Default Supplier (Optional),Pour le fournisseur par défaut (facultatif),
|
||||
From date cannot be greater than To date,La Date Initiale ne peut pas être postérieure à la Date Finale,
|
||||
Group by,Grouper Par,
|
||||
In stock,En stock,
|
||||
Item name,Nom de l'article,
|
||||
Item name,Libellé de l'article,
|
||||
Loan amount is mandatory,Le montant du prêt est obligatoire,
|
||||
Minimum Qty,Quantité minimum,
|
||||
More details,Plus de détails,
|
||||
@ -5473,7 +5473,7 @@ Percentage you are allowed to transfer more against the quantity ordered. For ex
|
||||
PUR-ORD-.YYYY.-,PUR-ORD-.YYYY.-,
|
||||
Get Items from Open Material Requests,Obtenir des Articles de Demandes Matérielles Ouvertes,
|
||||
Fetch items based on Default Supplier.,Récupérez les articles en fonction du fournisseur par défaut.,
|
||||
Required By,Requis Par,
|
||||
Required By,Requis pour le,
|
||||
Order Confirmation No,No de confirmation de commande,
|
||||
Order Confirmation Date,Date de confirmation de la commande,
|
||||
Customer Mobile No,N° de Portable du Client,
|
||||
@ -7223,8 +7223,8 @@ Basic Rate (Company Currency),Taux de Base (Devise de la Société ),
|
||||
Scrap %,% de Rebut,
|
||||
Original Item,Article original,
|
||||
BOM Operation,Opération LDM,
|
||||
Operation Time ,Moment de l'opération,
|
||||
In minutes,En quelques minutes,
|
||||
Operation Time ,Durée de l'opération,
|
||||
In minutes,En minutes,
|
||||
Batch Size,Taille du lot,
|
||||
Base Hour Rate(Company Currency),Taux Horaire de Base (Devise de la Société),
|
||||
Operating Cost(Company Currency),Coût d'Exploitation (Devise Société),
|
||||
@ -9267,7 +9267,7 @@ Sales Order Analysis,Analyse des commandes clients,
|
||||
Amount Delivered,Montant livré,
|
||||
Delay (in Days),Retard (en jours),
|
||||
Group by Sales Order,Regrouper par commande client,
|
||||
Sales Value,La valeur des ventes,
|
||||
Sales Value,La valeur des ventes,
|
||||
Stock Qty vs Serial No Count,Quantité de stock vs numéro de série,
|
||||
Serial No Count,Numéro de série,
|
||||
Work Order Summary,Résumé de l'ordre de travail,
|
||||
@ -9647,7 +9647,7 @@ Allow Multiple Sales Orders Against a Customer's Purchase Order,Autoriser plusie
|
||||
Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Valider le prix de vente de l'article par rapport au taux d'achat ou au taux de valorisation,
|
||||
Hide Customer's Tax ID from Sales Transactions,Masquer le numéro d'identification fiscale du client dans les transactions de vente,
|
||||
"The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.","Le pourcentage que vous êtes autorisé à recevoir ou à livrer plus par rapport à la quantité commandée. Par exemple, si vous avez commandé 100 unités et que votre allocation est de 10%, vous êtes autorisé à recevoir 110 unités.",
|
||||
Action If Quality Inspection Is Not Submitted,Action si l'inspection de la qualité n'est pas soumise,
|
||||
Action If Quality Inspection Is Not Submitted,Action si l'inspection qualité n'est pas soumise,
|
||||
Auto Insert Price List Rate If Missing,Taux de liste de prix d'insertion automatique s'il est manquant,
|
||||
Automatically Set Serial Nos Based on FIFO,Définir automatiquement les numéros de série en fonction de FIFO,
|
||||
Set Qty in Transactions Based on Serial No Input,Définir la quantité dans les transactions en fonction du numéro de série,
|
||||
@ -9838,3 +9838,35 @@ Enable European Access,Activer l'accès européen,
|
||||
Creating Purchase Order ...,Création d'une commande d'achat ...,
|
||||
"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Sélectionnez un fournisseur parmi les fournisseurs par défaut des articles ci-dessous. Lors de la sélection, un bon de commande sera effectué contre des articles appartenant uniquement au fournisseur sélectionné.",
|
||||
Row #{}: You must select {} serial numbers for item {}.,Ligne n ° {}: vous devez sélectionner {} numéros de série pour l'article {}.,
|
||||
Update Rate as per Last Purchase,Mettre à jour avec les derniers prix d'achats
|
||||
Company Shipping Address,Adresse d'expédition
|
||||
Shipping Address Details,Détail d'adresse d'expédition
|
||||
Company Billing Address,Adresse de la société de facturation
|
||||
Supplier Address Details,
|
||||
Bank Reconciliation Tool,Outil de réconcialiation d'écritures bancaires
|
||||
Supplier Contact,Contact fournisseur
|
||||
Subcontracting,Sous traitance
|
||||
Order Status,Statut de la commande
|
||||
Build,Personnalisations avancées
|
||||
Dispatch Address Name,Adresse de livraison intermédiaire
|
||||
Amount Eligible for Commission,Montant éligible à comission
|
||||
Grant Commission,Eligible aux commissions
|
||||
Stock Transactions Settings, Paramétre des transactions
|
||||
Role Allowed to Over Deliver/Receive, Rôle autorisé à dépasser cette limite
|
||||
Users with this role are allowed to over deliver/receive against orders above the allowance percentage,Rôle Utilisateur qui sont autorisé à livrée/commandé au-delà de la limite
|
||||
Over Transfer Allowance,Autorisation de limite de transfert
|
||||
Quality Inspection Settings,Paramétre de l'inspection qualité
|
||||
Action If Quality Inspection Is Rejected,Action si l'inspection qualité est rejetée
|
||||
Disable Serial No And Batch Selector,Désactiver le sélecteur de numéro de lot/série
|
||||
Is Rate Adjustment Entry (Debit Note),Est un justement du prix de la note de débit
|
||||
Issue a debit note with 0 qty against an existing Sales Invoice,Creer une note de débit avec une quatité à O pour la facture
|
||||
Control Historical Stock Transactions,Controle de l'historique des stransaction de stock
|
||||
No stock transactions can be created or modified before this date.,Aucune transaction ne peux être créée ou modifié avant cette date.
|
||||
Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées
|
||||
Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée
|
||||
"If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.","LEs utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire"
|
||||
Auto Insert Item Price If Missing,Création du prix de l'article dans les listes de prix si abscent
|
||||
Update Existing Price List Rate,Mise a jour automatique du prix dans les listes de prix
|
||||
Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les transactions de stock
|
||||
Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions
|
||||
Have Default Naming Series for Batch ID?,Nom de série par défaut pour les Lots ou Séries
|
||||
|
Can't render this file because it is too large.
|
Loading…
x
Reference in New Issue
Block a user