Multimag  0.2.992
doc.postuplenie.php
См. документацию.
1 <?php
2 
3 // MultiMag v0.2 - Complex sales system
4 //
5 // Copyright (C) 2005-2018, BlackLight, TND Team, http://tndproject.org
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
16 //
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 //
22 
24  function __construct($doc = 0) {
25  parent::__construct($doc);
26  $this->doc_type = 1;
27  $this->typename = 'postuplenie';
28  $this->viewname = 'Поступление товара на склад';
29  $this->sklad_editor_enable = true;
30  $this->header_fields = 'sklad cena separator agent';
31  }
32 
33  public function getExtControls() {
34  return $this->ext_controls = array(
35  'inputDoc' => [
36  'type' => 'docNumDate',
37  'label' => 'Входящая накладная',
38  ],
39  'inputSf' => [
40  'type' => 'docNumDate',
41  'label' => 'Счет-фактура',
42  ],
43  'kladovshik' => [
44  'type' => 'select',
45  'label' => 'Сотрудник',
46  'data_source' => 'worker.listnames',
47  ],
48  'return' => [
49  'type' => 'checkbox',
50  'label' => 'Возвратный документ'
51  ],
52  );
53  }
54 
55  function initDefDopdata() {
56  $this->def_dop_data = array(
57  'kladovshik' => $this->firm_vars['firm_kladovshik_id'],
58  'input_doc' => '',
59  'input_date' => '',
60  'sf_num' => '',
61  'sf_date' => '',
62  'return' => 0,
63  'cena' => 1);
64  }
65 
66  function dopHead() {
67  global $tmpl, $db;
68  $klad_id = $this->dop_data['kladovshik'];
69  if (!$klad_id) {
70  $klad_id = $this->firm_vars['firm_kladovshik_id'];
71  }
72  $tmpl->addContent("<hr>");
73  $tmpl->addContent("Ном. вх. накладной:<br><input type='text' name='input_doc' value='{$this->dop_data['input_doc']}'><br>");
74  $tmpl->addContent("Дата. вх. накладной:<br><input type='text' name='input_date' value='{$this->dop_data['input_date']}'><br>");
75  $tmpl->addContent("Ном. вх. счет-фактуры:<br><input type='text' name='sf_num' value='{$this->dop_data['sf_num']}'><br>");
76  $tmpl->addContent("Дата. вх. счет-фактуры:<br><input type='text' name='sf_date' value='{$this->dop_data['sf_date']}'><br>");
77  $checked = $this->dop_data['return'] ? 'checked' : '';
78  $tmpl->addContent("<label><input type='checkbox' name='return' value='1' $checked>Возвратный документ</label><hr>
79  Кладовщик:<br><select name='kladovshik'>
80  <option value='0'>--не выбран--</option>");
81  $res = $db->query("SELECT `user_id`, `worker_real_name` FROM `users_worker_info` WHERE `worker`='1' ORDER BY `worker_real_name`");
82  while ($nxt = $res->fetch_row()) {
83  $s = ($klad_id == $nxt[0]) ? 'selected' : '';
84  $tmpl->addContent("<option value='$nxt[0]' $s>" . html_out($nxt[1]) . "</option>");
85  }
86  $tmpl->addContent("</select><br>");
87  }
88 
89  function dopSave() {
90  $new_data = array(
91  'input_doc' => request('input_doc'),
92  'input_date' => rcvdate('input_date'),
93  'sf_num' => request('sf_num'),
94  'sf_date' => rcvdate('sf_date'),
95  'return' => rcvint('return'),
96  'kladovshik' => rcvint('kladovshik')
97  );
98  $this->setDopDataA($new_data);
99  }
100 
102  public function extendedApplyAclCheck() {
103  $acl_obj = ['store.global', 'store.'.$this->doc_data['sklad']];
104  if (!\acl::testAccess($acl_obj, \acl::APPLY)) {
105  $d_start = date_day(time());
106  $d_end = $d_start + 60 * 60 * 24 - 1;
107  if (!\acl::testAccess($acl_obj, \acl::TODAY_APPLY)) {
108  throw new \AccessException('Не достаточно привилегий для проведения документа с выбранным складом '.$this->doc_data['sklad']);
109  } elseif ($this->doc_data['date'] < $d_start || $this->doc_data['date'] > $d_end) {
110  throw new \AccessException('Не достаточно привилегий для проведения документа с выбранным складом '.$this->doc_data['sklad'].' произвольной датой');
111  }
112  }
113  parent::extendedApplyAclCheck();
114  }
115 
117  public function extendedCancelAclCheck() {
118  $acl_obj = ['store.global', 'store.'.$this->doc_data['sklad']];
119  if (!\acl::testAccess($acl_obj, \acl::CANCEL)) {
120  $d_start = date_day(time());
121  $d_end = $d_start + 60 * 60 * 24 - 1;
122  if (!\acl::testAccess($acl_obj, \acl::TODAY_CANCEL)) {
123  throw new \AccessException('Не достаточно привилегий для отмены проведения документа с выбранным складом '.$this->doc_data['sklad']);
124  } elseif ($this->doc_data['date'] < $d_start || $this->doc_data['date'] > $d_end) {
125  throw new \AccessException('Не достаточно привилегий для отмены проведения документа с выбранным складом '.$this->doc_data['sklad'].' произвольной датой');
126  }
127  }
128  parent::extendedCancelAclCheck();
129  }
130 
131  public function docApply($silent = 0) {
132  global $db;
133  if(!$this->isAltNumUnique() && !$silent) {
134  throw new Exception("Номер документа не уникален!");
135  }
136  $res = $db->query("SELECT `doc_list`.`id`, `doc_list`.`date`, `doc_list`.`type`, `doc_list`.`sklad`, `doc_list`.`ok`, `doc_list`.`firm_id`,
137  `doc_sklady`.`dnc`, `doc_sklady`.`firm_id` AS `store_firm_id`, `doc_vars`.`firm_store_lock`, `doc_list`.`p_doc`
138  FROM `doc_list`
139  INNER JOIN `doc_sklady` ON `doc_sklady`.`id`=`doc_list`.`sklad`
140  INNER JOIN `doc_vars` ON `doc_list`.`firm_id` = `doc_vars`.`id`
141  WHERE `doc_list`.`id`='{$this->id}'");
142  $doc_params = $res->fetch_assoc();
143  $res->free();
144 
145  if (!$doc_params) {
146  throw new \Exception('Документ ' . $this->id . ' не найден');
147  }
148  if ($doc_params['ok'] && (!$silent)) {
149  throw new \Exception('Документ уже проведён!');
150  }
151 
152  // Запрет на списание со склада другой фирмы
153  if($doc_params['store_firm_id']!=null && $doc_params['store_firm_id']!=$doc_params['firm_id']) {
154  throw new \Exception("Выбранный склад принадлежит другой организации!");
155  }
156  // Ограничение фирмы списком своих складов
157  if($doc_params['firm_store_lock'] && $doc_params['store_firm_id']!=$doc_params['firm_id']) {
158  throw new \Exception("Выбранная организация может работать только со своими складами!");
159  }
160 
161  $res = $db->query("SELECT `doc_list_pos`.`tovar` AS `pos_id`, `doc_list_pos`.`cnt`, `doc_base`.`pos_type`, `doc_list_pos`.`id` AS `line_id`
162  , `doc_list_pos`.`cost` AS `line_price`, `doc_base`.`cost` AS `base_price`, `doc_base_cnt`.`mesto` AS `place`
163  FROM `doc_list_pos`
164  LEFT JOIN `doc_base` ON `doc_base`.`id`=`doc_list_pos`.`tovar`
165  LEFT JOIN `doc_base_cnt` ON `doc_base_cnt`.`id`=`doc_base`.`id` AND `doc_base_cnt`.`sklad`='{$doc_params['sklad']}'
166  WHERE `doc_list_pos`.`doc`='{$this->id}' AND `doc_base`.`pos_type`='0'");
167  while ($line = $res->fetch_assoc()) {
168  if(\cfg::get('doc', 'restrict_in_noplace') && !$silent) {
169  if( ($line['place']=='' || $line['place']==='0') && $line['pos_type']==0 ) {
170  throw new \Exception("У товара ID:{$line['pos_id']} не задано место хранения. Проведение поступления без места хранения запрещено в настройках.");
171  }
172  }
173  $db->query("UPDATE `doc_base_cnt` SET `cnt`=`cnt`+'{$line['cnt']}' WHERE `id`='{$line['pos_id']}' AND `sklad`='{$doc_params['sklad']}'");
174  // Если это первое поступление
175  if ($db->affected_rows == 0) {
176  $db->query("INSERT INTO `doc_base_cnt` (`id`, `sklad`, `cnt`) VALUES ('{$line['pos_id']}', '{$doc_params['sklad']}', '{$line['cnt']}')");
177  }
178  if(\cfg::get('poseditor', 'sn_restrict')) {
179  $r = $db->query("SELECT COUNT(`doc_list_sn`.`id`) FROM `doc_list_sn` WHERE `prix_list_pos`='{$line['line_id']}'");
180  $sn_data = $r->fetch_row();
181  if ($sn_data[0] != $line['cnt']) {
182  throw new \Exception("Количество серийных номеров товара {$line['pos_id']} ({$line['cnt']})"
183  . " не соответствует количеству серийных номеров ($sn_data[0])");
184  }
185  }
186  if(\cfg::get('doc', 'update_in_cost') == 1 && (!$silent)) {
187  if ($line['line_price'] != $line['base_price']) {
188  $db->query("UPDATE `doc_base` SET `cost`='{$line['line_price']}', `cost_date`=NOW() WHERE `id`='{$line['pos_id']}'");
189 
190  $array = ['price'=>['old'=>$line['base_price'], 'new'=>$line['line_price']]];
191  doc_log("UPDATE", json_encode($array, JSON_UNESCAPED_UNICODE), 'pos', $line['pos_id']);
192  }
193  }
194 
195  }
196  if ($silent) {
197  return;
198  }
199  parent::docApply($silent);
200  // Транзиты
201  if($doc_params['p_doc']) {
202  $res = $db->query("SELECT `id`, `ok` FROM `doc_list` WHERE `ok`>0 AND `type`=12 AND `id`={$doc_params['p_doc']}");
203  if ($res->num_rows) {
204  $res = $db->query("SELECT `doc_list_pos`.`tovar`, `doc_list_pos`.`cnt`
205  FROM `doc_list_pos`
206  LEFT JOIN `doc_base` ON `doc_base`.`id`=`doc_list_pos`.`tovar`
207  WHERE `doc_list_pos`.`doc`='{$doc_params['p_doc']}'");
208  $vals = '';
209  while ($line = $res->fetch_row()) {
210  if ($vals) {
211  $vals .= ',';
212  }
213  $vals .= "('$line[0]', '$line[1]')";
214  }
215  if($vals) {
216  $db->query("INSERT INTO `doc_base_dop` (`id`, `transit`) VALUES $vals
217  ON DUPLICATE KEY UPDATE `transit`=`transit`-VALUES(`transit`)");
218  } else {
219  throw new \Exception("Не удалось провести пустой документ!");
220  }
221  }
222  }
223 
224  if(\cfg::get('doc', 'update_in_cost') == 2) {
225  $res = $db->query("SELECT `doc_list_pos`.`tovar` AS `pos_id`, `doc_base`.`cost` AS `base_price`
226  FROM `doc_list_pos`
227  LEFT JOIN `doc_base` ON `doc_base`.`id`=`doc_list_pos`.`tovar`
228  WHERE `doc_list_pos`.`doc`='{$this->id}' AND `doc_base`.`pos_type`='0'");
229  while ($line = $res->fetch_assoc()) {
230  $acp = getInCost($line['pos_id'], $doc_params['date']);
231  if ($line['base_price'] != $acp) {
232  $db->query("UPDATE `doc_base` SET `cost`='$acp', `cost_date`=NOW() WHERE `id`='{$line['pos_id']}'");
233  $array = ['price'=>['old'=>$line['base_price'], 'new'=>$acp]];
234  doc_log("UPDATE", json_encode($array, JSON_UNESCAPED_UNICODE), 'pos', $line['pos_id']);
235  }
236  }
237  }
238  }
239 
240  function docCancel() {
241  global $db;
242  $rs = $db->query("SELECT `doc_list`.`id`, `doc_list`.`date`, `doc_list`.`type`, `doc_list`.`sklad`, `doc_list`.`ok`, `doc_sklady`.`dnc`
243  FROM `doc_list`
244  LEFT JOIN `doc_sklady` ON `doc_sklady`.`id`=`doc_list`.`sklad`
245  WHERE `doc_list`.`id`='{$this->id}'");
246  if (!$rs->num_rows) {
247  throw new Exception("Документ {$this->id} не найден!");
248  }
249  $nx = $rs->fetch_assoc();
250  if (!$nx['ok']) {
251  throw new Exception("Документ ещё не проведён!");
252  }
253 
254  $db->update('doc_list', $this->id, 'ok', 0);
255  $this->doc_data['ok'] = 0;
256 
257  $res = $db->query("SELECT `doc_list_pos`.`tovar`, `doc_list_pos`.`cnt`, `doc_base_cnt`.`cnt`, `doc_base`.`name`, `doc_base`.`proizv`, `doc_base`.`pos_type`, `doc_base`.`vc`
258  FROM `doc_list_pos`
259  LEFT JOIN `doc_base` ON `doc_base`.`id`=`doc_list_pos`.`tovar`
260  LEFT JOIN `doc_base_cnt` ON `doc_base_cnt`.`id`=`doc_base`.`id` AND `doc_base_cnt`.`sklad`='{$nx['sklad']}'
261  WHERE `doc_list_pos`.`doc`='{$this->id}'");
262  while ($nxt = $res->fetch_row()) {
263  if ($nxt[5] == 0) {
264  if (!$nx['dnc']) {
265  if ($nxt[1] > $nxt[2]) {
266  $budet = $nxt[2] - $nxt[1];
267  $badpos = $nxt[0];
268  throw new Exception("Невозможно, т.к. будет недостаточно ($budet) товара '$nxt[3]:$nxt[4] - $nxt[6]($nxt[0])' на складе!");
269  }
270  }
271  $db->query("UPDATE `doc_base_cnt` SET `cnt`=`cnt`-'$nxt[1]' WHERE `id`='$nxt[0]' AND `sklad`='{$nx['sklad']}'");
272  if (!$nx['dnc']) {
273  $budet = getStoreCntOnDate($nxt[0], $nx['sklad']);
274  if ($budet < 0) {
275  $badpos = $nxt[0];
276  throw new Exception("Невозможно, т.к. будет недостаточно ($budet) товара '$nxt[3]:$nxt[4] - $nxt[6]($nxt[0])' !");
277  }
278  }
279  }
280  }
281  // Транзиты
282  if($this->doc_data['p_doc']) {
283  $res = $db->query("SELECT `id`, `ok` FROM `doc_list` WHERE `ok`>0 AND `type`=12 AND `id`={$this->doc_data['p_doc']}");
284  if ($res->num_rows) {
285  $res = $db->query("SELECT `doc_list_pos`.`tovar`, `doc_list_pos`.`cnt`
286  FROM `doc_list_pos`
287  LEFT JOIN `doc_base` ON `doc_base`.`id`=`doc_list_pos`.`tovar`
288  WHERE `doc_list_pos`.`doc`='{$this->doc_data['p_doc']}'");
289  $vals = '';
290  while ($nxt = $res->fetch_row()) {
291  if ($vals) {
292  $vals .= ',';
293  }
294  $vals .= "('$nxt[0]', '$nxt[1]')";
295  }
296  if($vals) {
297  $db->query("INSERT INTO `doc_base_dop` (`id`, `transit`) VALUES $vals
298  ON DUPLICATE KEY UPDATE `transit`=`transit`+VALUES(`transit`)");
299  }
300  }
301  }
302  $this->sentZEvent('cancel');
303  }
304 
309  public function getMorphList() {
310  $morphs = array(
311  'realizaciya' => ['name'=>'realizaciya', 'document' => 'realizaciya', 'viewname' => 'Реализация', ],
312  'rbank' => ['name'=>'rbank', 'document' => 'rbank', 'viewname' => 'Расходный банковский ордер', ],
313  'rko' => ['name'=>'rko', 'document' => 'rko', 'viewname' => 'Расходный кассовый ордер', ],
314  'payinfo' => ['name'=>'payinfo', 'document' => 'payinfo', 'viewname' => 'Информация о безналичном платеже', ],
315  );
316  return $morphs;
317  }
318 
323  protected function morphTo_realizaciya() {
324  $new_doc = new \doc_Realizaciya();
325  $new_doc->createFromP($this);
326  $data = [
327  'cena' => $this->dop_data['cena'],
328  'platelshik' => $this->doc_data['agent'],
329  'gruzop' => $this->doc_data['agent'],
330  'received' => 0,
331  ];
332  $new_doc->setDopDataA($data);
333  return $new_doc;
334  }
335 
340  protected function morphTo_payinfo() {
341 
342  $this->recalcSum();
343  $new_doc = new \doc_PayInfo();
344  $new_doc->createFrom($this);
346  $new_doc->setDocData('bank', $this->getDocData('bank'));
347  return $new_doc;
348  }
349 
354  protected function morphTo_rbank() {
355  global $db;
356  $this->recalcSum();
357  $new_doc = new \doc_rbank();
358  $new_doc->createFrom($this);
359  $codename = $this->getDopData('return')?'goods_return':'goods_buy';
360  $new_doc->setDebitTypeFromCodename($codename);
361  return $new_doc;
362  }
363 
368  protected function morphTo_rko() {
369  global $db;
370  $this->recalcSum();
371  $new_doc = new \doc_rko();
372  $new_doc->createFrom($this);
373  $codename = $this->getDopData('return')?'goods_return':'goods_buy';
374  $new_doc->setDebitTypeFromCodename($codename);
376  $new_doc->setDocData('kassa', $pref->getSitePref('site_cash_id'));
377  return $new_doc;
378  }
379 }
$res
Definition: fixer.php:178
extendedCancelAclCheck()
Выполнение дополнительных проверок доступа для отмены документа
rcvint($varname, $def=0)
Безопасное получение целого значения
Definition: core.php:208
getDopData($name)
Получить значение дополнительного параметра документа. Вернёт пустую строку в случае отсутствия парам...
Definition: document.php:292
static getInstance()
Definition: pref.php:70
__construct($doc=0)
Конструктор
$pref
Definition: counter.php:49
getInCost($pos_id, $limit_date=0, $serv_mode=0)
Definition: doc.core.php:470
html_out($data)
Обёртка над htmlentities.
Definition: core.php:249
request($varname, $def='')
Definition: core.php:190
const TODAY_CANCEL
Отмена проведения / остановка текущим днём
Definition: acl.php:34
rcvdate($varname, $def='1970-01-01')
Безопасное получение строки с датой
Definition: core.php:222
Базовый класс для всех документов системы. Содержит основные методы для работы с документами.
sentZEvent($event_type)
$line
Definition: priceload.php:39
$s
Definition: price_an.php:409
$db
extendedApplyAclCheck()
Выполнение дополнительных проверок доступа для проведения документа
static testAccess($object, $flags, $no_redirect=false)
Definition: acl.php:173
getStoreCntOnDate($pos_id, $store_id, $unixtime=null, $noBreakIfMinus=0, $extinfo=false)
Definition: doc.core.php:515
const APPLY
Проведение / запуск
Definition: acl.php:31
$tmpl
setDopDataA($array)
Установить дополнительные данные текущего документа
Definition: document.php:327
Документ *Поступление*.
isAltNumUnique()
Проверка уникальности альтернативного порядкового номера документа
const TODAY_APPLY
Проведение / запуск текущим днём
Definition: acl.php:33
$data
Definition: api.php:27
date_day($date)
const CANCEL
Отмена проведения / остановка
Definition: acl.php:32
$doc
Definition: doc.php:28
static get($sect, $param, $default=null)
Definition: cfg.php:46
doc_log($motion, $desc, $object='', $object_id=0)
Definition: doc.core.php:278
$r
Definition: keygen.php:23
getDocData($name)
Получить значение основного параметра документа. Вернёт пустую строку в случае отсутствия параметра ...
Definition: document.php:226