Merge branch 'develop' into fix-depr-after-sale
This commit is contained in:
commit
2e61c5eb55
@ -55,14 +55,6 @@ The Easy Way: our install script for bench will install all dependencies (e.g. M
|
||||
|
||||
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
||||
|
||||
### Virtual Image
|
||||
|
||||
You can download a virtual image to run ERPNext in a virtual machine on your local system.
|
||||
|
||||
- [ERPNext Download](http://erpnext.com/download)
|
||||
|
||||
System and user credentials are listed on the download page.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
@ -10,6 +10,7 @@ from frappe.contacts.doctype.address.address import (
|
||||
class ERPNextAddress(Address):
|
||||
def validate(self):
|
||||
self.validate_reference()
|
||||
self.update_compnay_address()
|
||||
super(ERPNextAddress, self).validate()
|
||||
|
||||
def link_address(self):
|
||||
@ -19,6 +20,11 @@ class ERPNextAddress(Address):
|
||||
|
||||
return super(ERPNextAddress, self).link_address()
|
||||
|
||||
def update_compnay_address(self):
|
||||
for link in self.get('links'):
|
||||
if link.link_doctype == 'Company':
|
||||
self.is_your_company_address = 1
|
||||
|
||||
def validate_reference(self):
|
||||
if self.is_your_company_address and not [
|
||||
row for row in self.links if row.link_doctype == "Company"
|
||||
|
@ -27,10 +27,12 @@
|
||||
"payment_accounts_section",
|
||||
"party_balance",
|
||||
"paid_from",
|
||||
"paid_from_account_type",
|
||||
"paid_from_account_currency",
|
||||
"paid_from_account_balance",
|
||||
"column_break_18",
|
||||
"paid_to",
|
||||
"paid_to_account_type",
|
||||
"paid_to_account_currency",
|
||||
"paid_to_account_balance",
|
||||
"payment_amounts_section",
|
||||
@ -440,7 +442,8 @@
|
||||
"depends_on": "eval:(doc.paid_from && doc.paid_to)",
|
||||
"fieldname": "reference_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Cheque/Reference No"
|
||||
"label": "Cheque/Reference No",
|
||||
"mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_23",
|
||||
@ -452,6 +455,7 @@
|
||||
"fieldname": "reference_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Cheque/Reference Date",
|
||||
"mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -707,15 +711,30 @@
|
||||
"label": "Received Amount After Tax (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "paid_from.account_type",
|
||||
"fieldname": "paid_from_account_type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Paid From Account Type"
|
||||
},
|
||||
{
|
||||
"fetch_from": "paid_to.account_type",
|
||||
"fieldname": "paid_to_account_type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Paid To Account Type"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-09 08:58:15.008761",
|
||||
"modified": "2021-10-22 17:50:24.632806",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
@ -29,6 +29,9 @@ def get_pricing_rules(args, doc=None):
|
||||
pricing_rules = []
|
||||
values = {}
|
||||
|
||||
if not frappe.db.exists('Pricing Rule', {'disable': 0, args.transaction_type: 1}):
|
||||
return
|
||||
|
||||
for apply_on in ['Item Code', 'Item Group', 'Brand']:
|
||||
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
|
||||
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
|
||||
|
@ -590,5 +590,11 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
|
||||
if (frm.doc.company) {
|
||||
frappe.db.get_value('Company', frm.doc.company, 'default_payable_account', (r) => {
|
||||
frm.set_value('credit_to', r.default_payable_account);
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -12,6 +12,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
}
|
||||
company() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
|
||||
let me = this;
|
||||
if (this.frm.doc.company) {
|
||||
frappe.db.get_value('Company', this.frm.doc.company, 'default_receivable_account', (r) => {
|
||||
me.frm.set_value('debit_to', r.default_receivable_account);
|
||||
});
|
||||
}
|
||||
}
|
||||
onload() {
|
||||
var me = this;
|
||||
|
@ -1987,22 +1987,23 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
def append_payment(payment_mode):
|
||||
payment = doc.append('payments', {})
|
||||
payment.default = payment_mode.default
|
||||
payment.mode_of_payment = payment_mode.parent
|
||||
payment.mode_of_payment = payment_mode.mop
|
||||
payment.account = payment_mode.default_account
|
||||
payment.type = payment_mode.type
|
||||
|
||||
doc.set('payments', [])
|
||||
invalid_modes = []
|
||||
for pos_payment_method in pos_profile.get('payments'):
|
||||
pos_payment_method = pos_payment_method.as_dict()
|
||||
mode_of_payments = [d.mode_of_payment for d in pos_profile.get('payments')]
|
||||
mode_of_payments_info = get_mode_of_payments_info(mode_of_payments, doc.company)
|
||||
|
||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||
for row in pos_profile.get('payments'):
|
||||
payment_mode = mode_of_payments_info.get(row.mode_of_payment)
|
||||
if not payment_mode:
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", row.mode_of_payment))
|
||||
continue
|
||||
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
payment_mode.default = row.default
|
||||
append_payment(payment_mode)
|
||||
|
||||
if invalid_modes:
|
||||
if invalid_modes == 1:
|
||||
@ -2018,6 +2019,24 @@ def get_all_mode_of_payments(doc):
|
||||
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
|
||||
{'company': doc.company}, as_dict=1)
|
||||
|
||||
def get_mode_of_payments_info(mode_of_payments, company):
|
||||
data = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
mpa.default_account, mpa.parent as mop, mp.type as type
|
||||
from
|
||||
`tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
where
|
||||
mpa.parent = mp.name and
|
||||
mpa.company = %s and
|
||||
mp.enabled = 1 and
|
||||
mp.name in (%s)
|
||||
group by
|
||||
mp.name
|
||||
""", (company, mode_of_payments), as_dict=1)
|
||||
|
||||
return {row.get('mop'): row for row in data}
|
||||
|
||||
def get_mode_of_payment_info(mode_of_payment, company):
|
||||
return frappe.db.sql("""
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
|
@ -8,7 +8,8 @@ frappe.ui.form.on('Tax Withholding Category', {
|
||||
if (child.company) {
|
||||
return {
|
||||
filters: {
|
||||
'company': child.company
|
||||
'company': child.company,
|
||||
'root_type': ['in', ['Asset', 'Liability']]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ from frappe.utils import cint, getdate
|
||||
class TaxWithholdingCategory(Document):
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_accounts()
|
||||
self.validate_thresholds()
|
||||
|
||||
def validate_dates(self):
|
||||
@ -25,6 +26,14 @@ class TaxWithholdingCategory(Document):
|
||||
if last_date and getdate(d.to_date) < getdate(last_date):
|
||||
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
|
||||
|
||||
def validate_accounts(self):
|
||||
existing_accounts = []
|
||||
for d in self.get('accounts'):
|
||||
if d.get('account') in existing_accounts:
|
||||
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get('account'))))
|
||||
|
||||
existing_accounts.append(d.get('account'))
|
||||
|
||||
def validate_thresholds(self):
|
||||
for d in self.get('rates'):
|
||||
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:
|
||||
|
@ -155,6 +155,8 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
|
||||
if filters.get("group_by") == "Group by Voucher":
|
||||
order_by_statement = "order by posting_date, voucher_type, voucher_no"
|
||||
if filters.get("group_by") == "Group by Account":
|
||||
order_by_statement = "order by account, posting_date, creation"
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
filters['company_fb'] = frappe.db.get_value("Company",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"column_break0",
|
||||
"supplier_group",
|
||||
"supplier_type",
|
||||
"pan",
|
||||
"allow_purchase_invoice_creation_without_purchase_order",
|
||||
"allow_purchase_invoice_creation_without_purchase_receipt",
|
||||
"disabled",
|
||||
@ -176,11 +175,6 @@
|
||||
"options": "Company\nIndividual",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "pan",
|
||||
"fieldtype": "Data",
|
||||
"label": "PAN"
|
||||
},
|
||||
{
|
||||
"fieldname": "language",
|
||||
"fieldtype": "Link",
|
||||
@ -438,11 +432,12 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2021-09-06 17:37:56.522233",
|
||||
"modified": "2021-10-20 22:03:33.147249",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
@ -1032,7 +1032,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
|
||||
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
|
||||
.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
|
||||
.format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True)
|
||||
|
||||
def throw_overbill_exception(self, item, max_allowed_amt):
|
||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||
@ -1354,8 +1354,8 @@ class AccountsController(TransactionBase):
|
||||
total = 0
|
||||
base_total = 0
|
||||
for d in self.get("payment_schedule"):
|
||||
total += flt(d.payment_amount)
|
||||
base_total += flt(d.base_payment_amount)
|
||||
total += flt(d.payment_amount, d.precision("payment_amount"))
|
||||
base_total += flt(d.base_payment_amount, d.precision("base_payment_amount"))
|
||||
|
||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
@ -1371,8 +1371,9 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||
if total != flt(grand_total, self.precision("grand_total")) or \
|
||||
base_total != flt(base_grand_total, self.precision("base_grand_total")):
|
||||
|
||||
if flt(total, self.precision("grand_total")) != flt(grand_total, self.precision("grand_total")) or \
|
||||
flt(base_total, self.precision("base_grand_total")) != flt(base_grand_total, self.precision("base_grand_total")):
|
||||
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
||||
|
||||
def is_rounded_total_disabled(self):
|
||||
|
@ -216,11 +216,14 @@ class StatusUpdater(Document):
|
||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||
item[args['target_ref_field']]) * 100
|
||||
|
||||
if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
|
||||
if overflow_percent - allowance > 0.01:
|
||||
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
|
||||
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
|
||||
|
||||
self.limits_crossed_error(args, item, qty_or_amount)
|
||||
if role not in frappe.get_roles():
|
||||
self.limits_crossed_error(args, item, qty_or_amount)
|
||||
else:
|
||||
self.warn_about_bypassing_with_role(item, qty_or_amount, role)
|
||||
|
||||
def limits_crossed_error(self, args, item, qty_or_amount):
|
||||
'''Raise exception for limits crossed'''
|
||||
@ -238,6 +241,19 @@ class StatusUpdater(Document):
|
||||
frappe.bold(item.get('item_code'))
|
||||
) + '<br><br>' + action_msg, OverAllowanceError, title = _('Limit Crossed'))
|
||||
|
||||
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
|
||||
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
|
||||
|
||||
msg = (_("{} of {} {} ignored for item {} because you have {} role.")
|
||||
.format(
|
||||
action,
|
||||
_(item["target_ref_field"].title()),
|
||||
frappe.bold(item["reduce_by"]),
|
||||
frappe.bold(item.get('item_code')),
|
||||
role)
|
||||
)
|
||||
frappe.msgprint(msg, indicator="orange", alert=True)
|
||||
|
||||
def update_qty(self, update_modified=True):
|
||||
"""Updates qty or amount at row level
|
||||
|
||||
|
@ -156,6 +156,8 @@ def get_employees_having_an_event_today(event_type):
|
||||
DAY({condition_column}) = DAY(%(today)s)
|
||||
AND
|
||||
MONTH({condition_column}) = MONTH(%(today)s)
|
||||
AND
|
||||
YEAR({condition_column}) < YEAR(%(today)s)
|
||||
AND
|
||||
`status` = 'Active'
|
||||
""",
|
||||
@ -166,6 +168,8 @@ def get_employees_having_an_event_today(event_type):
|
||||
DATE_PART('day', {condition_column}) = date_part('day', %(today)s)
|
||||
AND
|
||||
DATE_PART('month', {condition_column}) = date_part('month', %(today)s)
|
||||
AND
|
||||
DATE_PART('year', {condition_column}) < date_part('year', %(today)s)
|
||||
AND
|
||||
"status" = 'Active'
|
||||
""",
|
||||
|
@ -100,7 +100,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-23 20:27:36.027728",
|
||||
"modified": "2021-10-26 20:27:36.027728",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Taxes and Charges",
|
||||
|
@ -311,7 +311,7 @@ class ProductionPlan(Document):
|
||||
|
||||
if self.total_produced_qty > 0:
|
||||
self.status = "In Process"
|
||||
if self.total_produced_qty >= self.total_planned_qty:
|
||||
if self.check_have_work_orders_completed():
|
||||
self.status = "Completed"
|
||||
|
||||
if self.status != 'Completed':
|
||||
@ -575,6 +575,15 @@ class ProductionPlan(Document):
|
||||
|
||||
self.append("sub_assembly_items", data)
|
||||
|
||||
def check_have_work_orders_completed(self):
|
||||
wo_status = frappe.db.get_list(
|
||||
"Work Order",
|
||||
filters={"production_plan": self.name},
|
||||
fields="status",
|
||||
pluck="status"
|
||||
)
|
||||
return all(s == "Completed" for s in wo_status)
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_raw_materials(doc, warehouses=None):
|
||||
if isinstance(doc, str):
|
||||
|
@ -308,5 +308,7 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
|
||||
erpnext.patches.v13_0.add_default_interview_notification_templates
|
||||
erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
|
||||
erpnext.patches.v13_0.requeue_failed_reposts
|
||||
erpnext.patches.v12_0.update_production_plan_status
|
||||
erpnext.patches.v13_0.healthcare_deprecation_warning
|
||||
erpnext.patches.v14_0.delete_healthcare_doctypes
|
||||
erpnext.patches.v13_0.create_pan_field_for_india #2
|
||||
|
31
erpnext/patches/v12_0/update_production_plan_status.py
Normal file
31
erpnext/patches/v12_0/update_production_plan_status.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2021, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("manufacturing", "doctype", "production_plan")
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabProduction Plan` ppl
|
||||
SET status = "Completed"
|
||||
WHERE ppl.name IN (
|
||||
SELECT ss.name FROM (
|
||||
SELECT
|
||||
(
|
||||
count(wo.status = "Completed") =
|
||||
count(pp.name)
|
||||
) =
|
||||
(
|
||||
pp.status != "Completed"
|
||||
AND pp.total_produced_qty >= pp.total_planned_qty
|
||||
) AS should_set,
|
||||
pp.name AS name
|
||||
FROM
|
||||
`tabWork Order` wo INNER JOIN`tabProduction Plan` pp
|
||||
ON wo.production_plan = pp.name
|
||||
GROUP BY pp.name
|
||||
HAVING should_set = 1
|
||||
) ss
|
||||
)
|
||||
""")
|
28
erpnext/patches/v13_0/create_pan_field_for_india.py
Normal file
28
erpnext/patches/v13_0/create_pan_field_for_india.py
Normal file
@ -0,0 +1,28 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('buying', 'doctype', 'supplier', force=True)
|
||||
frappe.reload_doc('selling', 'doctype', 'customer', force=True)
|
||||
|
||||
custom_fields = {
|
||||
'Supplier': [
|
||||
{
|
||||
'fieldname': 'pan',
|
||||
'label': 'PAN',
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'supplier_type'
|
||||
}
|
||||
],
|
||||
'Customer': [
|
||||
{
|
||||
'fieldname': 'pan',
|
||||
'label': 'PAN',
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'customer_type'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -614,11 +614,17 @@ def get_custom_fields():
|
||||
fieldtype='Currency', insert_after='monthly_hra_exemption', read_only=1, depends_on='house_rent_payment_amount')
|
||||
],
|
||||
'Supplier': [
|
||||
{
|
||||
'fieldname': 'pan',
|
||||
'label': 'PAN',
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'supplier_type'
|
||||
},
|
||||
{
|
||||
'fieldname': 'gst_transporter_id',
|
||||
'label': 'GST Transporter ID',
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'supplier_type',
|
||||
'insert_after': 'pan',
|
||||
'depends_on': 'eval:doc.is_transporter'
|
||||
},
|
||||
{
|
||||
@ -640,11 +646,17 @@ def get_custom_fields():
|
||||
}
|
||||
],
|
||||
'Customer': [
|
||||
{
|
||||
'fieldname': 'pan',
|
||||
'label': 'PAN',
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'customer_type'
|
||||
},
|
||||
{
|
||||
'fieldname': 'gst_category',
|
||||
'label': 'GST Category',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'customer_type',
|
||||
'insert_after': 'pan',
|
||||
'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
|
||||
'default': 'Unregistered'
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ def validate_gstin_for_india(doc, method):
|
||||
.format(doc.gst_state_number), title=_("Invalid GSTIN"))
|
||||
|
||||
def validate_pan_for_india(doc, method):
|
||||
if doc.get('country') != 'India' or not doc.pan:
|
||||
if doc.get('country') != 'India' or not doc.get('pan'):
|
||||
return
|
||||
|
||||
if not PAN_NUMBER_FORMAT.match(doc.pan):
|
||||
|
@ -16,7 +16,6 @@
|
||||
"customer_name",
|
||||
"gender",
|
||||
"customer_type",
|
||||
"pan",
|
||||
"tax_withholding_category",
|
||||
"default_bank_account",
|
||||
"lead_name",
|
||||
@ -486,11 +485,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Sales Invoice Creation Without Delivery Note"
|
||||
},
|
||||
{
|
||||
"fieldname": "pan",
|
||||
"fieldtype": "Data",
|
||||
"label": "PAN"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
@ -517,11 +511,12 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2021-09-06 17:38:54.196663",
|
||||
"modified": "2021-10-20 22:07:52.485809",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"charts": [],
|
||||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]",
|
||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]",
|
||||
"creation": "2020-03-12 14:47:51.166455",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
@ -10,7 +10,7 @@
|
||||
"idx": 0,
|
||||
"label": "ERPNext Settings",
|
||||
"links": [],
|
||||
"modified": "2021-08-05 12:15:59.052328",
|
||||
"modified": "2021-10-26 21:32:55.323591",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "ERPNext Settings",
|
||||
@ -27,6 +27,14 @@
|
||||
"link_to": "Projects Settings",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"color": "Grey",
|
||||
"doc_view": "",
|
||||
"icon": "dot-horizontal",
|
||||
"label": "Naming Series",
|
||||
"link_to": "Naming Series",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"icon": "accounting",
|
||||
"label": "Accounts Settings",
|
||||
|
@ -18,7 +18,9 @@
|
||||
"get_item_locations",
|
||||
"section_break_6",
|
||||
"locations",
|
||||
"amended_from"
|
||||
"amended_from",
|
||||
"print_settings_section",
|
||||
"group_same_items"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -110,14 +112,28 @@
|
||||
"options": "STO-PICK-.YYYY.-",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "print_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Print Settings"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"fieldname": "group_same_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Group Same Items",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-17 11:38:41.932875",
|
||||
"modified": "2021-10-05 15:08:40.369957",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -184,4 +200,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -2,10 +2,8 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@ -121,6 +119,34 @@ class PickList(Document):
|
||||
and (self.for_qty is None or self.for_qty == 0):
|
||||
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
|
||||
|
||||
def before_print(self, settings=None):
|
||||
if self.get("group_same_items"):
|
||||
self.group_similar_items()
|
||||
|
||||
def group_similar_items(self):
|
||||
group_item_qty = defaultdict(float)
|
||||
group_picked_qty = defaultdict(float)
|
||||
|
||||
for item in self.locations:
|
||||
group_item_qty[(item.item_code, item.warehouse)] += item.qty
|
||||
group_picked_qty[(item.item_code, item.warehouse)] += item.picked_qty
|
||||
|
||||
duplicate_list = []
|
||||
for item in self.locations:
|
||||
if (item.item_code, item.warehouse) in group_item_qty:
|
||||
item.qty = group_item_qty[(item.item_code, item.warehouse)]
|
||||
item.picked_qty = group_picked_qty[(item.item_code, item.warehouse)]
|
||||
item.stock_qty = group_item_qty[(item.item_code, item.warehouse)]
|
||||
del group_item_qty[(item.item_code, item.warehouse)]
|
||||
else:
|
||||
duplicate_list.append(item)
|
||||
|
||||
for item in duplicate_list:
|
||||
self.remove(item)
|
||||
|
||||
for idx, item in enumerate(self.locations, start=1):
|
||||
item.idx = idx
|
||||
|
||||
|
||||
def validate_item_locations(pick_list):
|
||||
if not pick_list.locations:
|
||||
|
@ -4,6 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _dict
|
||||
|
||||
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
|
||||
|
||||
@ -356,6 +357,39 @@ class TestPickList(ERPNextTestCase):
|
||||
sales_order.cancel()
|
||||
purchase_receipt.cancel()
|
||||
|
||||
def test_pick_list_grouping_before_print(self):
|
||||
def _compare_dicts(a, b):
|
||||
"compare dicts but ignore missing keys in `a`"
|
||||
for key, value in a.items():
|
||||
self.assertEqual(b.get(key), value, msg=f"{key} doesn't match")
|
||||
|
||||
# nothing should be grouped
|
||||
pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[
|
||||
_dict(item_code="A", warehouse="X", qty=1, picked_qty=2),
|
||||
_dict(item_code="B", warehouse="X", qty=1, picked_qty=2),
|
||||
_dict(item_code="A", warehouse="Y", qty=1, picked_qty=2),
|
||||
_dict(item_code="B", warehouse="Y", qty=1, picked_qty=2),
|
||||
])
|
||||
pl.before_print()
|
||||
self.assertEqual(len(pl.locations), 4)
|
||||
|
||||
# grouping should halve the number of items
|
||||
pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[
|
||||
_dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
|
||||
_dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
|
||||
_dict(item_code="A", warehouse="X", qty=3, picked_qty=2),
|
||||
_dict(item_code="B", warehouse="Y", qty=2, picked_qty=2),
|
||||
])
|
||||
pl.before_print()
|
||||
self.assertEqual(len(pl.locations), 2)
|
||||
|
||||
expected_items = [
|
||||
_dict(item_code="A", warehouse="X", qty=8, picked_qty=3),
|
||||
_dict(item_code="B", warehouse="Y", qty=6, picked_qty=4),
|
||||
]
|
||||
for expected_item, created_item in zip(expected_items, pl.locations):
|
||||
_compare_dicts(expected_item, created_item)
|
||||
|
||||
# def test_pick_list_skips_items_in_expired_batch(self):
|
||||
# pass
|
||||
|
||||
|
@ -89,7 +89,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
||||
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
|
||||
|
||||
elif out.get("warehouse"):
|
||||
out.update(get_bin_details(args.item_code, out.warehouse, args.company))
|
||||
if doc and doc.get('doctype') == 'Purchase Order':
|
||||
# calculate company_total_stock only for po
|
||||
bin_details = get_bin_details(args.item_code, out.warehouse, args.company)
|
||||
else:
|
||||
bin_details = get_bin_details(args.item_code, out.warehouse)
|
||||
|
||||
out.update(bin_details)
|
||||
|
||||
# update args with out, if key or value not exists
|
||||
for key, value in iteritems(out):
|
||||
@ -485,8 +491,9 @@ def get_item_tax_template(args, item, out):
|
||||
"item_tax_template": None
|
||||
}
|
||||
"""
|
||||
item_tax_template = args.get("item_tax_template")
|
||||
item_tax_template = _get_item_tax_template(args, item.taxes, out)
|
||||
item_tax_template = None
|
||||
if item.taxes:
|
||||
item_tax_template = _get_item_tax_template(args, item.taxes, out)
|
||||
|
||||
if not item_tax_template:
|
||||
item_group = item.item_group
|
||||
@ -502,17 +509,17 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
|
||||
taxes_with_no_validity = []
|
||||
|
||||
for tax in taxes:
|
||||
tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company')
|
||||
if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']:
|
||||
# In purchase Invoice first preference will be given to supplier invoice date
|
||||
# if supplier date is not present then posting date
|
||||
validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
|
||||
tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, 'company')
|
||||
if tax_company == args['company']:
|
||||
if (tax.valid_from or tax.maximum_net_rate):
|
||||
# In purchase Invoice first preference will be given to supplier invoice date
|
||||
# if supplier date is not present then posting date
|
||||
validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
|
||||
|
||||
if getdate(tax.valid_from) <= getdate(validation_date) \
|
||||
and is_within_valid_range(args, tax):
|
||||
taxes_with_validity.append(tax)
|
||||
else:
|
||||
if tax_company == args['company']:
|
||||
if getdate(tax.valid_from) <= getdate(validation_date) \
|
||||
and is_within_valid_range(args, tax):
|
||||
taxes_with_validity.append(tax)
|
||||
else:
|
||||
taxes_with_no_validity.append(tax)
|
||||
|
||||
if taxes_with_validity:
|
||||
@ -890,8 +897,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa
|
||||
res[fieldname] = pos_profile.get(fieldname)
|
||||
|
||||
if res.get("warehouse"):
|
||||
res.actual_qty = get_bin_details(args.item_code,
|
||||
res.warehouse).get("actual_qty")
|
||||
res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty")
|
||||
|
||||
return res
|
||||
|
||||
|
@ -123,12 +123,11 @@ def set_as_cancel(voucher_type, voucher_no):
|
||||
(now(), frappe.session.user, voucher_type, voucher_no))
|
||||
|
||||
def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||
args.update({"doctype": "Stock Ledger Entry"})
|
||||
args["doctype"] = "Stock Ledger Entry"
|
||||
sle = frappe.get_doc(args)
|
||||
sle.flags.ignore_permissions = 1
|
||||
sle.allow_negative_stock=allow_negative_stock
|
||||
sle.via_landed_cost_voucher = via_landed_cost_voucher
|
||||
sle.insert()
|
||||
sle.submit()
|
||||
return sle
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user