src/ApplicationBundle/Modules/Purchase/Controller/ExpressPurchaseController.php line 20

Open in your IDE?
  1. <?php
  2. namespace ApplicationBundle\Modules\Purchase\Controller;
  3. use ApplicationBundle\Controller\GenericController;
  4. use ApplicationBundle\Interfaces\SessionCheckInterface;
  5. use ApplicationBundle\Modules\Authentication\Constants\UserConstants;
  6. use ApplicationBundle\Modules\Purchase\Service\ExpressPurchaseService;
  7. use Symfony\Component\HttpFoundation\Request;
  8. /**
  9.  * Express Purchase — one screen that records a small goods purchase by collapsing
  10.  * PO → GRN → Bill in a single save (ExpressPurchaseService::create posts stock +
  11.  * payable + GL). For tedious-free buying; the full multi-step chain stays for
  12.  * complex purchases.
  13.  */
  14. class ExpressPurchaseController extends GenericController implements SessionCheckInterface
  15. {
  16.     /** The entry form. */
  17.     public function newAction(Request $request)
  18.     {
  19.         $conn $this->getDoctrine()->getManager()->getConnection();
  20.         $suppliers $conn->fetchAllAssociative(
  21.             "SELECT s.supplier_id AS id, COALESCE(h.name, CONCAT('Supplier #', s.supplier_id)) AS name
  22.              FROM acc_suppliers s LEFT JOIN acc_accounts_head h ON h.accounts_head_id = s.accounts_head_id
  23.              ORDER BY name"
  24.         );
  25.         $warehouses $conn->fetchAllAssociative("SELECT id, name FROM warehouse WHERE (status IS NULL OR status = 1) ORDER BY name");
  26.         // supplier types for the inline "add new supplier" (each maps to a GL parent)
  27.         $supplierTypes $conn->fetchAllAssociative("SELECT supplier_type_id AS id, name FROM supplier_type ORDER BY supplier_type_id");
  28.         // Source the product list from the PRODUCT MASTER (inv_products), not from
  29.         // past purchase_order_item rows — a fresh tenant has no prior PO items, so the
  30.         // old query returned an empty list. The FDM is the durable product identity
  31.         // (ApprovalFunction::PurchaseOrder reconciles product_id from it on approval).
  32.         $products $conn->fetchAllAssociative(
  33.             "SELECT id AS id,
  34.                     product_fdm AS fdm,
  35.                     COALESCE(NULLIF(product_name_fdm, ''), name) AS name,
  36.                     unit_type_id AS unit_type_id
  37.              FROM inv_products
  38.              WHERE (status IS NULL OR status = 1)
  39.                AND product_fdm IS NOT NULL AND product_fdm <> ''
  40.              ORDER BY name LIMIT 2000"
  41.         );
  42.         return $this->render('@Purchase/pages/express_purchase.html.twig', [
  43.             'suppliers'     => $suppliers,
  44.             'warehouses'    => $warehouses,
  45.             'products'      => $products,
  46.             'supplierTypes' => $supplierTypes,
  47.             'flash'         => $request->getSession()->getFlashBag()->get('express')[0] ?? null,
  48.         ]);
  49.     }
  50.     /**
  51.      * Inline "add new supplier" from the Express Purchase page — minimal info only
  52.      * (name + phone + type). Reuses Supplier::CreateNew (auto-creates the GL payable
  53.      * head). Returns JSON {success, id, name} so the page can add + select it without
  54.      * a reload.
  55.      */
  56.     public function quickAddSupplierAction(Request $request)
  57.     {
  58.         $em      $this->getDoctrine()->getManager();
  59.         $loginId = (int) $request->getSession()->get(UserConstants::USER_LOGIN_ID);
  60.         $name trim((string) $request->request->get('supplierName'''));
  61.         $type = (int) $request->request->get('supplierType'1);
  62.         if ($name === '') {
  63.             return new \Symfony\Component\HttpFoundation\JsonResponse(['success' => false'message' => 'Supplier name is required.']);
  64.         }
  65.         if ($type <= 0) { $type 1; }
  66.         try {
  67.             $sid = \ApplicationBundle\Modules\Purchase\Supplier::CreateNew($em$this, [
  68.                 'accountsHeadId'             => 0,   // 0 → auto-create the payable head
  69.                 'advanceHeadId'              => 0,
  70.                 'supplierName'               => $name,
  71.                 'supplierShortCode'          => '',
  72.                 'supplierType'               => $type,
  73.                 'categoryID'                 => 0,
  74.                 'supplierDue'                => 0,
  75.                 'supplierAddress'            => trim((string) $request->request->get('supplierAddress''')),
  76.                 'factoryAddress'             => '',
  77.                 'contactPerson'              => '',
  78.                 'contactNumber'              => trim((string) $request->request->get('contactNumber''')),
  79.                 'email'                      => trim((string) $request->request->get('email''')),
  80.                 'generalInfoTin'             => '',
  81.                 'generalInfoBin'             => '',
  82.                 'generalInfoBankName'        => '',
  83.                 'generalInfoBankAcName'      => '',
  84.                 'generalInfoBankAcNumber'    => '',
  85.                 'taggedItemGroups'           => [],
  86.                 'loginId'                    => $loginId,
  87.             ]);
  88.         } catch (\Throwable $e) {
  89.             return new \Symfony\Component\HttpFoundation\JsonResponse(['success' => false'message' => 'Could not create supplier: ' $e->getMessage()]);
  90.         }
  91.         if (!$sid) {
  92.             return new \Symfony\Component\HttpFoundation\JsonResponse(['success' => false'message' => 'Supplier not created — the chosen type may be missing its GL parent setting.']);
  93.         }
  94.         return new \Symfony\Component\HttpFoundation\JsonResponse(['success' => true'id' => (int) $sid'name' => $name]);
  95.     }
  96.     /** Save → run the express chain. */
  97.     public function saveAction(Request $request)
  98.     {
  99.         $p       $request->request;
  100.         $em      $this->getDoctrine()->getManager();
  101.         $session $request->getSession();
  102.         $loginId = (int) $session->get(UserConstants::USER_LOGIN_ID);
  103.         $supplierId  = (int) $p->get('supplierId');
  104.         $warehouseId = (int) $p->get('warehouseId');
  105.         // product select value = "id|fdm|unitTypeId" (avoids client-side wiring)
  106.         $sel   $p->get('productSel', []);
  107.         $qty   $p->get('qty', []);
  108.         $price $p->get('price', []);
  109.         $items = [];
  110.         for ($i 0$n count($sel); $i $n$i++) {
  111.             if (empty($sel[$i]) || (float) ($qty[$i] ?? 0) <= 0) { continue; }
  112.             $parts explode('|'$sel[$i]);
  113.             $items[] = [
  114.                 'productId'  => (int) ($parts[0] ?? 0),
  115.                 'fdm'        => $parts[1] ?? '',
  116.                 'unitTypeId' => (int) ($parts[2] ?? 0),
  117.                 'qty'        => (float) $qty[$i],
  118.                 'price'      => (float) ($price[$i] ?? 0),
  119.                 'type'       => 1,
  120.             ];
  121.         }
  122.         if (!$supplierId || !$warehouseId || empty($items)) {
  123.             $session->getFlashBag()->add('express''⚠ Pick a supplier, a warehouse and at least one product line.');
  124.             return $this->redirect($this->generateUrl('express_purchase_new'));
  125.         }
  126.         try {
  127.             $res ExpressPurchaseService::create($em, [
  128.                 'supplierId'           => $supplierId,
  129.                 'warehouseId'          => $warehouseId,
  130.                 'docDate'              => (new \DateTime('yesterday'))->format('Y-m-d'),
  131.                 'currency'             => 1'currencyMultiply' => 1'currencyMultiplyRate' => 1,
  132.                 'note'                 => $p->get('note''Express purchase'),
  133.                 'items'                => $items,
  134.             ], $loginId);
  135.             $session->getFlashBag()->add('express'sprintf(
  136.                 '✓ Done — PO #%d → GRN #%d → Bill #%d created and approved. Stock updated and supplier payable posted.',
  137.                 $res['poId'], $res['grnId'], $res['piId']
  138.             ));
  139.         } catch (\Throwable $e) {
  140.             $session->getFlashBag()->add('express''✕ Failed: ' $e->getMessage());
  141.         }
  142.         return $this->redirect($this->generateUrl('express_purchase_new'));
  143.     }
  144. }